我有(在内存中映射)两个目标文件,“Ao”和“Bo”,具有相同的CPU指令集(不一定是英特尔 - 可以是x86,x86_64,MIPS(32/64),ARM(32/64),PowerPC(32/64),......,但在两个目标文件中总是相同的。)
此外,两个目标文件都使用相同的字节序编译(小端,或两个大端)。
然而(你知道有一个,否则就没有任何问题),“Ao”和“Bo”可以有一个不同的函数调用约定,并使事情变得更糟,彼此不知道(“Ao”甚至没有关于“Bo”中函数的调用约定的任何想法,反之亦然。)
“Ao”和“Bo”显然是为了在同一个目标文件中调用函数,但必须有(非常)有限的接口才能在它们之间进行通信(否则,如果执行从“Ao”中的某个功能开始,如果没有这样的界面,则不会执行“Bo”中的任何功能。)
执行开始的文件(让我们假设它是“Ao”)知道来自“Bo”的所有静态符号的地址(所有函数的地址和全局变量)。但事实恰恰相反(好吧,我试图编写的有限界面会克服这个问题,但“Bo”不知道来自“Ao”的任何地址在建立这样的界面之前)。
最后一个问题:如何执行从“Ao”中的函数跳转到“Bo”中的函数,然后返回,还要传达一些数据?
我需要它:
目前,我最好的想法似乎可以满足所有这些要求,除了线程安全。例如,如果我像这样定义struct
:
struct data_interface {
int value_in;
int value_out; };
我可以将“Ao”中的struct
指针写入“Bo”的全局变量(事先知道这样的全局变量) “Bo”中的变量具有足以存储指针的空间。
然后,接口函数将是void interface(void)
(我假设调用void(void)
函数在不同的调用约定中是安全的......如果不是这样,那么我的想法就不会工作)。从“A.o”到“B.o”调用此类函数会将数据传递给“B.o”中的代码。并且,手指交叉,当“Bo”中的被调用函数返回时,它会很好地返回(假设从{返回时)不同的调用约定不会改变行为{1}}功能)。
但是,当然这不是线程安全的。
为了保证线程安全,我想我唯一的选择是访问堆栈。
但是......可以在标准C中以便携方式访问堆栈吗?
答案 0 :(得分:1)
以下是两条建议。
这详细说明了您自己定义的结构。根据我过去看到的情况,编译器通常使用单个寄存器(例如eax
)作为返回值(假设返回类型适合寄存器)。我的猜测是,以下函数原型很可能不受不同调用约定的影响。
struct data_interface *get_empty_data_interface(void);
如果是这样,那么您可以使用与您使用数组的想法类似的方式使用它。在B中定义以下结构和函数:
struct data_interface {
int ready;
int the_real_data;
};
struct data_interface *get_empty_data_interface(void)
{
struct data_interface *ptr = malloc(sizeof(struct data_interface));
add_to_list_of_data_block_pointers(ptr);
ptr->ready = 0;
return ptr;
}
void the_function(void)
{
execute_functionality_for_every_data_block_in_my_list_that_is_flagged_ready_and_remove_from_list();
}
要调用此功能,请在A:
中执行此操作struct data_interface *ptr = get_empty_data_interface();
ptr->the_real_data = 12345;
ptr->ready = 1;
the_function();
为了线程安全,请确保B维护的数据块列表是线程安全的。
get_empty_data_interface
不应该覆盖列表中的对方插槽。the_function
不应该同时接听相同的列表元素。您可以尝试使用众所周知的调用约定(例如cdecl)公开包装函数;如果需要在单独的目标文件中定义 知道它包装的函数的调用约定。
不幸的是,您可能需要非便携式功能属性。
您可以通过声明可变参数包装函数(使用省略号参数,如printf
)来欺骗您。编译器很可能会依赖于cdecl。这消除了非便携式功能属性,但可能不可靠;您必须验证我对您希望支持的每个编译器的假设。在测试时,请记住编译器选项(特别是优化)可能会发挥作用。总而言之,这是一种肮脏的方法。
答案 1 :(得分:1)
这个问题暗示两个目标文件的编译方式不同,除了字节顺序,它们被链接在一起成为一个可执行文件。
它说A.o知道来自B.o的所有静态符号,但事实恰恰相反。
不要对所涉及的电话会议做出任何假设。
所以我们只使用void f(void)
类型的函数。
你在B.o中声明int X, Y;
,在A.o中声明extern int X, Y;
所以在你调用B.中的函数之前,你检查Y标志,如果被提升则等到它下降。当调用B函数时,它会引发Y标志,从X读取输入,进行一些计算,将结果写回X并返回。
然后A.o中的调用函数将X中的值复制到它自己的编译单元中并清除Y标志。
...如果调用void f(void)
函数只是从代码中的一个点跳到另一个点。
另一种方法是在B.o中声明static int Y = 0;
并在A.o中完全省略它。
然后当一个B.o函数被调用时,它会检查Y == 0
是否增加Y,读取X,进行计算,写入X,减少Y并返回。如果不是这样,那么等待成为0并阻止调用函数。
或者甚至可能在每个B.o函数中都有一个静态标志,但由于通信数据在B.o中是全局的,所以我没有看到这种浪费的重点
答案 2 :(得分:1)
请记住,存在调用者保存和被调用者保存约定,以及使用寄存器传递值,使用或不使用帧指针的变体,甚至(在某些体系结构中,在某些优化级别中)使用分支中的延迟槽来保存子程序的第一条指令。如果不了解游戏中的调用约定,你将无法做到这一点,但幸运的是链接器无论如何都需要它。据推测,有一些更高级别的实体负责加载这些DLL并知道它们的调用约定?
如果不是技术上未定义的行为,那么你在这里做的任何事情都将最好深入实现定义的领域,并且你将需要对链接器和加载器进行深入研究(特别是链接器必须知道如何解析动态你的未知调用约定中的链接,或者你将无法以有意义的方式加载该共享对象,因此你可以使用libbfd或其他方式来保留它,但这超出了C)的范围。
这种事情可能会出错的地方是,如果共享资源在A中分配并在B中释放(记住弹簧,因为内存管理通常是基于库的操作系统SBRK或类似的包装器,并且这些内存管理的实现在内存布局中并不具有内在的兼容性,其他地方你可能会被这个包括IO(参见sjannanigans,你有时在c ++中混合printf和cout以获得良好的例子)和锁定。