【发布时间】: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