在Ada95/构造函数和受控类型中使用C++类

Using a C++ Class in Ada95 / Constructors and Controlled Types

本文关键字:C++ 类型 Ada95 构造函数      更新时间:2023-10-16

在Ada95/构造函数和受控类型中使用C++类

我希望能够在我的Ada代码中使用C++类。我的目标是保持Ada代码可移植到Ada95规范。我不想使用GNAT或Ada05特定方法。

我使用C中带有包装器函数的pragma Import(C)来实现接口。但我很难弄清楚如何获得我的C++Ctors/Dtors自动呼叫。我的第一个想法是使用Ada控制类型Initialize将调用Ctor,Finalize将调用Dtor。这一切都很好,直到我有了一个需要我传递参数的Ctor。

Foo.h

class Foo
{
public:
    Foo();
    Foo(long x, long y, long z);
    Foo(const Foo& that);
    ~Foo();
    Foo& operator=(const Foo& that);
    long getX() const;
    long getY() const;
    long getZ() const;
    void setX(long x);
    void setY(long y);
    void setZ(long z);
private:
    long mX;
    long mY;
    long mZ;    
};

Foo_Exports.cpp

#include "foo.h"
#include <new>
extern "C"
{
    void extFoo_New    (Foo* foo) { new (foo) Foo(); }  
    void extFoo_NewXYZ (Foo* foo, long x, long y, long z)   { new (foo) Foo(x,y,z); }
    void extFoo_Delete (Foo* foo) { foo->~Foo(); }  
    long extFoo_getX(const Foo& foo) { return foo.getX(); }
    long extFoo_getY(const Foo& foo) { return foo.getY(); }
    long extFoo_getZ(const Foo& foo) { return foo.getZ(); }
    void extFoo_setX(const Foo& foo, long x) { foo.setX(x) };
    void extFoo_setY(const Foo& foo, long y) { foo.setY(y) };
    void extFoo_setZ(const Foo& foo, long z) { foo.setZ(z) };
}

cpp.foo.ads

with Ada.Finalization;
with Interfaces.C;
use Interfaces.C;
package Cpp.Foo is
    type Obj_t is new Ada.Finalization.Controlled_Type with private;
    procedure Initialize (This : in out Obj_T);
    procedure Adjust     (This : in out Obj_T);
    procedure Finalize   (This : in out Obj_T);
    function Get_X (This : access Obj_T) return Long;
    function Get_Y (This : access Obj_T) return Long;
    function Get_Z (This : access Obj_T) return Long;
    procedure Set_X(This : access Obj_T; 
                    X    : in     Long );
    procedure Set_Y(This : access Obj_T; 
                    Y    : in     Long );
    procedure Set_Z(This : access Obj_T; 
                    Z    : in     Long );
private
    type Obj_t is new Ada.Finalization.Controlled_Type with null record;
    for Obj_T'Size use 192;
    for Obj_T'Alignment use 8;
    pragma Import (C, Get_X, "extFoo_getX");
    pragma Import (C, Get_Y, "extFoo_getY");
    pragma Import (C, Get_Z, "extFoo_getZ");
    pragma Import (C, Set_X, "extFoo_setX");
    pragma Import (C, Set_Y, "extFoo_setY");
    pragma Import (C, Set_Z, "extFoo_setZ");
end Cpp.Foo;

cpp.foo.adb

with System;
package body Cpp.Foo is
    procedure Initialize (This : in out Obj_T) is
        procedure ExtFoo_New(Addr : in System.Address);
        pragma Import (C, ExtFoo_New "extFoo_New");
        procedure ExtFoo_NewXYZ(Addr  : in System.Address,
                                X     : in Long;
                                Y     : in Long;
                                Z     : in Long);
        pragma Import (C, ExtFoo_NewXYZ "extFoo_NewXYZ");
    begin
        null; -- **WHAT DO I DO HERE?!**
    end Initialize;
    procedure Adjust     (This : in out Obj_T) is
    begin
        null; -- TBD copy ctor
    end Adjust;
    procedure Finalize   (This : in out Obj_T) is
        procedure ExtFoo_Delete(Addr : in System.Address);
        pragma Import (C, ExtFoo_Delete, extFoo_Delete);
    begin
        ExtFoo_Delete(This'address);
    end Finalize;

end Cpp.Foo;

Initialize将仅在默认初始化时调用:

O : Obj_t;

是应该调用CCD_ 2的子程序。为了创建具有初始值的Obj_t,您需要另一个函数来调用ExtFoo_NewXYZ,也许是

function Create (X, Y, Z : Long_Integer) return Obj_T;

然后

O : Obj_T := Create (41, 42, 43);

也就是说,我真的不认为覆盖Ada Obj_t和C++Foo的方案是个好主意,因为这两种语言都有权使用隐藏字段。我可以想象,C++可能会存储一个指向调度表的指针,并且我确信(在GNAT中)Ada.Finalization.Controlled的子级包含实现终结链的链接。其他编译器可能会采用不同的方式(GNAT在GCC 4.7中更改了策略)。因此,我会让Obj_t包含对C++对象的引用(即Foo()返回的值)。

在Ada 95中,确实没有任何可移植的(语言标准)方法可以调用C++类方法。这适用于C、Fortran和大多数其他主要编译系统的编程语言,您可能偶尔想要与C++接口。

要处理这个问题,您必须使用与C程序接口时通常用于处理它的技术相同的技术。基本上,任何需要从其他语言调用的东西都必须有一个与之相关的C链接C++函数。在C++中,这通常意味着裸函数或静态成员函数,并应用extern "C"

Gnat由于与GCC的紧密集成,在处理C++代码方面有一些更好的设施。然而,这不是标准。

请注意,通过传递对象作为其参数,然后让非OO调用对该对象进行适当的方法调用,可以将这种非OO调用"引导"到OO方法调用中。不过,这意味着每个方法都需要一个这样的自举函数。

我已经扩展了您的代码示例,并将其上传到BitBucket 上的repo

它包含ctor包装,包括默认构造函数、复制构造函数和自定义构造函数。我添加了一些代码来提供调试输出。据我所见,一切都如预期的那样工作,但Create(自定义ctor包装器)会导致复制/删除的两个冗余跳。

Foo()
Foo(long, long, long)
Foo(const Foo&)
~Foo()
Foo(const Foo&)
~Foo()
Foo()
Foo(const Foo&)
Uninitialized A, should be   0   0   0:    0   0   0
Initialized B,   should be   4   5   6:    4   5   6
Copied B to C,   should be   4   5   6:    4   5   6
Modified C,      should be   4   7   6:    4   7   6
~Foo()
Foo(const Foo&)
Copied C to A,   should be   4   7   6:    4   7   6
~Foo()
~Foo()
~Foo()
~Foo()

我的包装器依赖于这样一种假设,即从逐位复制的对象内容中移动对象内容并调用复制构造函数是安全的,而不是从真正的构造函数创建的真正对象中复制。

当以某种方式跟踪内存中的C++对象位置时,这种方法在极少数情况下不起作用。Adjust.Temp_Copy将以这种方式成为孤儿。

Flat_Get_X使用System.Address而不是访问,因为访问常量Argument是Ada 2005的一个功能,只读This不能使用纯访问。