Куб 0
Жесткая типизация в языке С++ подчас создаёт непреодолимые трудности. Например, вы, программист-разработчик, и занимаетесь реализацией некоего модульного проекта. Предположим, что это звуковой редактор с плагинами. Плагины вы реализуете в виде .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++, Нетривиальный вызов