Передача указателей на функции как интерфейс 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 (для доступа к локальным вариабелям) и т. Д. В этих условиях ваш подход кажется быть способом идти.