为什么 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 时:

我无法准确地挑出需要那些额外 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.

上严格是单线程的