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 值。
可提及的读物:
我有一个用 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 在他的
[...] 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 值。
可提及的读物: