xll 函数的线程安全

Posted

技术标签:

【中文标题】xll 函数的线程安全【英文标题】:Thread-safety of xll functions 【发布时间】:2017-06-05 19:03:19 【问题描述】:

我有这个愚蠢的 xll 函数:

__declspec(dllexport) long F(long arg1, long arg2)

    return arg1 + arg2;

我猜应该没有问题,生成的excel函数将是线程安全的。

但是一个接受数组并返回数组的函数呢?以函数为例:

__declspec(dllexport) LPXLOPER12 WINAPI G(LPXLOPER12 arrayin1, LPXLOPER12 arrayin2)

    static XLOPER12 xlArray; // the reference to this one is going to be returned
    // treating arrayin1 and arrayin2 and outputting something in xlArray
    return static_cast<LPXLOPER12>(&xlArray);

显然static 不利于 excel 中的线程安全和并发调用。我应该如何修改我的函数以确保线程安全?

编辑

使用句柄的代码示例:

__declspec(dllexport) LPXLOPER12 WINAPI CreateComplex(LPXLOPER12 arrayin1, LPXLOPER12 arrayin2)

    static XLOPER12 xlArray;
    xlArray.xltype = xltypeStr;
    if (arrayin1->xltype != xltypeNum)
    
        xlArray.val.str = L"blah";
        return static_cast<LPXLOPER12>(&xlArray);
    
    if (arrayin2->xltype != xltypeNum)
    
        xlArray.val.str = L"blahblah";
        return static_cast<LPXLOPER12>(&xlArray);
    
    double real = arrayin1->val.num;
    double imag = arrayin2->val.num;
    Complex * z = new Complex(real, imag);
    char * handle = StoreObject("Complex", z);
    xlArray.xltype = xltypeStr;
    FromCharPtrToWChartPtr(handle, &xlArray.val.str);
    return static_cast<LPXLOPER12>(&xlArray);


__declspec(dllexport) double WINAPI GetComplexNorm(LPXLOPER12 arrayin)

    char *handle = nullptr;
    FromWChartPtrToWharPtr(arrayin->val.str, &handle);
    Complex * z = (Complex*)RetrieveObject(handle);
    double res = z->getNorm();
    return res;

Complex 是一个经典的复数类,“记忆”功能如下(注意它是旧的 C 代码):

#include "stdafx.h"
#include <string.h>
#include "memory.h"
#include <stdio.h>
#include "XLCALL.H"
#include <cstdlib>

void FromCharPtrToWChartPtr(char * from, XCHAR **to)

    size_t len = strlen(from);
    *to = new XCHAR[len + 1];
    *to[0] = static_cast<XCHAR>(len);
    if (len > 0)
    
        mbstowcs(*to + 1, from, len + 1);
    


void FromWChartPtrToWharPtr(XCHAR * from, char **to)

    size_t len = from[0];
    *to = new char[len + 1];
    wcstombs(*to, from + 1, len);


typedef struct _TObject

    char *name;
    int   version; /* increments by 1 for every new store operation */
    void *data;
    void *next;
 TObject;


static TObject *cache = NULL;

#define SEPARATOR '#'

TObject* FindNode(char* name)

    TObject *node = cache;
    while (node)
    
        if (_stricmp(node->name, name) == 0)
            break;

        node = (TObject*)node->next;
    

    return node;


#define FAILURE -1
#define SUCCESS 0

char* StoreObject(char* name, void* data)

    static char *routine = "StoreObject";
    int          status = FAILURE;
    char        *handle = NULL;
    TObject     *node;

    static char buffer[255];

    if (data == NULL)
    
        // error to handle later
        goto done;
    

    if (name == NULL)
    
        // error to handle later
        goto done;
    

    if (strlen(name) > 200)
    
        // error to handle later
        goto done;
    

    node = FindNode(name);
    if (node == NULL)
    
        node = new TObject();
        if (node == NULL)
            goto done;

        node->name = _strdup(name);
        node->version = 1;
        node->data = data;
        node->next = cache;
        cache = node;
    
    else
    
        node->version += 1;
        delete node->data; // should I template taylor the object destruction diffenrently ?
        node->data = data;
    

    sprintf(buffer, "%s%c%d\0", node->name, SEPARATOR, node->version);
    handle = _strdup(buffer);
    if (handle == NULL)
        goto done;

    strcpy(handle, buffer);
    status = SUCCESS;

done:
    if (status != SUCCESS)
    
        // error to handle later
        delete handle;
        handle = NULL;
    

    return handle;


void* RetrieveObject(char* handle)

    static char *routine = "RetrieveObject";
    int          status = FAILURE;
    void        *data = NULL;
    char        *name = NULL;
    TObject     *node;
    char        *sep;

    if (handle == NULL)
    
        // error to handle later
        goto done;
    

    name = _strdup(handle);
    if (name == NULL)
        goto done;

    /* remove version number from lookup string */
    sep = strchr(name, SEPARATOR);
    if (sep != NULL)
        *sep = '\0';

    node = FindNode(name);
    if (node == NULL)
    
        // error to handle later
        goto done;
    

    data = node->data;
    status = SUCCESS;

done:
    if (status != SUCCESS)
    
        // error to handle later
        data = NULL;
    

    delete name;
    return data;


void FreeObjects()

    TObject *next = cache;
    TObject *node;

    while (next)
    
        node = next;
        next = (TObject*)node->next;
        delete node->name;
        delete node->data;
        delete node;
    

【问题讨论】:

使其非静态并分配内存? 【参考方案1】:

this MSN webpage 上有很好的介绍,确实 static 不是线程安全的,需要将新变量分配到线程本地安全内存中。您可以查看上述页面中的函数get_thread_local_xloper12

【讨论】:

谢谢,我会看看并验证你的答案。奇怪的是链接上的代码据说是c#但是是vanilla c++。 备注:我习惯于实现返回“句柄”的函数,以及以“句柄”为参数的函数,句柄是内存中对象的标签。您认为该框架适合多线程吗?我将在今晚晚些时候发布代码 是的,如果相关的内存和代码本身是线程安全的(用临界区锁定)但它可能难以实现,请注意死锁。在您的情况下,我更愿意将函数标记为无线程安全,以避免句柄的所有潜在问题...... 您的缓存不是线程安全的,您需要使用锁保护每个操作(查找、检索、释放...),以防止多个线程同时修改缓存的状态。 Ok thx,我从来没有真正学会编写线程安全代码,我想我必须这样做

以上是关于xll 函数的线程安全的主要内容,如果未能解决你的问题,请参考以下文章

从构造函数看线程安全

线程安全与可重入

线程安全与可重入函数

对线程安全, 可重入函数, 异步安全的理解

C/C++线程安全型队列的实现

Win32线程安全问题.同步函数