我试着阅读源代码来自己解决这个问题,但找到我想要的东西证明太难了,所以任何帮助都会受到赞赏.
编辑
为了验证未声明时调用约定是自动确定的,我编写了以下32位测试DLL,用MSVC的C编译器编译:
// Use multibyte characters for our default char type
#define _MBCS 1
// Speed up build process with minimal headers.
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
// System includes
#include <windows.h>
#include <stdio.h>
#define CALLCONV_TEST(CCONV) \
int __##CCONV test_##CCONV(int arg1, float arg2, const char* arg3) \
{ \
return CALLCONV_WORK(arg1, arg2, arg3); \
__pragma(comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__ )) \
}
#define CALLCONV_WORK(arg1,arg2,arg3) \
test_calls_work(__FUNCTION__, arg1, arg2, arg3, __COUNTER__);
static int test_calls_work(const char* funcname, int arg1, float arg2, const char* arg3, int retcode)
{
printf("[%s call]\n", funcname);
printf(" arg1 => %d\n", arg1);
printf(" arg2 => %f\n", arg2);
printf(" arg3 => \"%s\"\n", arg3);
printf(" <= return %d\n", retcode);
return retcode;
}
CALLCONV_TEST(cdecl) // => int __cdecl test_cdecl(int arg1, float arg2, const char* arg3);
CALLCONV_TEST(stdcall) // => int __stdcall test_stdcall(int arg1, float arg2, const char* arg3);
CALLCONV_TEST(fastcall) // => int __fastcall test_fastcall(int arg1, float arg2, const char* arg3);
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
if(dwReason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hInstance);
}
return TRUE;
}
然后我编写了一个LUA脚本,用于使用ffi模块调用导出的函数:
local ffi = require('ffi')
local testdll = ffi.load('ljffi-test.dll')
ffi.cdef[[
int test_cdecl(int arg1, float arg2, const char* arg3);
int test_stdcall(int arg1, float arg2, const char* arg3);
int test_fastcall(int arg1, float arg2, const char* arg3);
]]
local function run_tests(arg1, arg2, arg3)
local function cconv_test(name)
local funcname = 'test_' .. name
local handler = testdll[funcname]
local ret = tonumber(handler(arg1, arg2, arg3))
print(string.format(' => got %d\n', ret))
end
cconv_test('cdecl')
cconv_test('stdcall')
cconv_test('fastcall')
end
run_tests(3, 1.33, 'string value')
编译DLL并运行脚本后,我收到以下输出:
[test_cdecl call] arg1 => 3 arg2 => 1.330000 arg3 => "string value" <= return 0 => got 0 [test_stdcall call] arg1 => 3 arg2 => 1.330000 arg3 => "string value" <= return 1 => got 1 [test_fastcall call] arg1 => 0 arg2 => 0.000000 arg3 => "(null)" <= return 2 => got 2
如您所见,ffi模块准确地解析了__cdecl调用约定和__stdcall调用约定的调用约定. (但似乎错误地调用了__fastcall函数)
最后,我已经包含了dumpbin的输出,以显示所有函数都是以未修饰的名称导出的.
> dumpbin.exe /EXPORTS ljffi-test.dll
Microsoft (R) COFF/PE Dumper Version 10.00.40219.01
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file ljffi-test.dll
File Type: DLL
Section contains the following exports for ljffi-test.dll
00000000 characteristics
548838D4 time date stamp Wed Dec 10 04:13:08 2014
0.00 version
1 ordinal base
3 number of functions
3 number of names
ordinal hint RVA name
1 0 00001000 test_cdecl
2 1 000010C0 test_fastcall
3 2 00001060 test_stdcall
Summary
1000 .data
1000 .rdata
1000 .reloc
1000 .text
编辑2
只是为了澄清,因为调用约定只与32位Windows编译器真正相关,所以这是这个问题的主要焦点. (除非我弄错了,针对Win64平台的编译器只使用FASTCALL调用约定,而GCC对LuaJIT支持的所有其他平台使用CDECL调用约定)
据我所知,找到从PE文件导出的函数信息的唯一地方是IMAGE_EXPORT_DIRECTORY,如果导出的函数名没有装饰器,则没有剩余的信息表示特定函数的调用约定.
遵循该逻辑,我可以考虑用于确定函数调用约定的唯一剩余方法是分析导出函数的程序集,并根据堆栈使用情况确定约定.但是,当我考虑不同编译器和优化级别产生的差异时,这似乎有点多了.
调用约定是平台依赖的.通常有一个平台的默认值,您可以指定其他平台.
从http://luajit.org/ext_ffi_semantics.html开始:
The C parser complies to the C99 language standard plus the following extensions:
…
GCC attribute with the following attributes: aligned, packed, mode, vector_size, cdecl, fastcall, stdcall, thiscall.
…
MSVC __cdecl, __fastcall, __stdcall, __thiscall, __ptr32, __ptr64,
最有趣的是Win32.这里调用约定可能用装饰器Win32 calling conventions编码.
LuaJIT具有识别装饰器的代码.
此外,LuaJIT默认使用WinAPI Dll的__stdcall调用约定:kernel32.dll,user32.dll和gdi32.dll.
