为什么 Ada DLL 在通过 FFI 接口从 Rust 调用时会卡在 adainit?
Why does Ada DLL get stuck at adainit when called from Rust over FFI interface?
快乐案例
我使用 Mingw 在 Ada 中成功编译了一个最小的 hello world windows DLL 并通过 FFI 接口使用它:
package MY_FFI is
procedure Hello_World
with
Export => True,
Convention => C,
External_Name => "hello_world";
end MY_FFI;
package body MY_FFI is
procedure Hello_World is
begin
Ada.Text_IO.Put_Line("Hello world!");
end Send_Request;
end MY_FFI;
#[link(name = "my_ffi")]
extern "C" {
#[link_name = "hello_world"]
fn ada_hello_world();
fn my_ffiinit(); // same as adainit just renamed by gprbuild
fn my_ffifinal(); // same as adafinal just renamed by gprbuild
}
pub fn initialize_my_ffi() {
unsafe {
println!("step 1");
my_ffiinit();
println!("step 2");
ada_hello_world();
println!("step 3");
}
}
这导致:
step 1
step 2
Hello world!
step 3
真正的问题
当我的 Ada 库变得更加复杂并且需要以下额外的系统 DLL 时:
libgnarl-7.dll
libgnat-7.dll
libgcc_s_seh-1.dll
(mingw posix)
libwinpthreadthread-1.dll
(mingw posix)
我无法准确地挑出需要那些额外 DLL 的代码,但是一旦代码变得复杂到需要这些 DLL,它就会在 my_ffiinit
函数处停滞不前,输出仅:
step 1
x64 Linux 上的相同代码可以正常工作。其他 Linux 平台(powerpc、arm64)的交叉编译也有效。
当我使用 Library_Auto_Init="true"
时,wine64 输出:
0009:err:module:LdrInitializeThunk "libmy_ffi.dll" failed to initialize, aborting
0009:err:module:LdrInitializeThunk Initializing dlls for L"Z:\test.exe" failed, status 20474343
主项目gpr:
with "/opt/libA/a_lib.gpr";
with "/opt/libB/b_lib.gpr";
library project MY_FFI is
for Languages use ("Ada");
for Library_Name use "my_ffi";
for Library_Kind use "dynamic";
for Library_Standalone use "encapsulated";
for Source_Dirs use ("src/**");
for Library_Interface use (
"my_ffi",
);
type Target_Type is ("windows64", "windows32", "linux64", "armhf", "powerpc", "arm64");
Target : Target_Type := external ("target", "linux64");
for Library_Dir use "lib/" & external ("target", "linux64");
for Object_Dir use "obj/" & external ("target", "linux64");
end MY_FFI;
预编译共享库 A 的 gpr:
library project a_lib is
for Languages use ("Ada");
for Library_Name use "a";
for Library_Kind use "dynamic";
for Source_Dirs use ("src/**");
type Target_Type is ("windows64", "windows32", "linux64", "armhf", "powerpc", "arm64");
Target : Target_Type := external ("target", "linux64");
for Library_Dir use "lib/" & external ("target", "linux64");
for Object_Dir use "obj/" & external ("target", "linux64");
package Naming is
for Spec_Suffix ("ada") use ".ads";
for Body_Suffix ("ada") use ".adb";
for Separate_Suffix use ".adb";
for Casing use "MixedCase";
for Dot_Replacement use "-";
end Naming;
package Compiler is
for Default_Switches ("ada") use ("-fPIC");
end Compiler;
for Externally_Built use "true";
end a_lib;
从源代码编译的库 B 的 gpr:
library project b_lib is
for Source_Dirs use ("src");
for Library_Name use "b";
for Library_Dir use "lib";
for Object_Dir use "obj";
for Languages use ("Ada");
package Compiler is
for Switches ("ada") use ("-O2", "-ffunction-sections", "-gnatQ", "-fdata-sections", "-fPIC", "-gnatf", "-gnatwa");
end Compiler;
package Builder is
for Switches ("ada") use ("-j0", "-k", "-s");
end Builder;
type Target_Type is ("windows64", "windows32", "linux64", "armhf", "powerpc", "arm64");
Target : Target_Type := external ("target", "linux64");
for Library_Dir use "lib/" & external ("target", "linux64");
for Object_Dir use "obj/" & external ("target", "linux64");
end b_lib;
对Ada不是很了解,但是它没有自己的ABI吗?这个 document 建议你需要用 Convention => C
标记一个函数以便从 C 调用它,类似于你在 Rust 中使用 extern "C"
的方式。
问题是在主项目配置文件中使用 for Library_Standalone use "encapsulated";
,当它被替换为默认值 standard
项目编译失败并出现以下错误:
shared library project "my_ffi" cannot import static library project "b"
将行 for Library_Kind use "relocatable";
添加到库 B 项目配置后,编译错误消失并且 libmy_ffi.dll
编译成功。更重要的是,生成的 DLL 在从 Rust 调用时按预期工作。
尚未解决的怪癖:
- 为什么这对 Linux 平台来说不是问题
- 为什么
gprbuild
在编译项目时不强制执行或警告唯一的静态策略
您是否正在 DLL:s 中创建图书馆级别的任务?这行不通,因为动态库中的初始化代码在 Windows.
上严格是单线程的
快乐案例
我使用 Mingw 在 Ada 中成功编译了一个最小的 hello world windows DLL 并通过 FFI 接口使用它:
package MY_FFI is
procedure Hello_World
with
Export => True,
Convention => C,
External_Name => "hello_world";
end MY_FFI;
package body MY_FFI is
procedure Hello_World is
begin
Ada.Text_IO.Put_Line("Hello world!");
end Send_Request;
end MY_FFI;
#[link(name = "my_ffi")]
extern "C" {
#[link_name = "hello_world"]
fn ada_hello_world();
fn my_ffiinit(); // same as adainit just renamed by gprbuild
fn my_ffifinal(); // same as adafinal just renamed by gprbuild
}
pub fn initialize_my_ffi() {
unsafe {
println!("step 1");
my_ffiinit();
println!("step 2");
ada_hello_world();
println!("step 3");
}
}
这导致:
step 1
step 2
Hello world!
step 3
真正的问题
当我的 Ada 库变得更加复杂并且需要以下额外的系统 DLL 时:
libgnarl-7.dll
libgnat-7.dll
libgcc_s_seh-1.dll
(mingw posix)libwinpthreadthread-1.dll
(mingw posix)
我无法准确地挑出需要那些额外 DLL 的代码,但是一旦代码变得复杂到需要这些 DLL,它就会在 my_ffiinit
函数处停滞不前,输出仅:
step 1
x64 Linux 上的相同代码可以正常工作。其他 Linux 平台(powerpc、arm64)的交叉编译也有效。
当我使用 Library_Auto_Init="true"
时,wine64 输出:
0009:err:module:LdrInitializeThunk "libmy_ffi.dll" failed to initialize, aborting
0009:err:module:LdrInitializeThunk Initializing dlls for L"Z:\test.exe" failed, status 20474343
主项目gpr:
with "/opt/libA/a_lib.gpr";
with "/opt/libB/b_lib.gpr";
library project MY_FFI is
for Languages use ("Ada");
for Library_Name use "my_ffi";
for Library_Kind use "dynamic";
for Library_Standalone use "encapsulated";
for Source_Dirs use ("src/**");
for Library_Interface use (
"my_ffi",
);
type Target_Type is ("windows64", "windows32", "linux64", "armhf", "powerpc", "arm64");
Target : Target_Type := external ("target", "linux64");
for Library_Dir use "lib/" & external ("target", "linux64");
for Object_Dir use "obj/" & external ("target", "linux64");
end MY_FFI;
预编译共享库 A 的 gpr:
library project a_lib is
for Languages use ("Ada");
for Library_Name use "a";
for Library_Kind use "dynamic";
for Source_Dirs use ("src/**");
type Target_Type is ("windows64", "windows32", "linux64", "armhf", "powerpc", "arm64");
Target : Target_Type := external ("target", "linux64");
for Library_Dir use "lib/" & external ("target", "linux64");
for Object_Dir use "obj/" & external ("target", "linux64");
package Naming is
for Spec_Suffix ("ada") use ".ads";
for Body_Suffix ("ada") use ".adb";
for Separate_Suffix use ".adb";
for Casing use "MixedCase";
for Dot_Replacement use "-";
end Naming;
package Compiler is
for Default_Switches ("ada") use ("-fPIC");
end Compiler;
for Externally_Built use "true";
end a_lib;
从源代码编译的库 B 的 gpr:
library project b_lib is
for Source_Dirs use ("src");
for Library_Name use "b";
for Library_Dir use "lib";
for Object_Dir use "obj";
for Languages use ("Ada");
package Compiler is
for Switches ("ada") use ("-O2", "-ffunction-sections", "-gnatQ", "-fdata-sections", "-fPIC", "-gnatf", "-gnatwa");
end Compiler;
package Builder is
for Switches ("ada") use ("-j0", "-k", "-s");
end Builder;
type Target_Type is ("windows64", "windows32", "linux64", "armhf", "powerpc", "arm64");
Target : Target_Type := external ("target", "linux64");
for Library_Dir use "lib/" & external ("target", "linux64");
for Object_Dir use "obj/" & external ("target", "linux64");
end b_lib;
对Ada不是很了解,但是它没有自己的ABI吗?这个 document 建议你需要用 Convention => C
标记一个函数以便从 C 调用它,类似于你在 Rust 中使用 extern "C"
的方式。
问题是在主项目配置文件中使用 for Library_Standalone use "encapsulated";
,当它被替换为默认值 standard
项目编译失败并出现以下错误:
shared library project "my_ffi" cannot import static library project "b"
将行 for Library_Kind use "relocatable";
添加到库 B 项目配置后,编译错误消失并且 libmy_ffi.dll
编译成功。更重要的是,生成的 DLL 在从 Rust 调用时按预期工作。
尚未解决的怪癖:
- 为什么这对 Linux 平台来说不是问题
- 为什么
gprbuild
在编译项目时不强制执行或警告唯一的静态策略
您是否正在 DLL:s 中创建图书馆级别的任务?这行不通,因为动态库中的初始化代码在 Windows.
上严格是单线程的