Потокобезопасность функций xll

У меня есть эта глупая функция 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;
    }
}

person Olorin    schedule 05.06.2017    source источник
comment
Сделать его нестатичным и выделить память?   -  person NathanOliver    schedule 05.06.2017


Ответы (1)


На этой веб-странице MSN есть хорошая презентация, действительно static не является потокобезопасным, вам нужно выделить новую переменную в локальную безопасную память потока. Вы можете посмотреть функцию get_thread_local_xloper12 на вышеупомянутой странице.

person Malick    schedule 07.06.2017
comment
Спасибо, я посмотрю и подтвержу ваш ответ после. Странно, что в ссылке указан код С#, но это ванильный С++. - person Olorin; 07.06.2017
comment
Примечание. Я привык реализовывать функции, возвращающие дескрипторы, и функции, принимающие дескрипторы в качестве параметров, причем дескрипторы являются метками для объекта в памяти. Считаете ли вы, что фреймворк хорошо подходит для многопоточности? вечером выложу код - person Olorin; 07.06.2017
comment
Да, если связанная память и код сами по себе потокобезопасны (блокировка с критическим разделом), но это может быть сложно реализовать, будьте осторожны с взаимоблокировками. В вашем случае я бы предпочел пометить функции как небезопасные для потоков, чтобы избежать всех потенциальных проблем с дескрипторами... - person Malick; 07.06.2017
comment
Ваш кеш не является потокобезопасным, вам необходимо защитить каждую операцию ( Find, Retrieve, Free ...) блокировками, чтобы предотвратить одновременное изменение состояния кеша несколькими потоками. - person Malick; 08.06.2017
comment
Хорошо, спасибо, я так и не научился писать потокобезопасный код, думаю, мне придется - person Olorin; 08.06.2017
comment
Так что в основном мне просто нужно определить static std::mutex mutx; вне функций и обернуть коды тела всех функций между mutx.lock(); и mutx.unlock() и все? - person Olorin; 08.06.2017
comment
Разве я не могу сделать то же самое для функции xll (по модулю переключения на объявления нестатических массивов)? (Поэтому мне не нужно прибегать к ссылке msdn, на которую вы мне указываете.) - person Olorin; 08.06.2017
comment
Моя ошибка, нет, это невозможно, потому что каждый раз, когда excel вызывает ваш xll, он должен копировать возвращаемую переменную внутри. Если он заблокирован другим процессом, я думаю, он выйдет из строя. - person Malick; 08.06.2017
comment
Значит, защита моих функций памяти невозможна сама по себе? - person Olorin; 08.06.2017
comment
Последний комментарий: вы должны защитить все операции, связанные со статическим кешем, и вы не можете защитить возвращаемую переменную таким же образом, вам нужно использовать локальную память потока для возвращаемой переменной, как я сказал в своем ответе. - person Malick; 08.06.2017