Frage

Okay, this'll be a long one, I apologise for that in advance. =)

I need to point out that the code used here unfortunately does not exactly match the actual production code, for confidentiality reasons, but is constructed to illustrate the problem, some tested solutions, and to facilitate discussion. On a conceptual level, it's similar enough, although everything has been stripped down and simplified. Data protection, although necessary in reality, has been ignored here.

The problem

We have a shared object, 'data_provider', which is generated from Ada code. Data_provider has an internal data record, which we need to access from a number of shared objects, 'data_user' 1 through n, also generated from (different) Ada code. These share Ada specs containing type defs, but essentially the data needs to be shared across shared object boundaries, preferably without unnecessary copying, for (valid; both estimated and benchmarked) performance reasons.

These shared objects are linked to a c++ main program (called 'wrapper' here), either compile-time or at run-time, via libdl (this isn't written in stone yet), so the solution needs to work either way. I should add that it is beneficial if the data can be inspected from the c++ end as well, even if we don't have the complete type def available there.

The code likely needs to pass Ada95 compilation, although -05 might work in a pinch. -12 is off the table. Platform is GNAT on RHEL5.

Stuff I've tried

Currently, a 'working' solution is to simply grab the address of the data record, pass it out to the wrapper, pass it into the data_user objects, convert address->access there, and copy the pointee data into internal objects. This is the method implemented in the example code below. The extra copy may be a problem performance-wise though.

Another tested method that 'works' is to simply let the data_provider export the variable, and the data_users import the same, but this imposes a requirement that they all be linked at compile-time, and it also exposes the data globally, which makes me feel rather dirty, not to mention it's brittle.

I believe for data'address use addr clauses would require knowing the address at elaboration time, and thus would not work..?

A few other things have been tried and discarded, but I'm leaving them off the table for now.

I hope that, combined with the code below is enough to get a few suggestions; I'm all ears. If anything needs clarification, please ask for it. =)

I'm actually rather hoping I'm just being daft and missing something obvious here. And I do realise that this entire mess doesn't quite adhere to good coding practice, in Ada or otherwise, but I'm still sort of stuck with it.

wrapper.cpp

extern "C"  {
    void update_data( int fa, int fb );
    int get_address( void );
    void set_address( int addr );
    void handle_new_data( void );
}

int main( int argc, char** argv ) {
    int addr;
    addr = get_address();
    set_address( addr );
    for (int i = 0; i < 42; i++) {
        update_data( i, -i );
        handle_new_data();
    }
}

data_types.ads

package data_types is

    -- dummy data type
    -- SIMPLIFIED from actual use case
    type data_t is
        record
            field_a : integer := 16#c0ffee#;
            field_b : integer := 16#c0ffee#;
        end record;

    for data_t use
        record
            field_a at 0 range 0..31;
            field_b at 4 range 0..31;
        end record;

    type data_t_ptr is access data_t;

end data_types; 

data_provider.ads

with    system,
        data_types;
use     system,
        data_types;

package data_provider is
    -- update internal data structure
    -- SIMPLIFIED from actual use case
    procedure update_data
        (   fa : in integer;
            fb : in integer );
    pragma export_procedure
    (   internal => update_data,
        external => "update_data" );


    -- return address to record containing data
    function get_address return system.address;
    pragma export_function
    (   internal => get_address,
        external => "get_address" );

    -- 'dummy' data; this needs to be passed to data_user
    data : data_t;

end data_provider;    

data_provider.adb

with    system;
use     system;

package body data_provider
is
    procedure update_data
        (   fa : in integer;
            fb : in integer )
    is
    begin
        data.field_a := fa;
        data.field_b := fb;
    end ;


    function get_address return system.address
    is
    begin
        return data'address;
    end;

end data_provider; 

data_user.ads

with    system,
        data_types;
use     system,
        data_types;

package data_user
is
    -- set address for the data record
    procedure set_address
        (   addr : system.address );
    pragma export_procedure
        (   internal => set_address,
            external => "set_address" );

    -- use the new data in internal data structure
    -- SIMPLIFIED from actual use case
    procedure handle_new_data;
    pragma export_procedure
        (   internal => handle_new_data,
            external => "handle_new_data" );

    -- 'dummy' data; this needs to be passed from data_provider
    data : data_t;

end data_user;

data_user.adb

with    system,
        unchecked_conversion,
        data_types;
use     system,
        data_types;

package body data_user
is

    function to_ptr is new unchecked_conversion
        (   source => system.address,
            target => data_t_ptr );

    -- set address for the data record
    procedure set_address
        (   addr : system.address )
    is
        ptr : data_t_ptr;
    begin
        ptr := to_ptr( addr );
        data := ptr.all;
    end;

    -- use the new data in internal data structure
    -- SIMPLIFIED from actual use case
    procedure handle_new_data
    is
    begin
        null;
    end;

end data_user;

TLDR

Multiple shared libraries, written in Ada and externally called from C++, need to access the same data, stored in an Ada record, preferably without copying. How do I do that?

War es hilfreich?

Lösung

The only place where the data gets copied to the user is in Data_User.Set_Address. Calls to Data_Provider.Update_Data change the copy in Data_Provider but have no effect on any of the users.

Why not have Set_Address store the pointer and then Handle_New_Data reads through it?

package data_user
is

   --  as before

   data_ptr : data_t_ptr;

end data_user;

.......
package body data_user
is

   .....

   -- set address for the data record
   procedure set_address
     (   addr : system.address )
   is
   begin
      data_ptr := to_ptr( addr );
   end;

   procedure handle_new_data
   is
   begin
      -- work with data_ptr.all
   end;

end data_user;

By the way, you should be using System.Address_To_Access_Conversions rather than Unchecked_Conversion for this job.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top