我正在开发一个复杂的程序,它将具有调用函数的插件,但是这些函数的方法将在启动时选择,并使用函数指针进行分配。
我希望在主可执行文件中有一些有效的包装函数来调用相应的函数,而不是传递函数指针。
由于这是针对插件接口的,调用约定将根据构建目标(使用宏)定义__cdecl
或__stdcall
,并且函数将声明为extern "C"
基本上我希望能够在我的可执行文件中声明一个SYMBOL,插件可以根据需要加载。对于解决复杂科学问题所需的不同任务,但是有一系列解决方案或方法可以获得这些任务的结果,这些将自己存储在插件中,因此很容易添加新方法(不需要重新编译整个应用程序)这也使得分享新方法变得更容易,因为任何拥有基本代码的人都可以添加任何不需要经验的插件。
我计算出来的任何方法都可以使用这个概念,或者我在加载它时必须将函数映射传递给插件但是该函数映射的细节依赖于加载的配置和插件因此我不知道实际上我知道它是什么,直到我完成加载插件这将是一个问题。因此,我的解决方案将地图存储为主可执行文件中的一组全局变量,可通过包装函数访问。
然而,这不是直截了当的,因为函数具有调用约定,这些约定涉及在调用之后和返回之前操纵堆栈,这应该在包装器上被忽略,它也应该为intel x386 ASM执行无条件跳转jmp
而不是函数调用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
包装器和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
是否可以在函数之间起作用。
答案 0 :(得分:2)
就你的问题而言,
如何获得以跨平台和交叉编译方式生成的原始代码?
,答案是“完全没有”。
函数调用约定深入到平台和编译器/语言细节。你正在触摸所谓的 ABI (应用程序二进制接口);问题如:
this
)是如何实现的?我已经给出了类似的答案in this SO thread,然后特别针对关于做“downcalls”64位Windows的问题 - &gt; 32位Windows stdcall
。唉,除了“它很复杂,通常不可能而且总是非常强大的代码/编译器和依赖于操作系统”之外,并没有多少添加。
这可以在特定的情况下完成(技术术语是“thunking”。每个“thunk”都非常具体:比如说,如果你知道被调用的函数使用32位Windows / x86样式{ {1}}并且只有一个参数,您可以编写一个“thunk”来进行接口(可能还有处理器状态切换),允许您从64位Linux代码中调用它。这个thunk会有所不同其中第一个参数是fastcall
中传递的浮点值,但是......等等。
对于一般情况......请参阅the infinite heap of programming knowledge that's SO again,抱歉,不是通用函数指针 :(
修改强> 如果关注的是代码生成,那么请尝试以下方法:
XMM0
如果我使用/* sourcefile 1 */
extern void (*p)(char *, ...);
static __inline__ void wrapper(char *arg, char *s) {
return p(arg, s);
}
int main(int argc, char **argv)
{
wrapper("Hello, I am %s\n", argv[0]);
return 0;
}
/* sourcefile 2 */
extern void printf(char*, ...);
void (*p)(char *, ...) = printf;
优化编译这两个,编译器会为gcc
创建以下代码:
0000000000400500 <main>: 400500: 48 83 ec 08 sub $0x8,%rsp 400504: 48 8b 36 mov (%rsi),%rsi 400507: bf 0c 06 40 00 mov $0x40060c,%edi 40050c: ff 15 d6 03 10 00 callq *1049558(%rip) # 5008e8 <p> 400512: 31 c0 xor %eax,%eax 400514: 48 83 c4 08 add $0x8,%rsp 400518: c3 retq
这几乎是你想要的 - 除了消除 main
,但是通过函数指针直接内联调用。
答案 1 :(得分:0)
我找到了解决问题的方法,而不是使用裸函数或传递函数指针列表。
我可以将指针传递给函数指针的结构,即
struct Functions {
bool (AppAPI *logInfo(std::string,...)),
bool (AppAPI *logWarn(std::string,...)),
bool (AppAPI *logError(std::string,...)),
bool (AppAPI *registerFunction(std::string,void *))
...
} PluginFunctions;
for (int i = 0;i<plugins;i++) {
plugin[i].initialize(&PluginFunctions)
}
PluginFunctions.logInfo = LogInfo;
...
作为插件的init函数,传递了一个指向struct的指针,它可以存储它然后从内存中加载函数指针的当前值,struct只是一个指针在内存中的表,函数指针可以设置在将struct传递给插件之后,它仍然会更新插件。