Simulink "Access Violation" запись в переменную PWork в списке захвата лямбда-функции C ++ - PullRequest
0 голосов
/ 05 сентября 2018

У меня есть C ++ S-функция, которая обрабатывает множество потоковых операций через std :: thread, std :: async и обратные вызовы. Дело в том, что одним из обратных вызовов является S-функция, которая имеет буфер в списке захвата. Этот буфер находится в PWork модели Simulink. Однако, похоже, что Matlab вылетает, как только я пытаюсь записать в него.

Ниже приведен минимальный пример сбоя моей S-функции (только функция mdlStart), который содержит соответствующий код:

static void mdlStart(SimStruct *S)
{
    ssGetPWork(S)[0] = (void *) new ThreadedDataServer();
    ssGetPWork(S)[1] = (void *) new DatagramAssembler();
    ssGetPWork(S)[2] = (void *) new MyBufferType(); // actually an std::array<char, LARGENUMBER>

    auto server          = (ThreadedDataServer *) ssGetPWork(S)[0];
    auto assembler       = (DatagramAssembler*)   ssGetPWork(S)[1];
    auto copy_buffer_ptr = (MyBufferType *)       ssGetPWork(S)[2];

    server->attachDataHandler([&copy_buffer_ptr, &assembler](const ThreadedDataServer::buffer_t & buffer, size_t num_bytes)
    {
        /* Minimal crashing action */
        copy_buffer_ptr->at(5) = 'b'; // Any index != 0
        /* Original code */
        //std::copy(buffer.begin(), buffer.begin() + num_bytes, copy_buffer_ptr->data());
        //assembler->feedData(*copy_buffer_ptr, num_bytes);
    });
}

Обработчик вызывается из рабочего потока сервера данных (отличается от основного потока Simulink). Другие действия внутри функции обратного вызова работают гладко (чтение параметров, выполнение других операций ...).

Есть подсказка, почему это происходит? Этот же код работал в независимом исполняемом файле до его интеграции в Simulink.

Ответы [ 2 ]

0 голосов
/ 05 сентября 2018

Благодаря ответу @ max-langhof (висящий указатель) и некоторой дополнительной работе я наконец-то нашел решение:

static void mdlStart(SimStruct *S)
{
    ssGetPWork(S)[0] = (void *) new ThreadedDataServer();
    ssGetPWork(S)[1] = (void *) new DatagramAssembler();
    ssGetPWork(S)[2] = (void *) new MyBufferType(); // actually an std::array<char, LARGENUMBER>
    ssGetPWork(S)[3] = (void *) new std::mutex();

    auto server          = (ThreadedDataServer *) ssGetPWork(S)[0];
    auto assembler       = (DatagramAssembler*)   ssGetPWork(S)[1];
    auto copy_buffer_ptr = (MyBufferType *)       ssGetPWork(S)[2];
    auto assembly_mutex  = (std::mutex *)         ssGetPWork(S)[3];

    server->attachDataHandler([copy_buffer_ptr, assembler, assembly_mtx](const ThreadedDataServer::buffer_t & buffer, size_t num_bytes)
    {
        // Mutex scoped lock
        {
            std::lock_guard<std::mutex> lckg(assembly_mutex);
            std::copy(buffer.begin(), buffer.begin() + num_bytes, copy_buffer_ptr.data());
            assembler.feedData(copy_buffer_ptr, num_bytes);
        }
    });

Эта реализация решает две проблемы:

  • Переменные в списке захвата ссылались на указатель в стеке, который заканчивался висячими указателями и вызывал сбой.
  • Несколько обращений к обработчику данных одновременно обращались к буферу и объекту Assembler, очевидно вызывая другое нарушение прав доступа.

Сейчас работает: -)

0 голосов
/ 05 сентября 2018

Вы захватываете copy_buffer_ptr (локальная переменная стека) по ссылке. Эта ссылка будет зависать, как только вернется mdlStart, после чего вызывать лямбду будет неопределенным поведением. (Это также относится к assembler).

Исправление заключается в простом захвате copy_buffer_ptr и assembler по значению (это простые указатели, вы можете просто скопировать их без проблем):

server->attachDataHandler([copy_buffer_ptr, assembler](const ThreadedDataServer::buffer_t & buffer, size_t num_bytes)
{
  /* etc. */
});

Если у вас есть лямбда, которая переживет текущую область, подумайте долго и усердно, прежде чем захватывать что-либо по ссылке - если это локально в стеке, вы, вероятно, собираетесь сгореть.

...