将从C例程分配的数组传递给ADA

时间:2014-04-29 12:15:10

标签: c arrays pointers ada

将结构/记录数组从ADA传递到C例程是一回事。在这种情况下,内存管理在ADA中完成。但是当与第三方库连接时,通常存在内存管理在C部分中完成的问题。

例如:对于C结构:

typedef struct _MYREC
{
      int n;
      char *str;
 } MYREC;

以下C-routine分配Memory并返回一个指向带有n个元素的MYREC数组的指针:

MYREC * allocMyrec(int n);

问题是返回的指针不包含ADA中内存安全计算所需的大小信息。

在ADA中

我想对(MYREC *)指针使用相应的Array-Definition:

type MYREC_Array is array (Int range <>) of aliased MYREC;
pragma Convention (C, MYREC_Array);

相应的(尺寸大小的)ADA功能allocMyrec如何看起来或者什么是正确的策略?

B.t.w。对于一个元素,可以将C指针映射到access MYREC。这就是Gnat-Binding生成器的功能。但这没有用。

高度赞赏。

4 个答案:

答案 0 :(得分:3)

我终于使用包Interface.C.Pointers了解它,这很简单,这里是代码:

with Interfaces.C, Interfaces.C.Pointers ;
use Interfaces.C ;
with Ada.Text_IO ; -- To Check 

procedure MYRECTest is 

    package IIO is new Ada.Text_IO.Integer_IO (Int) ;

    type PChar is access all Char ;

    type MYREC is record
        N : Int ;
        Str : PChar ;
    end record ;
    pragma Convention(C, MYREC); 

    DefaultMyrec : MYREC := (0, null) ; 

    type MYREC_Array is array (Int range <>) of aliased MYREC ;
    pragma Convention(C, MYREC_Array); -- Not sure if useful...

    -- Here is the trick
    package PMYREC is new Interfaces.C.Pointers (Int, MYREC, MYREC_Array, DefaultMyrec) ;

    function AllocMyrec (N : in Int) return PMYREC.Pointer ;
    pragma Import (C, AllocMyrec, "allocMyrec");

    P : PMYREC.Pointer := AllocMyrec(5) ;
    StartP : PMYREC.Pointer := P ; -- Initial pointer
    A : MYREC_Array(0..4) := PMYREC.Value(P, 5) ; -- Here is a copy

begin

    for I in A'Range loop
        -- Real access:
        IIO.Put(P.all.N) ;
        P.all.N := P.all.N + 3 ; -- Here you're really accessing the allocated memory, not just a copy
        PMYREC.Increment(P) ; 
        -- 'Fake' access:
        IIO.Put(A(I).N) ;
        A(I).N := A(I).N + 3 ; -- Here you're accessing a copy, so the modification is not made on the allocated memory
    end loop ;
    Ada.Text_IO.New_Line ;

end MYRECTest ;

我测试了上面的代码,将C函数中所有n元素的_MYREC属性设置为42 + i并将其打印在Ada正文中,它有效(我得到42,43 ,44,45,46)。 C函数看起来像(测试):

typedef struct _MYREC { 
    int n ;
    char *str ;
} MYREC ;

MYREC * allocMyrec (int n) {
    MYREC *res = malloc(n * sizeof(MYREC)) ;
    int i ;
    for (i = 0 ; i < n ; i++) { 
        res[i].n = 42 + i;
    }
    return res ;
}

DefaultMyrec变量没用,但是创建包是必需的。我假设您总是使用带有Value参数的length函数来检索值。

有关该套餐的更多信息:http://www.adaic.org/resources/add_content/standards/05rm/html/RM-B-3-2.html

编辑:原始代码正在制作P指向的内存的副本,因此如果更新数组A中的任何内容,则不会更改分配的内存。实际上,您应该直接使用指针P,如编辑后的代码所示。

编辑:我对access all Char的{​​{1}}属性使用'愚蠢'str,但您可以(并且应该)使用来自必要时MYREC。我测试了它,但不想把它放在答案中,因为它没有添加任何内容。

答案 1 :(得分:0)

抱歉,我认为这不是一般解决方案。如果您声明的类型可以访问MYREC_Array,例如

type MYREC_Array is array (Int range <>) of aliased MYREC;
type MYREC_Array_Access is access all MYREC_Array;

我认为GNAT编译器会指望这个数据指向包含后跟数据的边界的数据。并且您不会让C allocMyrec为边界留出空间,除非您可以访问C代码并且可以编写一个为边界留出空间的包装器。但要做到这一点,您必须确切知道GNAT编译器的工作原理,并且答案将与该特定实现相关联。如果有一些特殊声明可以告诉GNAT将界限分开,我就不知道了。

有些编译器可能能够处理这个问题;例如Irvine编译器的Ada编译器将绑定信息保留为MYREC_Array_Access类型的一部分,而不是与数据连续。 (这种方法有优点和缺点。)对于这样的编译器,您可以构造一个具有所需边界的指针,并指向allocMyrec返回的数据。但这样做需要使用未经检查的操作,并且是高度特定于实现的。

在某些情况下,您可以执行以下操作:

procedure Do_Some_Work (Size : Integer) is
    subtype MYREC_Array_Subtype is MYREC_Array (1 .. Size);
    type MYREC_Array_Access is access all MYREC_Array_Subtype;
    function allocMyrec (Size : Interfaces.C.int) return MYREC_Array_Subtype;
    pragma Import (C, allocMyrec);
begin
    ...

现在,由于边界内置于子类型中,因此不需要将它们存储在内存中的任何位置;这样就行了,每次引用allocMyrec返回的数组元素时,编译器都会确保索引在1..Size范围内。但是你无法在Do_Some_Work之外使用此结果。您无法将访问对象转换为Do_Some_Work之外定义的任何其他访问类型。所以这是一个相当有限的解决方案。

编辑:我假设你想要一个指向数组的Ada访问对象;如果没有,那么霍尔特的回答是好的。对不起,如果我误解了你在找什么。

答案 2 :(得分:0)

这是您在预览答案评论中提出的问题的另一个答案(与之前的答案太长且太不同,只能进行编辑)。

所以你的C代码如下:

typedef struct { ... } MYREC ;

MYREC *last_allocated ;

// Allocate an array of N MYREC and return N.
// The allocated array is "stored" in last_allocated.
int alloc_myrec (void) { ... }

MYREC* get_last_allocated (void) {
    return last_allocated ;
}

然后是你的Ada身体:

procedure MYREC_Test is

    type MYREC is record
        ...
    end record ;
    pragma Convention(C, MYREC) ;

    -- Global and unconstrained array
    type MYREC_Array is array (Int range <>) of aliased MYREC ;
    pragma Convention(C, MYREC_Array);

begin

    declare

        -- Allocate and retrieve the array
        Size : Int := AllocMyrec ;
        subtype MYREC_Array_Subtype is MYREC_Array (1 .. Size);
        type MYREC_Array_Access is access all MYREC_Array_Subtype;
        function GetAlloc return MYREC_Array_Access;
        pragma Import (C, GetAlloc, "get_last_alloc");

        MyArray : MYREC_Array_Access := GetAlloc ;

    begin

    -- Do whatever you want with MyArray

    end ;

end ;

正如我在之前的评论中所说,以这种方式工作有点难看。 Interfaces.C.Pointers包意味着你可以做你想做的事情,如果你把所需的一切都放在一个包里,它会更容易使用。

答案 3 :(得分:0)

total = total + 3.00;

此记录包含“未固定”格式的数据,即您不直接使用它。相反,每当需要使用数据时,都必须固定它们:

type MYREC is record
   n: Integer;
   str: System.Address;
end record with Convention => C;

可以使用闭包(或泛型)自动执行此任务。

declare
   Pinned_MYREC : String (1 .. MYREC_Value.n)
     with Import, Address => MYREC_Value.str;
begin
   -- work with Pinned_MYREC
end;

而且,通常(当未将pragma Pack应用于访问类型时),您可以以依赖于系统的方式构造胖指针类型。不是火箭科学。

根据我的经验,我遇到的问题是胖指针不够胖。在GNAT中,它的第二个指针指向边界,因此必须将它们分配到某个位置。因此,这些定制的胖指针只能驻留在为边界提供存储的某个容器中。并且可能在发生“调整新位置”时将胖指针的下半部分修补到新边界位置。

In this project我提供用于直接Unbounded_String访问的类型安全的字符串视图和编辑器;适用于Linux x86-64的GNAT。在我的项目容器中有限制,因此不需要调整。在我的项目中,胖指针是访问判别式,因此它们不会泄漏。