Передача указателей на функции как интерфейс API в скомпилированную библиотеку

Дорогой стек обмена,

Я программирую МРТ сканер. Я не буду вдаваться в подробности, но я довольно ограничен в объеме кода, к которому у меня есть доступ, и в том, как все было настроено... неоптимально. У меня такая ситуация:

  • Есть большая библиотека, написанная на C++. В конечном итоге он выполняет "транскодирование" (наихудшим из возможных способов), выписывая сборку FPGA, которая делает ThThings. Он предоставляет набор функций для "пользовательского пространства", которые транслируются (через смесь макросов препроцессора и черной магии) в длинные строки из 16-битных и 32-битных слов. Способ, которым это делается, склонен к переполнению буфера и, как правило, к переполнению.*
  • Затем сборка FPGA выводится через прославленную последовательную связь с соответствующей электроникой, которая выполняет ее (выполняет сканирование) и возвращает данные обратно для обработки.
  • Предполагается, что программисты будут использовать функции, предоставляемые библиотекой, для выполнения своих задач в функциях C (не C++), которые связаны со стандартной библиотекой. К сожалению, в моем случае мне нужно расширить библиотеку.
  • Существует довольно сложная цепочка подстановки и токенизации препроцессора, вызовов и (в общем) вещей, происходящих между тем, как вы пишете doSomething() в своем коде, и соответствующей библиотечной функцией, фактически выполняющей его. Я думаю, что до некоторой степени понял, но это в основном означает, что у меня нет реального представления о масштабах чего-либо...

Короче говоря, моя проблема:

  • В середине метода, в глубоком темном уголке из многих тысяч строк кода в большом двоичном объекте, я почти не контролирую, с бог-знает-что происходит переменная область видимости, мне нужно:
    • Расширьте этот метод, чтобы взять в качестве аргумента указатель функции (на функцию пользователя), но
    • Пусть эта пользовательская функция, написанная после того, как библиотека скомпилирована, имеет доступ к переменным, которые являются локальными как для области действия метода, в котором она появляется, так и для переменных в функции (C), где она вызывается.

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

Краткий набросок того, как я планирую действовать следующим образом:

В библиотеке.cpp:

/* In something::something() /*
/* declare a pointer to a function */
    void (*fp)(int*, int, int, ...);
/* by default, the pointer points to a placeholder at compile time*/
    fp = &doNothing(...);

...

/* At the appropriate time, point the pointer to the userland function, whose address is supplied as an argument to something(): /*
    fp= userFuncPtr;
/* Declare memory for the user function to plonk data into */ 
    i_arr_coefficients = (int) malloc(SOMETHING_SENSIBLE);
/* Create a pointer to that array for the userland function */ 
    i_ptr_array=&i_arr_coefficients[0];
/* define a struct of pointers to local variables for the userland function to use*/
 ptrStrct=createPtrStruct(); 

/* Call the user's function: */
fp(i_ptr_array,ptrStrct, ...);

CarryOnWithSomethingElse();

Смысл функции-заполнителя состоит в том, чтобы держать все как есть, если пользовательская функция не связана. Я понимаю, что это можно заменить на #DEFINE, но хитрость или глупость компилятора может привести к странному (по крайней мере, на мой взгляд, невежественному) поведению.

В функции userland у нас будет что-то вроде:

void doUsefulThings(i_ptr_array, ptrStrct, localVariableAddresses, ...) { 
    double a=*ptrStrct.a;
    double b=*ptrStrct.b;
    double c=*localVariableAddresses.c;
    double d=doMaths(a, b, c);
    /* I.e. do maths using all of these numbers we've got from the different sources */

    storeData(i_ptr_array, d);
    /* And put the results of that maths where the C++ method can see it */
}

...

something(&doUsefulThings(i_ptr_array, ptrStrct, localVariableAddresses, ...), ...); 

...

Если это так же ясно, как грязь, пожалуйста, скажите мне! Большое спасибо за Вашу помощь. И, кстати, я искренне желаю, чтобы кто-нибудь сделал открытую аппаратную / исходную систему МРТ.

* Кроме того, это основное оправдание, которое производитель использует, чтобы отговорить нас от изменения большой библиотеки в первую очередь!

1 ответ

Решение

У вас есть полный доступ к коду C У вас ограниченный доступ к коду библиотеки C++. Код на C определяет функцию "doUsefullthings". Из кода C вы вызываете функцию "Something" (класс / функция C++) с указателем на "doUseFullThings" в качестве аргумента. Теперь управление переходит в библиотеку C++. Здесь различные аргументы выделяются памятью и инициализируются. Затем с этими аргументами вызывается doUseFullThings. Здесь управление переходит обратно к коду C. Короче говоря, основная программа (C) вызывает библиотеку (C++), а библиотека вызывает функцию C.

Одним из требований является то, что "пользовательская функция должна иметь доступ к локальной переменной из кода C, где она вызывается". Когда вы звоните "что-то", вы даете только адрес "doUseFullThings". Не существует параметра / аргумента "что-то", которое бы захватывало адрес локальных переменных. Таким образом, doUseFullThings не имеет доступа к этим переменным.

Оператор malloc возвращает указатель. Это не было обработано должным образом (вероятно, вы пытались дать нам обзор). Вы должны заботиться, чтобы освободить это где-нибудь.

Поскольку это смесь кода на C и C++, трудно использовать RAII (заботясь о выделенной памяти), Perfect forwarding (избегать копирования переменных), функции Lambda (для доступа к локальным вариабелям) и т. Д. В этих условиях ваш подход кажется быть способом идти.

Другие вопросы по тегам