Почему Ada DLL застревает в adainit при вызове из Rust через интерфейс FFI?

#windows #dll #rust #ada #ffi

#Windows #dll #Ржавчина #ada #ффи

Вопрос:

Счастливый случай

Используя Mingw, я успешно скомпилировал минимальную библиотеку DLL Windows hello world в Ada и использовал ее через интерфейс 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, он просто зависает в rust в 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
 

Георадар основного проекта:

 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/" amp; external ("target", "linux64");
  for Object_Dir   use  "obj/" amp; external ("target", "linux64");
end MY_FFI;
 

gpr для предварительно скомпилированной разделяемой библиотеки A:

 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/" amp; external ("target", "linux64");
  for Object_Dir   use  "obj/" amp; 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, скомпилированный из исходного кода:

 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/" amp; external ("target", "linux64");
   for Object_Dir   use  "obj/" amp; external ("target", "linux64");
end b_lib;
 

Комментарии:

1. Немного удивлен, потому Ada.Text_IO что реализован в libgnat . Вы пробовали сделать свою библиотеку автономной ? (требуется заклинание gprbuild)

2. Я использую for Library_Standalone use "encapsulated"; в своем файле проекта gprbuild. Однако моя более сложная версия библиотеки использует другие разделяемые библиотеки Ada в качестве зависимостей, которые не используют for Library_Standalone use "encapsulated"; flag.

3. Я никогда не использовал encapsulated , поэтому не уверен, но в руководстве говорится: «при использовании инкапсулированной библиотеки библиотека может зависеть только от статических библиотек»

4. Обсуждение здесь, на comp.lang.ada, похоже, связано: groups.google.com/g/comp.lang.ada/c/Pz5bQBnO180/m/sgk49s-qAgAJ Лично я избегаю динамических библиотек и не могу комментировать потенциальные проблемы, с которыми можно столкнуться при их использовании.

5. Добавлен заголовок пакета и конфигурации для основного проекта и библиотек зависимостей. Я также успешно решил проблему благодаря комментарию @SimonWright об использовании encapsulated . Я отформатирую решение в форме ответа, как только смогу.

Ответ №1:

Проблема заключается в использовании for Library_Standalone use "encapsulated"; в главном файле конфигурации проекта, когда это значение заменяется значением по умолчанию standard , компиляция проекта завершается ошибкой со следующей ошибкой:

 shared library project "my_ffi" cannot import static library project "b"
 

Как только строка for Library_Kind use "relocatable"; добавляется в конфигурацию проекта library B, ошибка компиляции исчезает и libmy_ffi.dll компилируется успешно. Более того, результирующая DLL работает должным образом при вызове из Rust.

Причуды, которые остаются без ответа:

  • Почему это не проблема для платформ Linux
  • Почему gprbuild не применяется или не предупреждает о единственной статической политике при компиляции проекта

Комментарии:

1. Я вспоминаю этот материал adainit (почему вы называете это FFI? Rust не является скриптом) был обходным путем против тупиковой ситуации Windows из-за запуска задач из DllInit. Это также может быть связано с вашей ситуацией

2. Вызываю его my_fiiinit , потому что именно так gprbuild переименовывает его. Когда имя библиотеки равно XXX, gprbuild переименовывается adainit в XXXinit .

Ответ №2:

Мало что знаю об Ada, но разве у нее нет собственного ABI? В этом документе предполагается, что вам нужно пометить функцию с Convention => C помощью, чтобы вызвать ее из C, аналогично тому, как вы бы использовали extern "C" в Rust.

Ответ №3:

Может быть, вы создаете задачи на уровне библиотеки в DLL: s? Это не сработает, поскольку init-код в динамических библиотеках строго однопоточный в Windows.