
Жесткая типизация в языке С++ подчас создаёт непреодолимые трудности. Например, вы, программист-разработчик, и занимаетесь реализацией некоего модульного проекта. Предположим, что это звуковой редактор с плагинами. Плагины вы реализуете в виде .dll, которые динамически подключаются при загрузке редактора. И всё бы ничего, но функции плагинов настолько разнообразны и разноплановы, что описать их в некоем формализованном виде просто нереально. В самом деле, представьте: один плагин принимает пять параметров, возвращает три; другой плагин принимает десять входных аргументов, и не выдает ни одного, зато пишет файл и рисует график на экране. Как быть в таком случае?
Читать далее... У вас есть имя функции, которую нужно выполнить, и список параметров (типов), которые нужно передать функции. Достаточно ли этого в условиях жесткой типизации языка С++, чтобы выполнить функцию? Оказывается, вполне достаточно, если воспользоваться прямым помещением параметров в стек с помощью встроенного ассемблера, а по имени функции извлечь ее адрес из .dll с помощью WinAPI-функции ::GetProcAddress(). Другими словами, это позволяет избавиться от объявления функции непосредственно в коде программы, и вынести объявление функции во внешний конфигурационный файл.
Приблизительный код универсального вызывателя функций приведен ниже. Первым идёт параметр-указатель на функцию вида void func(), массив Array содержит значения или указатели любых типов, приведенных к __int32 (то есть двойное слово, DWORD, определено как stack_cell_t), и Count содержит количество двойных слов. Входные данные в Array должны быть выровнены по 4-байтной границе.
//------------------------------------------------------------ typedef __int32 stack_cell_t;
__int64 call(void (__cdecl * FuncPtr)(), stack_cell_t * Array, int Count) { __int64 ResultInt64 = 0; stack_cell_t * val_ptr = Array + Count; int Cnt = Count; int StackSize = Count * sizeof(int); int StackCellSz = sizeof(stack_cell_t); __asm { mov esi, esp cmp Cnt, 0 je CallProc LoopPush: // indexing values mov ecx, StackCellSz sub val_ptr, ecx // saving ptr mov eax, [val_ptr] // updating stack sub esp, ecx mov edx, [eax] mov [esp], edx // loop dec Cnt jnz LoopPush // call CallProc: call [FuncPtr] mov dword ptr [ResultInt64], eax mov dword ptr [ResultInt64+4], edx // check for stack state cmp esi, esp je StackIsCleared // clear stack add esp, StackSize StackIsCleared: } return ResultInt64; }//------------------------------------------------------------
|
В таком виде, как описано выше, функция call() будет возвращать 64-битное целое, которое может преобразовано в целое любого другого типа, либо в любой указатель.
Если необходимо возвратить число с плавающей запятой, то строки
//------------------------------------------------------------ mov dword ptr [ResultInt64], eax mov dword ptr [ResultInt64+4], edx //------------------------------------------------------------
|
необходимо заменить на
//------------------------------------------------------------ fstp [ResultFloat64] //------------------------------------------------------------
|
Разумеется, ResultFloat64 нужно объявить в функции call как double.
P.S. Вообще, описанный материал достаточно обширный и предоставляет немалое поле для разного рода исследований и проверок. При необходимости, Куб 0 выложит дополнительную информацию и примеры в комментариях.
@темы:
Asm & C++,
Нетривиальный вызов