调用MSVC和gcc之间的约定

时间:2015-08-11 19:52:39

标签: c++ visual-c++ gcc calling-convention

我有一个使用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

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

2 个答案:

答案 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,或者将结构作为指针参数传递给函数,并让函数根据需要填充它。

     

[...]单独返回一个指针将被调用约定的规则所覆盖。涵盖了普通的内置类型(整数,浮点数/双精度数,指针等)。用户定义的类型(类/结构)由每个编译器决定,通常是由于他们优化代码的方式不同。

因此,由于约定规则未涵盖返回值,因此只应使用普通类型作为返回值。

可提及的读物: