将Ada闭包转换为C回调(函数+ void *)

时间:2012-08-01 08:15:50

标签: c closures ada

大多数干净的C API将回调声明为回调函数和用户数据的组合。用户数据通常无效*。 WinAPI使用指针大小的整数(lParam)。在进行厚度绑定时,自然的愿望是允许使用Ada 2005闭包来代替C回调。

我有一个代码。它就像GNAT上的魅力(GPL 2012,x86-windows至少经过测试),但通常无法保证Run_Closure_Adapter.X变量和Run_Closure.X参数具有相同的内部结构。

问题是:是否有适当的(符合标准的)方法?也许是涉及标记类型,接口或泛型的技巧。至少有一种方法可以做到这一点:在不同的任务中运行闭包执行器和闭包,并使用集合点。但那太慢了。

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;

closure_executor.c

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

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

2 个答案:

答案 0 :(得分:4)

我认为您正在寻找的东西可以通过使用泛型来实现(顺便说一下,我没有看到使用任务如何确保数据类型匹配?)

也许像

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;确保用户只能使用提供的Closure创建Create对象。)

在发生事件时传递给C库以回调的场景中,主要的问题是Closure对象实际上是由C库维护的。

除了你真的不需要这个Ada Closure之外,匿名访问子程序值会导致潜在的问题,即子程序可以在本地声明并且已经离开C库到达时调用它的范围。这将是坏消息。

在Ada世界中,编译器以两种方式处理这个问题。首先,您不能存储匿名访问子程序值(因此上面的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

答案 1 :(得分:2)

作为替代方案,您可以查看GtkAda如何处理来自GTK+的回调。如GtkAda tutorial

所示
  

Gtk.Handlers中的每个通用包中都有一组To_Marshaller个函数。它们只接受一个参数,即要调用的函数的名称,并返回一个可以直接在Connect中使用的处理程序。

Interaction是一个示例,它实例化了几个这样的处理程序,并使用 access-to-subprogram 参数连接相应的回调。