Ada调用时无法使用查找表,从C语言调用Ada的情况。

4
我正在处理一些Ada代码,需要从C中调用它,但我遇到了一个问题,无法解决,也不知道为什么会出现这个问题。
这里是一个测试项目,以说明这个问题: lookup.ads
with Interfaces.C; use Interfaces.C;

package lookup is
    procedure Printf(str : in Interfaces.C.char_array; i : in Positive);
    pragma Import(C, printf, "printf");

    procedure PrintLookup;
    pragma Export(C, PrintLookup, "print_lookup");
end lookup;

lookup.adb

with Interfaces.C; use Interfaces.C;

package body lookup is

    -- Month_Length : constant array (1..12) of Positive := (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

    Month_Length : constant array (1..12) of Positive := (4 | 6 | 9 | 11 => 30, 2 => 28, others => 31);

    procedure PrintLookup is
    begin

        printf("Month_Length(5): %d"&To_C(ascii.LF)&To_C(ascii.NUL), Month_Length(5));

    end PrintLookup;

end lookup;

main.adb

with lookup;

procedure main is
begin
    lookup.PrintLookup;
end main;

main.c

extern void print_lookup();

int main()
{
    print_lookup();
    return 0;
}

我有一个简单的 Makefile 来构建它:
BUILD=ada

GM=gnatmake
CC=gcc
LIB=-L/usr/lib/gcc/i686-linux-gnu/4.9/adalib


ifeq ($(BUILD),ada)
main:
    $(GM) lookup.adb main.adb
else
main: lookup.o main.o
    $(CC) $(LIB) lookup.o main.o -o $@ -lgnat

lookup.o:
    $(GM) lookup.adb

main.o:
    $(CC) -c main.c
endif


.PHONY: clean
clean:
    rm -f lookup.ali lookup.o
    rm -f main.ali main.o
    rm -f main

该makefile将生成一个名为main的可执行文件。如果makefile的第一行中的BUILD变量设置为ada,它将使用Ada的main.adb,否则使用C的main.c
现在,问题来了:如果在lookup.adb中使用Month_Length数组的第一个变体(当前已注释掉),则两个主程序的输出如下,这是正确的:

Month_Length(5): 31

但是在另一个数组(称为查找表)的情况下,C变量返回0:

Month_Length(5): 0

有人知道为什么从C调用查找表数组时会返回0吗? 有人遇到过这个问题吗? 我需要注意什么吗? 感谢您的帮助。

6
引用自[https://docs.adacore.com/gnat_ugn-docs/html/gnat_ugn/gnat_ugn/building_executable_programs_with_gnat.html]: " adainit在第一次调用Ada子程序之前,您必须调用此例程以调用必要的实体化例程,从而初始化程序的Ada部分。必须在第一次调用Ada子程序之前调用adainit。" - Vroomfondel
没错,谢谢提示,我已经发布了一个答案,说明我是如何解决这个问题的。 - Zoltán
2个回答

3

正如Vroomfondel在评论中提到的,必须调用adainit来初始化ADA。

这是我所做的修改使其工作:

这是makefile:

BUILD=c

GM=gnatmake
GB=gnatbind
CC=gcc
LIB=-L/usr/lib/gcc/i686-linux-gnu/4.9/adalib


ifeq ($(BUILD),ada)
main:
    $(GM) lookup.adb main.adb
else
main: lookup.o main.o
    $(CC) $(LIB) lookup.o b~lookup.o main.o -o $@ -lgnat

lookup.o:
    $(GM) lookup.adb
    $(GB) -n lookup.ali
    $(GM) b~lookup.adb

main.o:
    $(CC) -c main.c
endif


.PHONY: clean
clean:
    rm -f lookup.ali lookup.o
    rm -f b~lookup.*
    rm -f main.ali main.o
    rm -f main
< p > < em > gnatbind 生成 b~lookup.ads b~lookup.adb 文件,其中包含 adainit() adafinal() 函数,然后我用 gnatmake 构建它们(我不使用 gnatlink 所以必须构建),并将生成的 b~lookup.o 文件包含在连接部分中。

必须按照以下方式修改 main.c 文件(在 ADA 调用之前和之后简单地调用初始化和终止函数):

extern void print_lookup();
extern void adainit();
extern void adafinal();

int main()
{
    adainit();
    print_lookup();
    adafinal();

    return 0;
}

其余部分保持不变。

Ada库的展开(elaboration)经常被忽略。据我所知,有一种方法可以强制执行展开代码在加载库时自动执行。现在无法记起具体细节... - LoneWanderer
@LoneWanderer,请查看此答案 - 从“解决第二个问题的方法…”开始。 - Simon Wright

2
作为补充答案,我记得有一种替代方案可以自动调用init()。
Simon Wright在这个问题中指出了他的详细回答。

当DLL被加载时,Windows会系统地调用一个名为DllMain的例程。因此,可以直接从DllMain调用adainit而无需提供显式的初始化例程。 不幸的是,如果您的程序具有库级任务,则无法从DllMain调用adainit,因为系统串行化访问DllMain入口点(即,每次只能有一个线程“通过”它执行),这意味着GNAT运行时将死锁等待新创建的任务完成其初始化。

请参见此链接
也可以查看这里的详细方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接