]> granicus.if.org Git - php/commitdiff
Support calling convention specific function name mangling
authorChristoph M. Becker <cmbecker69@gmx.de>
Fri, 26 Jul 2019 12:37:34 +0000 (14:37 +0200)
committerChristoph M. Becker <cmbecker69@gmx.de>
Sun, 4 Aug 2019 10:14:11 +0000 (12:14 +0200)
On Windows certain calling conventions cause C function names to be
mangled, so to import them we have to use the properly mangled names.

ext/ffi/ffi.c
ext/ffi/tests/callconv.phpt [new file with mode: 0644]
ext/ffi/tests/callconv_x86.dll [new file with mode: 0644]

index c665ad151e68b37a7420d1fca3115e83bd2f9e80..552d168fd69e2e30fe5fe897676521cdc7995510 100644 (file)
@@ -765,6 +765,38 @@ again:
 }
 /* }}} */
 
+#if defined(ZEND_WIN32) && (defined(HAVE_FFI_FASTCALL) || defined(HAVE_FFI_STDCALL))
+static size_t zend_ffi_arg_size(zend_ffi_type *type) /* {{{ */
+{
+       zend_ffi_type *arg_type;
+       size_t arg_size = 0;
+
+       ZEND_HASH_FOREACH_PTR(type->func.args, arg_type) {
+               arg_size += ZEND_FFI_TYPE(arg_type)->size;
+       } ZEND_HASH_FOREACH_END();
+       return arg_size;
+}
+/* }}} */
+#endif
+
+static zend_always_inline zend_string *zend_ffi_mangled_func_name(zend_string *name, zend_ffi_type *type) /* {{{ */
+{
+#ifdef ZEND_WIN32
+       switch (type->func.abi) {
+# ifdef HAVE_FFI_FASTCALL
+               case FFI_FASTCALL:
+                       return strpprintf(0, "@%s@%zu", ZSTR_VAL(name), zend_ffi_arg_size(type));
+# endif
+# ifdef HAVE_FFI_STDCALL
+               case FFI_STDCALL:
+                       return strpprintf(0, "_%s@%zu", ZSTR_VAL(name), zend_ffi_arg_size(type));
+# endif
+       }
+#endif
+       return zend_string_copy(name);
+}
+/* }}} */
+
 #if FFI_CLOSURES
 typedef struct _zend_ffi_callback_data {
        zend_fcall_info_cache  fcc;
@@ -2842,7 +2874,10 @@ ZEND_METHOD(FFI, cdef) /* {{{ */
                                        }
                                        sym->addr = addr;
                                } else if (sym->kind == ZEND_FFI_SYM_FUNC) {
-                                       addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(name));
+                                       zend_string *mangled_name = zend_ffi_mangled_func_name(name, ZEND_FFI_TYPE(sym->type));
+
+                                       addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(mangled_name));
+                                       zend_string_release(mangled_name);
                                        if (!addr) {
                                                zend_throw_error(zend_ffi_exception_ce, "Failed resolving C function '%s'", ZSTR_VAL(name));
                                        }
@@ -3172,7 +3207,10 @@ ZEND_METHOD(FFI, load) /* {{{ */
                                }
                                sym->addr = addr;
                        } else if (sym->kind == ZEND_FFI_SYM_FUNC) {
-                               addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(name));
+                               zend_string *mangled_name = zend_ffi_mangled_func_name(name, ZEND_FFI_TYPE(sym->type));
+
+                               addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(mangled_name));
+                               zend_string_release(mangled_name);
                                if (!addr) {
                                        if (preload) {
                                                zend_error(E_WARNING, "failed pre-loading '%s', cannot resolve C function '%s'", filename, ZSTR_VAL(name));
diff --git a/ext/ffi/tests/callconv.phpt b/ext/ffi/tests/callconv.phpt
new file mode 100644 (file)
index 0000000..aa481de
--- /dev/null
@@ -0,0 +1,41 @@
+--TEST--
+Different calling conventions
+--SKIPIF--
+<?php
+require_once('skipif.inc');
+if (substr(PHP_OS, 0, 3) != 'WIN') die('skip for Windows only');
+if (PHP_INT_SIZE != 4) die("skip this test is for 32bit platforms only");
+?>
+--FILE--
+<?php
+$header = <<<HEADER
+void __cdecl cdecl_func(int arg1, double arg2);
+void __stdcall stdcall_func(int arg1, double arg2);
+void __fastcall fastcall_func(int arg1, double arg2);
+HEADER;
+$headername = __DIR__ . '/callconv.h';
+$dllname = __DIR__ . "/callconv_x86.dll";
+
+$ffi1 = FFI::cdef($header, $dllname);
+$ffi1->cdecl_func(1, 2.3);
+$ffi1->stdcall_func(4, 5.6);
+$ffi1->fastcall_func(7, 8.9);
+
+file_put_contents($headername, "#define FFI_LIB \"$dllname\"\n$header");
+
+$ffi2 = FFI::load($headername);
+$ffi2->cdecl_func(2, 3.4);
+$ffi2->stdcall_func(5, 6.7);
+$ffi2->fastcall_func(8, 9.1);
+?>
+--EXPECT--
+cdecl: 1, 2.300000
+stdcall: 4, 5.600000
+fastcall: 7, 8.900000
+cdecl: 2, 3.400000
+stdcall: 5, 6.700000
+fastcall: 8, 9.100000
+--CLEAN--
+<?php
+unlink(__DIR__ . '/callconv.h');
+?>
diff --git a/ext/ffi/tests/callconv_x86.dll b/ext/ffi/tests/callconv_x86.dll
new file mode 100644 (file)
index 0000000..f4818d1
Binary files /dev/null and b/ext/ffi/tests/callconv_x86.dll differ