Why doesn't the LuaJIT FFI module require declared calling conventions?
This is what I've been curious about for a while: I was wondering how the LuaJIT FFI module can use the correct calling conventions to call external native functions without any need for declarations in custom prototypes.
I tried to read the source code to figure it out on my own, but finding what I was looking for turned out to be too difficult, so any help would be appreciated.
Edit
To make sure that calling conventions are automatically detected when not declared, I wrote the following 32-bit test DLL to compile with the MSVC C compiler:
// 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;
}
Then I wrote a LUA script to call the exported functions with the ffi module:
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')
After compiling the DLL and running the <script, I got the following output:
[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
As you can see, the ffi module exactly resolves calling conventions for calling convention __cdecl
and calling convention __stdcall
. (but apparently called the __fastcall function incorrectly)
Finally, I've included the dumpbin output to show that all functions are exported with unconfirmed names.
> 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
Edit 2
Just to clarify, since convention calls are really only relevant for Windows 32-bit compilers, so this is the main focus of this question. (If I'm wrong, compilers targeting the Win64 platform only use the calling convention FASTCALL
, while GCC uses the calling convention CDECL
for all other platforms supported by LuaJIT).
As far as I know, the only place to find information about functions exported from a PE file is IMAGE_EXPORT_DIRECTORY
, and if function names are exported without decorators, no information is left, which indicates the calling convention of a particular function.
Following this logic, the only remaining method I can think of for defining a function calling convention is to analyze the assembly of the exported function and determine the convention based on the use of the stack. This is a bit like when I look at the differences that occur across compilers and optimization levels.
source to share
The calling convention is platform dependent. There is usually one default platform and you can specify others.
From http://luajit.org/ext_ffi_semantics.html :
The C parser conforms to the C99 language standard plus the following extensions:
...
GCC attribute with the following attributes: aligned, packet, mode, vector_size, cdecl, fastcall, stdcall, thiscall.
...
MSVC __cdecl, __fastcall, __stdcall, __thiscall, __ptr32, __ptr64,
The most interesting thing is Win32. Here the call to the convention can be coded using Win32 convention decorators .
LuaJIT has code to recognize decorators.
Additionally, LuaJIT uses the default __stdcall convention for WinAPI Dlls: kernel32.dll, user32.dll, and gdi32.dll.
source to share