我正在开发一个复杂的程序,该程序将具有调用函数的插件,但是这些函数的方法将在启动时选择,并使用函数指针进行分配。
而不是传递函数指针,我希望在主可执行文件中有一些有效的包装函数来调用适当的函数。
由于这是针对插件接口的,调用约定将根据构建目标定义__cdecl
或定义__stdcall
(使用宏),并且函数将声明为extern "C"
.
基本上我希望能够在我的可执行文件中声明一个符号,插件可以根据需要加载。对于需要解决复杂科学问题的不同任务,但是有多种解决方案或方法可以获取这些任务的结果,这些将自行存储在插件中,因此很容易添加新方法(无需重新编译整个应用程序)这也使得共享新方法变得更容易,因为任何拥有基本代码的人都可以添加任何不需要自己经验的插件。
我想出的任何方法都可以使用这个概念,或者我必须在加载插件时将函数映射传递给插件,但是该函数映射的细节取决于加载的配置和插件,因此我实际上并不在我完成加载插件之前知道它是什么,这将是一个问题。因此,我的解决方案是将地图作为一组全局变量存储在主可执行文件中,可通过包装函数访问。
然而,这不是直截了当的,因为函数具有调用约定,其中涉及在调用之后和返回之前操作堆栈,这应该在包装器上被忽略,它还应该jmp
对 intel x386 ASM 执行无条件跳转,而不是call
对 intel x386进行函数调用ASM 和控件应该从跳转到函数返回到调用代码而不是包装器。但是,我需要 C/C++ 代码独立于编译器/平台/处理器来执行此操作。
下面是我收集的一个基本概念示例,用于测试我的想法并展示我想要做什么:
C++ 代码(Microsoft Visual C++ 2010(特定))
#include <iostream>
void * pFunc;
int doit(int,int);
int wrapper(int, int);
int main() {
pFunc = (void*)doit;
std::cout << "Wrapper(2,3): " << wrapper(2,3) << std::endl;
std::cout << "doit(2,3): " << doit(2,3) << std::endl;
return 0; }
int doit(int a,int b) { return a*b; }
__declspec(naked) int wrapper(int, int) { __asm jmp pFunc }
代码已经过测试可以正常工作,两个调用都输出 6
wrapper 和 doit 的 ASM 输出
PUBLIC ?wrapper@@YAHHH@Z ; wrapper
; Function compile flags: /Odtp
; COMDAT ?wrapper@@YAHHH@Z
_TEXT SEGMENT
___formal$ = 8 ; size = 4
___formal$ = 12 ; size = 4
?wrapper@@YAHHH@Z PROC ; wrapper, COMDAT
; File c:\users\glen fletcher\documents\visual studio 2010\projects\test_wrapper\test_wrapper.cpp
; Line 15
jmp DWORD PTR ?pFunc@@3PAXA ; pFunc
?wrapper@@YAHHH@Z ENDP ; wrapper
_TEXT ENDS
PUBLIC ?doit@@YAHHH@Z ; doit
; Function compile flags: /Ogtp
; COMDAT ?doit@@YAHHH@Z
_TEXT SEGMENT
_a$ = 8 ; size = 4
_b$ = 12 ; size = 4
?doit@@YAHHH@Z PROC ; doit, COMDAT
; Line 14
push ebp
mov ebp, esp
mov eax, DWORD PTR _a$[ebp]
imul eax, DWORD PTR _b$[ebp]
pop ebp
ret 0
?doit@@YAHHH@Z ENDP ; doit
; Function compile flags: /Ogtp
_TEXT ENDS
用于包装器的非包装器 ASM
PUBLIC wrapper
_1$ = 8
_2$ = 12
_TEXT SEGMENT
wrapper PROC
push ebp
mov ebp, esp
mov eax, DWORD PTR _2$[ebp]
push eax
mov ecx, DWORD PTR _1$[ebp]
push ecx
call DWORD PTR pFunc
add esp, 8
pop ebp
ret 0
wrapper ENDP
_TEXT ENDS
如何获得以跨平台和跨编译器的方式生成的原始代码?与具有编译器生成的 Epilog 和 prolog 代码的 C/C++ 函数的标准相反,注意也不想对处理器做出假设,因此不能做一个单独的 ASM 文件,希望编译器生成代码仅使用无条件跳转语句。
goto
不起作用,因为 pFunc 是变量而不是标签,甚至不确定它goto
是否会在函数之间起作用。