4

ほとんどのクリーンな C API は、コールバック関数とユーザー データの組み合わせとしてコールバックを宣言します。ユーザーデータは通常 void* です。WinAPI は、ポインター サイズの整数 (lParam) を使用します。シック バインディングを作成する際、C コールバックの代わりに Ada 2005 クロージャを使用できるようにしたいというのが自然な望みです。

私はコードをもっている。GNAT (GPL 2012、x86-windows は少なくともテスト済み) では魅力的に動作しますが、通常、Run_Closure_Adapter.X 変数と Run_Closure.X 引数が同じ内部構造を持つという保証はありません。

問題は、これを行うための適切な (標準に準拠した) 方法があるかどうかです。タグ付けされた型、インターフェース、またはジェネリックを含むトリックかもしれません。これを行うには、少なくとも 1 つの方法があります。クロージャ エグゼキュータとクロージャを異なるタスクで実行し、ランデブーを使用することです。しかし、それは遅すぎます。

Closure_Test.adb :

with Closure_Lib; use Closure_Lib;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;

procedure Closure_Test is

   procedure Closure_Tester is

      Local_String : String := "Hello, world!";

      procedure Closure is
      begin
         Put_Line (Local_String);
      end Closure;

   begin
      Run_Closure (Closure'Access);
   end Closure_Tester;

   procedure Ada_Run_Closure (X : access procedure) is
   begin
      X.all;
   end Ada_Run_Closure;

   -- Nested_Closure fills the execution stack with
   -- several activation records of Nested_Closure_Tester
   -- Having done so (local I = 0) we start a Fibonacci
   -- algorithm using Print_Closure access values of
   -- different dynamic nesting levels

   procedure Nested_Closure_Tester
     (I : Integer;
      Closure_Runner: access procedure (X : access procedure);
      Prev_Closure, Prev_Closure2: access procedure)
   is

      procedure Print_Closure is
      begin
         if Prev_Closure /= null and Prev_Closure2 /= null then
            Closure_Runner (Prev_Closure);
            Closure_Runner (Prev_Closure2);
         else
            Put (".");
         end if;
      end Print_Closure;

      procedure Nested_Closure is
      begin
         if I > 0 then
            Nested_Closure_Tester (I - 1, Closure_Runner,
                                   Print_Closure'Access, Prev_Closure);
         else
            Print_Closure;
         end if;
      end Nested_Closure;
   begin
      Closure_Runner (Nested_Closure'Access);
   end Nested_Closure_Tester;

begin
   -- Closure_Tester;
   -- I = 6 gives 13 dots
   Nested_Closure_Tester(6, Ada_Run_Closure'Access, null, null);
   New_Line;
   Nested_Closure_Tester(6, Run_Closure'Access, null, null);
end Closure_Test;

Closure_Lib.ads :

with Interfaces.C;
with System;

package Closure_Lib is

   procedure Run_Closure (X : access procedure);

private

   type Simple_Callback is access procedure(Data : in System.Address);
   pragma Convention (C, Simple_Callback);

   procedure Run_Callback (X : in Simple_Callback; Data : in System.Address);

   pragma Import (C, Run_Callback, "Run_Callback");

   procedure Sample_Callback (Data : in System.Address);
   pragma Convention (C, Sample_Callback);

end Closure_Lib;

Closure_Lib.adb :

with Interfaces.C;
with System;
with System.Storage_Elements; use System.Storage_Elements;
with Ada.Text_IO; use Ada.Text_IO;

package body Closure_Lib is

   procedure Sample_Callback (Data : in System.Address) is
   begin
      Ada.Text_IO.Put_Line ("Simple_Callback");
   end Sample_Callback;

   procedure Run_Closure_Adapter (Data : in System.Address);
   pragma Convention (C, Run_Closure_Adapter);

   procedure Run_Closure_Adapter (Data : in System.Address) is
      X : access procedure;
      for X'Address use Data;
      pragma Import (Ada, X);
      X_Size : constant Storage_Count := X'Size / System.Storage_Unit;
   begin
      -- Put_Line ("Variable access procedure size:" & Storage_Count'Image (X_Size));
      X.all;
   end Run_Closure_Adapter;

   procedure Run_Closure (X : access procedure) is
      X_Size : constant Storage_Count := X'Size / System.Storage_Unit;
      X_Address : constant System.Address := X'Address;
   begin
      -- Put_Line ("Anonymous access procedure size:" & Storage_Count'Image (X_Size));
      Run_Callback (Run_Closure_Adapter'Access, X_Address);
   end Run_Closure;

end Closure_Lib;

クロージャ_executor.c :

typedef void (*Simple_Callback)(void* Data);

void Run_Callback (Simple_Callback X, void* Data) {
    (*X)(Data);
}
4

2 に答える 2

6

あなたが探しているものは、ジェネリックを使用することで満たされる可能性があると思います(ちなみに、タスクを使用してデータ型が一致することを確認する方法がわかりませんか?)

多分何かのような

generic
   type Client_Data is private;
package Closure_G is
   type Closure (<>) is private;
   function Create (Proc : access procedure (Parameter : Client_Data);
                    And_Parameter : Client_Data) return Closure;
   procedure Execute (The_Closure : Closure);
private
   type Procedure_P is access procedure (Parameter : Client_Data);
   type Closure is record
      The_Procedure : Procedure_P;
      And_Parameter : Client_Data;
   end record;
end Closure_G;

ユーザーが を呼び出すとExecute (A_Closure)、 にProc提供されたがその時点で提供されたCreateで呼び出されAnd_Parameterます。

( は、ユーザーが提供された を使用してのみオブジェクトをtype Closure (<>) is private;作成できるようにします。)ClosureCreate

これに関する主な問題は、イベントが発生したときに C ライブラリに渡してコールバックするというシナリオでは、Closureオブジェクトが実際には C ライブラリによって維持されることです。

この Ada は実際には必要ないという事実は別として、Closure匿名のサブプログラムへのアクセス値によって引き起こされる潜在的な問題があります。これは、サブプログラムがローカルで宣言され、C ライブラリが取得されるまでにスコープ外になる可能性があることです。それを呼び出すことに丸めます。これは悪いニュースです。

Ada の世界では、コンパイラはこの問題に 2 つの方法で対処します。まず、サブプログラムへの匿名のアクセス値を保存することは許可されていません (したがって、type Procedure_P上記の理由)。第二に、これを次のように回避したとしても

function Create (Proc : access procedure (Parameter : Client_Data);
                 And_Parameter : Client_Data) return Closure is
begin
   return (The_Procedure => Procedure_P'(Proc),
           And_Parameter => And_Parameter);
end Create;

実際の「アクセシビリティ レベル」は実行時にチェックされます。間違えるとProgram_Error.

于 2012-08-01T19:41:26.070 に答える
3

GtkAda別の方法として、からのコールバックを処理する方法を確認することもできますGTK+GtkAda User's Guideに示され、 §4.2.2で説明されています。Gtk.Handlersパッケージ経由で接続し

このGtk.Marshallersパッケージは、コールバックとして直接使用できる一連の関数を提供しますGtkAda。一連のTo_Marshaller関数は、Gtk.Handlers. これらは、呼び出したい関数の名前である 1 つの引数を取り、 で直接使用できるハンドラを返しますConnect

Interactionは、そのようなハンドラをいくつかインスタンス化し、サブプログラムへのアクセスパラメータを使用して対応するコールバックを接続する例です。

于 2012-08-02T00:48:13.663 に答える