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.
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.