将结构/记录数组从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生成器的功能。但这没有用。
高度赞赏。
答案 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。在我的项目容器中有限制,因此不需要调整。在我的项目中,胖指针是访问判别式,因此它们不会泄漏。