MSVC 和 gcc 之间的调用约定

Calling convention between MSVC and gcc

我有一个用 MSVC 构建的程序,它正在动态加载一个 dll。 dll 提供了一个从主程序调用的函数。如果两者都是使用 MSVC 或 gcc 构建的,一切都很好,但是当我编译时,例如MSVC 的 main 和 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;
}

问题是,在调用目标之后是zero而不是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

即使我在两个编译器上使用相同的调用约定,为什么会这样?我该如何解决?

在这种情况下,调用约定毫无意义。问题是 vtable 布局之类的东西。 MSVC ABI 和 Itanium 在很多事情上存在分歧。除非明确支持,否则您不能编译 C++ 接口并在编译器之间混合和匹配。如果设置正确,Clang 和 G++ 应该可以互操作,并且 Clang 和 MSVC 可以互操作,具体取决于您使用的具体功能。

一般来说,不要为 C++ 接口混合搭配 C++ 编译器。不行。

即使我花了一年时间来回答我自己的问题(我真的是一个懒惰的人;),我还是想澄清一下。正如 Puppy 在他的 , the ABI between the compilers differs. But this is just half true. Since Microsoft invented COM back in 1992 they created an interface which can be mapped into c++ vtable interfaces. So other big compiler vendors implemented the mapping between COM and C++ vtables 中提到的那样:

[...] Since a Windows compiler that can't use COM is pretty limited, other compiler vendors enforced the mapping between COM vtables and C++ vtables. [...]

此外,正如 Remy Lebeau 在他上面的评论中提到的那样:

The function is returning a structure. More likely, msvc and gcc do not agree on how to pass that structure around, whether on the call stack or in registers , etc. Returning a struct from a function is not portable. The calling convention dictates how parameters are passed, but does not dictate how non-trivial return values are passed. The portable solution is to either return just the uint32_t by itself, or else pass the struct into the function as a pointer parameter and let the function fill it as needed.

[...] returning just a pointer by itself would be covered by the rules of the calling convention. Trivial built-in types (integers, floats/doubles, pointers, etc) are covered. User-defined types (classes/structs) are at the discretion of each compiler, typically due to differences in how they optimize their code.

因此,由于 return 值未包含在约定规则中,因此仅应将普通类型用作 return 值。

可提及的读物: