简体   繁体   English

MSVC和gcc之间的调用约定

[英]Calling convention between MSVC and gcc

I have a program build with MSVC which is loading a dll dynamically. 我有一个使用MSVC构建的程序,该程序可以动态加载dll。 The dll provides a function which is called from the main program. dll提供了从主程序调用的功能。 If both are build with either MSVC or gcc everything is fine, but when I compile eg main with MSVC and the dll with gcc something is wrong. 如果两者都是用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;
}

The problem is, after the call target is zero instead of 0xAFFE . 问题是,在调用目标为零而不是0xAFFE之后 The main program is compiled using __cdecl as calling convention. 主程序使用__cdecl作为调用约定进行编译。 The struct is packed (no alignment), but I also tried to align to different sizes (1, 2, 4, 8, 16). 该结构已打包(不对齐),但我也尝试将其对齐为不同的大小(1、2、4、8、16)。 I also tried to use __declspec/__atribute__(dllexport) and different combinations of both variants. 我还尝试使用__declspec/__atribute__(dllexport)和两个变体的不同组合。

If I take a look into the assembler code, there are two big differences: 如果我看一下汇编代码,有两个很大的不同:

; 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

Why is this even if I use the same calling conventions on both compilers ? 即使在两个编译器上都使用相同的调用约定,这又为什么呢? And how do I do fix it? 我该如何解决呢?

The calling convention is pretty meaningless in this case. 在这种情况下,调用约定是毫无意义的。 The issue is things like vtable layout. 问题是vtable布局之类的东西。 MSVC ABI and Itanium disagree about a lot of things. MSVC ABI和Itanium在很多事情上意见分歧。 You can't compile a C++ interface and mix and match between compilers, unless it is explicitly supported. 除非明确支持,否则您不能编译C ++接口并在编译器之间进行混搭。 Clang and G++ should be interoperable if you set the right settings, and Clang and MSVC may be interoperable depending on which exact features you use. 如果您设置正确的设置,则Clang和G ++应该可以互操作,而Clang和MSVC可能可以互操作,具体取决于所使用的确切功能。

Generally speaking, do not mix and match C++ compilers for C++ interfaces. 一般而言,请勿混用和匹配用于C ++接口的C ++编译器。 It won't work. 它不会工作。

Even if it took me one year to answer my own question (I'm a realy lazy person ;) , I would like to clarify this. 即使我花了一年时间回答自己的问题(我是一个非常懒惰的人;),我也想澄清一下。 As Puppy mentioned in his answer , the ABI between the compilers differs. 正如Puppy在他的回答中提到的那样,编译器之间的ABI是不同的。 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. 自从Microsoft在1992年发明COM以来,他们创建了一个可以映射到c ++ vtable接口的接口。 So other big compiler vendors implemented the mapping between COM and C++ vtables : 因此,其他大型编译器供应商实现了COM和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. [...]由于不能使用COM的Windows编译器非常有限,因此其他编译器供应商强制执行COM vtable和C ++ vtable之间的映射。 [...] [...]

Also as Remy Lebeau mentioned in his comments above: 就像雷米·勒博(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. 无论是在调用堆栈还是在寄存器等上,msvc和gcc都不太可能就如何传递该结构达成共识。从函数返回结构是不可移植的。 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. 可移植的解决方案是要么只返回uint32_t本身,要么将结构作为指针参数传递给函数,然后让函数根据需要填充它。

[...] 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. 用户定义的类型(类/结构)由每个编译器决定,通常是由于它们优化代码的方式不同。

So, as the return value is not covered by the rules of the convention, only trivial types should be used as return values. 因此,由于约定的规则未涵盖返回值,因此仅应将琐碎的类型用作返回值。

Mentionable readings: 值得一提的读数:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM