int add_integer(int l, int r); float add_float(float l, float r);A user of this library could call the above routines directly (by name) and the shared library linker would be able to resolve the names at 'link edit' time (i.e. when the executable was built). Thus apart from minor command line changes, using a statically-linked shared library is much the same as using a non-shared library.
Dynamic linking requires routines to be called to load the required shared library and lookup the address of symbols in that library. The user then has to call the routine at the specified address. Most systems also allow the library to be unloaded when no longer required. Two reasons to use dynamic shared libraries are
procedure Test is
function Add_Int(A, B: Integer) return Integer;
pragma Import (C, Add_Int, "add_integer");
A, B, C : Integer;
begin
A := 1;
B := 2;
C := 0;
C := Add_Int(A,B);
if C = (A+B) then
Text_Io.Put_Line("Add successful");
else
Text_Io.Put_Line("Add failed");
end if;
end Test;
Here the main program is in Ada, but the interface is assumed to be defined in terms of C.
Creating an Ada shared library has operating system, compiler and application specific aspects.
The simple test library can be trivially coded as:
package Test_Lib is
function Add(A, B: Integer) return Integer;
function Add(C, D: Float) return Float;
pragma Export_Function(Internal => Add,
External => add_integer,
Parameter_Types => (Integer, Integer),
Result_Type => integer);
pragma Export_Function(Internal => Add,
External => add_float,
Parameter_Types => (Float, Float),
Result_Type => Float);
end Test_Lib;
package body Test_Lib is
function Add(A, B: Integer) return Integer is
begin
return A + B;
end Add;
function Add(C, D: Float) return Float is
begin
return C + D;
end Add;
end Test_Lib;
The only noteworthy thing here is that the Add routines are explicitly exported with 'C-like' names. A 'non-shared library' executable can be built to test the basic system. For GNAT use the following:
gcc -g -c Test_Lib.adb gnatmake -g Test.adb -largs Test_Lib.oNote that the closure for Test no longer includes Test_Lib and so the Test_Lib object file must be explicitly passed to the linker. The resulting executable should be able to add two integers!
The details for compiling Test_Lib.adb as a shared library, and the subsequent binder/linker commands depend on the operating system being used. For OS/2 the following sequence works:
gcc -g -Zmt -Zdll Test_Lib.adb Test_Lib.def -lc_dllrt emximp -o Test_Lib.lib Test_Lib.def emximp -o Test_Lib.a Test_Lib.libThe first command creates the shared library (dll). The second two commands are required to create a COFF-style stub library so that references to the exported routines will be satisfied when
Test.exe is built. A definition file (Test_Lib.def) is required to specify the routines being exported:
LIBRARY test_lib INITINSTANCE TERMINSTANCE PROTMODE DATA NONSHARED EXPORTS add_integer add_floatFinally the executable itself can be created by:
gcc -c -g Test.adb gnatbl Test.ali Test_Lib.aThe intermediate
.lib file can then be deleted.
void *dlopen(char *path, int mode); int dlclose(void *handle); void *dlsym(void *handle, char *symbol); char *dlerror(void);This can be mapped to an Ada package spec:
package Dll is
type Handle_Type is private;
procedure Open(Handle : in out Handle_Type;
Name : in String;
Mode : in Integer := 0);
procedure Close(Handle : in out handle_type);
function Error(Handle : in Handle_Type) return String;
generic
type Item_Type is private;
procedure Sym(Handle : in out Handle_Type;
Name : in String;
Item : out Item_Type);
Dll_Exception : exception;
private
type Handle_Type is
record
Os_Handle : System.Address;
end record;
end Dll;
Here open, close and error map pretty much directly to the corresponding 'dl' functions.
In order to simplify the mapping of addresses to subprogram calls, the generic procedure Sym is used. This is instantiated with an access to the kind of symbol being accessed. Sym then allows the look-up to be made and returns an access to the appropriate object.
As an example, consider dynamically loading the simple library of the Introduction and use the routines to add two ints. [This is probably GNAT specific, or at any rate makes no effort to ensure that C-types map to Ada-types].
procedure Test is
type Shared_Function is access function (L,R:Integer) return Integer;
procedure Map_Shared_Function is new Dll.Sym(Item_Type => Shared_Function);
Handle : Dll.Handle_Type;
Add_Int : Shared_Function;
A, B, C : Integer;
begin
Dll.Open(Handle => Handle,
Name => "test.dll");
Map_Shared_Function(Handle => Handle,
Name => "add_integer",
Item => Add_Int);
A := 1;
B := 2;
C := 0;
C := Add_Int(A,B);
if C = (A+B) then
Text_Io.Put_Line("Add successful");
else
Text_Io.Put_Line("Add failed");
end if;
Dll.Close(Handle);
exception
when Dll.Dll_Exception => Text_Io.Put_Line("dll exception: " & Dll.Error(Handle));
end Test;
The procedure Test instantiates Dll.Sym so that subprograms with the signature
function (l,r : integer) return integer;can be mapped to a (dynamic) shared library.
The body of the test function simply opens the shared library (called test.dll), maps the address of add_integer to the Ada name Add_Int then uses the routine in the normal way. Finally the shared library in unloaded for completeness.
The body of Dll is also fairly unspectacular:
package body Dll is
-- Map UNIX-style dynamic-library functions to ada routines
function Dlopen (Lib_Name : Interfaces.C.Strings.Chars_Ptr;
Mode : Interfaces.C.int) return System.Address;
pragma Import(C, Dlopen, "dlopen");
function Dlsym (Handle : System.Address;
Sym_name : Interfaces.C.Strings.Chars_Ptr) return System.Address;
pragma Import(C, Dlsym, "dlsym");
function Dlclose (Handle : System.Address) return Interfaces.C.int;
pragma Import(C, Dlclose, "dlclose");
function Dlerror return Interfaces.C.Strings.Chars_Ptr;
pragma Import (C, Dlerror, "dlerror");
function Error(Handle : in Handle_Type) return String is
C_Str : Interfaces.C.Strings.Chars_Ptr;
begin
C_Str := Dlerror;
if C_Str = Interfaces.C.Strings.Null_Ptr then
return "";
else
return Interfaces.C.Strings.Value(C_Str);
end if;
end Error;
procedure Open(Handle : in out Handle_Type;
Name : in String;
Mode : in Integer := 0) is
Raw_Address : System.Address;
C_Str : Chars_Ptr;
begin
C_Str := Interfaces.C.Strings.New_String(Name);
Raw_Address := Dlopen(Lib_Name => C_Str,
Mode => Interfaces.C.Int(Mode));
Interfaces.C.Strings.Free(C_Str);
Handle.Os_Handle := Raw_Address;
if Raw_Address = System.Null_Address then raise Dll_Exception; end if;
end;
procedure Close(Handle : in out handle_type) is
Os_Ret_Code : Interfaces.C.Int;
begin
Os_Ret_Code := Dlclose(Handle.Os_Handle);
if Os_Ret_Code /= 0 then raise Dll_Exception; end if;
end Close;
procedure Sym(Handle : in out Handle_Type;
Name : in String;
Item : out Item_Type) is
function Address_To_Access is new
Unchecked_Conversion(Source => System.Address, Target => Item_Type);
Raw_Address : System.Address;
C_Str : Chars_Ptr;
begin
C_Str := Interfaces.C.Strings.New_String(Name);
Raw_Address := Dlsym(Handle => Handle.Os_handle,
Sym_Name => C_Str);
Interfaces.C.Strings.Free(C_Str);
Item := Address_To_Access(Raw_Address);
if Raw_Address = System.Null_Address then raise Dll_Exception; end if;
end;
end Dll;
| Original at http://www.sharpe-practice.co.uk/isharpe/technote/ada_sl.htm. | $Id: ada_sl.htm,v 1.1 2002/01/18 12:36:01 i_sharpe Exp $ |