【问题标题】:Calling convention between MSVC and gccMSVC 和 gcc 之间的调用约定
【发布时间】:2015-08-11 19:52:39
【问题描述】:

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

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

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

【问题讨论】:

  • DLL 调用不应该是标准调用吗?
  • @CemKalyoncu:这不是要求。有很多 DLL 使用 __cdecl 代替。只要应用程序和 DLL 都同意使用__cdecl,它是绝对安全的。这不是这里的问题。
  • 函数正在返回一个结构。更有可能的是,msvc 和 gcc 不同意如何传递该结构,无论是在调用堆栈还是在寄存器等。从函数返回结构是不可移植的。调用约定规定了如何传递参数,但没有规定如何传递重要的返回值。可移植的解决方案是要么自己返回uint32_t,要么将结构作为指针参数传递给函数,让函数根据需要填充它。
  • @RemyLebeau 解释了为什么如果我返回一个整数它会起作用。返回一个指向实例的指针怎么样?我知道返回指向某物的指针不是一个好主意,但这会被公约涵盖吗?顺便说一句,什么是反对票?
  • @user1810087:是的,仅返回一个指针本身将包含在调用约定的规则中。涵盖了简单的内置类型(整数、浮点数/双精度数、指针等)。用户定义的类型(类/结构)由每个编译器自行决定,通常是由于它们优化代码的方式不同。

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


【解决方案1】:

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

一般来说,不要为 C++ 接口混合和匹配 C++ 编译器。它不会工作。

【讨论】:

    【解决方案2】:

    即使我花了一年的时间来回答我自己的问题(我真的很懒惰;),我还是想澄清一下。正如 Puppy 在他的 answer 中提到的,编译器之间的 ABI 不同。但这只是对了一半。自从微软在 1992 年发明了COM 以来,他们创建了一个可以映射到 c++ vtable 接口的接口。于是其他大编译器厂商实现了COM and C++ vtables之间的映射:

    [...] 由于不能使用 COM 的 Windows 编译器非常有限,因此其他编译器供应商强制执行 COM vtables 和 C++ vtables 之间的映射。 [...]

    正如 Remy Lebeau 在上面的 cmets 中提到的那样:

    函数正在返回一个结构。更有可能的是,msvc 和 gcc 不同意如何传递该结构,无论是在调用堆栈还是在寄存器等。从函数返回结构是不可移植的。调用约定规定了如何传递参数,但没有规定如何传递重要的返回值。可移植的解决方案是要么自己返回 uint32_t,要么将结构体作为指针参数传递给函数,让函数根据需要填充它。

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

    因此,由于返回值不受约定规则的约束,因此只能使用普通类型作为返回值。

    可提及的读数:

    【讨论】:

      猜你喜欢
      • 2012-12-13
      • 2016-08-18
      • 1970-01-01
      • 1970-01-01
      • 2023-03-28
      • 1970-01-01
      • 2022-09-24
      • 1970-01-01
      • 2014-04-15
      相关资源
      最近更新 更多