我有一个使用MSVC构建的程序,它正在动态加载一个DLL。 dll提供从主程序调用的函数。如果两者都使用MSVC或gcc构建,一切都很好,但是当我编译时,例如主要用MSVC和带有gcc的dll是错误的。
# ifdef __GNUC__
# define CDECL __attribute__ ((__cdecl__))
# else
# define CDECL __cdecl
# endif
struct EXP result {
uint32_t code;
};
#define SUCCESS result{0};
virtual result CDECL foo(char const* const*& target) const {
target = (char const* const*)0xAFFE;
return SUCCESS;
}
问题是,在呼叫目标零而不是 0xAFFE 之后。主程序使用__cdecl
作为调用约定进行编译。结构被打包(没有对齐),但我也尝试对齐不同的大小(1,2,4,8,16)。我还尝试使用__declspec/__atribute__(dllexport)
和两种变体的不同组合。
如果我查看汇编代码,有两个很大的区别:
; MSVC | gcc
;===============================|================================
; before calling |
;-------------------------------|--------------------------------
| sub dword ptr [esp+4],8
|
; foo(); |
;-------------------------------|--------------------------------
push ebp | push ebp
mov ebp,esp | mov ebp,esp
mov eax,dword ptr [target] |
mov dword ptr [eax],0AFFEh |
mov eax,dword ptr [ebp+0Ch] | mov eax,dword ptr [ebp+0Ch]
mov dword ptr [eax],0 | mov dword ptr [eax],0AFFEh
| mov eax,0
pop ebp | pop ebp
ret | ret
为什么即使我在两个编译器上使用相同的调用约定呢?我该如何解决?
答案 0 :(得分:6)
在这种情况下,调用约定毫无意义。问题是像vtable布局这样的东西。 MSVC ABI和Itanium对很多事情都持不同意见。除非明确支持,否则您无法编译C ++接口并在编译器之间进行混合和匹配。如果设置正确的设置,Clang和G ++应该是可互操作的,并且Clang和MSVC可以互操作,具体取决于您使用的确切功能。
一般来说,不要混用和匹配C ++接口的C ++编译器。它不会工作。
答案 1 :(得分:1)
即使我花了一年的时间来回答我自己的问题(我是一个真正懒惰的人;),我想澄清一下。正如Puppy在answer中提到的,编译器之间的ABI不同。但这只是一半。自从微软于1992年发明COM以来,他们创建了一个可以映射到c ++ vtable接口的接口。因此,其他大型编译器供应商实现了COM and C++ vtables:
之间的映射[...]由于不能使用COM的Windows编译器非常有限,因此其他编译器供应商强制实施COM vtable和C ++ vtable之间的映射。 [...]
正如Remy Lebeau在上述评论中提到的那样:
该函数返回一个结构。更有可能的是,msvc和gcc不同意如何在调用堆栈或寄存器等中传递该结构。从函数返回结构是不可移植的。调用约定规定了参数的传递方式,但没有规定如何传递非平凡的返回值。可移植的解决方案是单独返回uint32_t,或者将结构作为指针参数传递给函数,并让函数根据需要填充它。
[...]单独返回一个指针将被调用约定的规则所覆盖。涵盖了普通的内置类型(整数,浮点数/双精度数,指针等)。用户定义的类型(类/结构)由每个编译器决定,通常是由于他们优化代码的方式不同。
因此,由于约定规则未涵盖返回值,因此只应使用普通类型作为返回值。
可提及的读物: