The initialization code in a DLL build with gnat is not running automatically when imported. I did a MCVE that consists on:
division.ads
with System;
with Interfaces.C;
package Division is
--Neither of these work
procedure DllMainCRTStartup ;
pragma Export (StdCall, DllMainCRTStartup , "DllMainCRTStartup"); --Edited as noticed by Brian
-- procedure DllMain
-- pragma Export (StdCall, DllMain , "DllMain ");
function Div (A : in INTEGER; B : in INTEGER) return INTEGER;
pragma Export (C, Div, "MyDivision");
-- --If I put this, it does not compile... maybe a wrong linkage option set?
-- procedure AdaInit;
-- pragma Import (C, AdaInit, "adainit");
end Division;
division.adb
with text_io;
package body Division is
procedure DllMainCRTStartup is begin --DllMain or DllMainCRTStartup
text_io.put("INIT CODE YEAH!!!*************!"); --This does not execute :(
--AdaInit;
end DllMainCRTStartup ;
function Div(A : in INTEGER; B : in INTEGER) return INTEGER is
X : INTEGER := A/B;
begin
return X;
end Div;
end Division;
and the gpr:
library project Proj_Name is
for Library_Name use "math";
for Object_Dir use "obj";
for Source_Dirs use ("src");
for Library_Dir use "lib";
for Library_Interface use ("Division");
for Library_Kind use "dynamic";
for Library_Options use ("-LC:\GNAT\2015\lib\gcc\i686-pc-mingw32\4.9.3\adalib",
"-LC:\GNAT\2015\lib\gcc\i686-pc-mingw32\4.9.3\adalib\libgnat");
end Proj_Name;
I am testing the dll from python, with ctypes. I import it with ctypes.CDLL and I'm able to use MyDivision. However, the init code does not run when importing the dll, as the text_io is not executed.
On the other hand, if I add the AdaInit procedure to the code I get something like this when compiling:
undefined reference to `adainit'
Thank you very much!
I’m not sure how you know that the initialization code isn’t being run?
I’m running on macOS, but the Ada aspects should be similar. I wrote this package spec/body as a simpler version of yours:
package Division is
function Div (A : in INTEGER; B : in INTEGER) return INTEGER;
pragma Export (C, Div, "MyDivision");
end Division;
with Ada.Text_IO;
package body Division is
function Div(A : in INTEGER; B : in INTEGER) return INTEGER is
X : INTEGER := A/B;
begin
return X;
end Div;
procedure Test_For_Elaboration is
begin
Ada.Text_IO.Put_Line ("hello world!");
end Test_For_Elaboration;
begin
Test_For_Elaboration;
end Division;
with this simpler GPR
library project Proj_Name is
for Library_Name use "math";
for Object_Dir use "obj";
for Source_Dirs use ("src");
for Library_Dir use "lib";
for Library_Interface use ("Division");
for Library_Kind use "dynamic";
end Proj_Name;
and tested with this C code:
#include <stdio.h>
extern int MyDivision(int, int);
int main()
{
printf("42 / 2 => %d\n", MyDivision(42, 2));
return 0;
}
and the result was
$ ./caller
hello world!
42 / 2 => 21
so clearly, for me, library elaboration was called without my having to do anything.
The reason is that you specified Library_Interface in your project file, which means you’re building a stand-alone library, which
is a library that contains the necessary code to elaborate the Ada units that are included in the library. A stand-alone library is a convenient way to add an Ada subsystem to a more global system whose main is not in Ada since it makes the elaboration of the Ada part mostly transparent.
You can specify a stand-alone dynamic library which is not automatically initialized, using
for Library_Auto_Init use "false";
in which case you need to call the library’s initialization procedure yourself; it's called {library-name}init (in your case, mathinit). But then you need to call it from your main program; it’d need to be declared, in C
extern void mathinit();
Related
In the following code Mix_Card_Reader inherits from Mix_IO_Device, the latter being an abstract tagged record.
Previously it contained one Positive and two Stream_Access members. I'd like to alter the code so that it uses File_Type members instead.
The reason for that is that I want each instance of this type to be able to open and close its files as and when required, or not at all if need be.
The problem is that I cannot initialise this inheriting type because File_Type is a limited type. How can I write my Create_Mix_Card_Reader function to allow this?
.ads...
type Mix_IO_Device is abstract tagged limited
record
Block_Size : Positive;
Input_File : File_Type;
Output_File : File_Type;
end record;
type Mix_Card_Reader is new Mix_IO_Device with null record;
.adb...
function Create_Mix_Card_Reader return Mix_IO_Device_Access is
Ret : Mix_IO_Device_Access := new Mix_Card_Reader'(16, null, null);
begin
return Ret;
end Create_Mix_Card_Reader;
GNAT is complaining that I cannot pass null, null into the pair of File_Type members because they are not compatible of course, the nulls are a left-over from when this used to have Stream_Access members. It seems that I have to pass something in here but I don't want to have to prematurely open the files simply to placate the compiler.
What to do?
Edit:
I have a couple of obvious options:
use access File_Type instead (but I still have to maintain the opening/closing of the files elsewhere).
store all the File_Type objects in an array separately and just refer to them using Streams as before but this seems messy.
This should do the trick:
function Create_Mix_Card_Reader return Mix_IO_Device_Access is
Ret : Mix_IO_Device_Access := new Mix_Card_Reader'(
16, Input_Type => <>, Ouptut_Type => <>);
begin
return Ret;
end Create_Mix_Card_Reader;
The box notation is a placeholder for the default value. You need at least Ada 2005 to use it in aggregates and must not use positional notation, details are explained in the Ada 2005 Rationale. You can shorten the two assignments to others => <> if you want.
You don’t really need to initialize the File_Type variables, since they start off initialized (but not opened).
I got the impression that you didn’t start off using an access type? Try this (not an answer to the question as posed, but may still be useful):
with Ada.Text_IO; use Ada.Text_IO;
package Wossname is
type Mix_IO_Device is abstract tagged limited
record
Block_Size : Positive;
Input_File : File_Type;
Output_File : File_Type;
end record;
type Mix_Card_Reader is new Mix_IO_Device with null record;
function Create_Mix_Card_Reader return Mix_IO_Device'Class;
end Wossname;
I’m not 100% sure of the exact legality here, but I think this is "initializing in place":
package body Wossname is
function Create_Mix_Card_Reader return Mix_IO_Device'Class is
begin
return Ret : Mix_Card_Reader do
Ret.Block_Size := 16;
end return;
end Create_Mix_Card_Reader;
end Wossname;
and as you can see it compiles (and runs!) OK.
procedure Wossname.Test is
Reader : Mix_IO_Device'Class := Create_Mix_Card_Reader;
begin
begin
Create (Reader.Output_File, Name => "wossname.out", Mode => Out_File);
exception
when Use_Error =>
Open (Reader.Output_File, Name => "wossname.out", Mode => Out_File);
end;
Put (Reader.Output_File, "hi!");
Close (Reader.Output_File);
end Wossname.Test;
So this is a simple project I put together just to test before doing some cool stuff with it later. The eventual goal is to make the program able to handle modularity through awesome dll kung fu.
But, baby steps. Right now I just want to make the thing link.
Here I have Adder.ads:
package Adder is
function Add(A : Integer; B : Integer) return Integer;
end Adder;
And the respective Adder.adb:
package body Adder is
function Add(A : Integer; B : Integer) return Integer is
begin
return A + B;
end Add;
end Adder;
Exciting, I know.
I've seen several different tutorials on how to do this, and none of them agree, but taking a cue from this one, I came up with these commands:
gnatmake -c Adder.adb
gcc -shared -shared-libgcc -o Adder.dll Adder.o
This at least generates a dll. I dunno if it generates one that will actually work or if the problem is with the main exe though.
Now the main exe, I have kept everything in a separate directory so gnat doesn't try to cheat and use the .ali and .o files. Then you copy the dll into the directory before trying to build. I've tried this tweaking lots of different ways and gotten several different errors, but here is what I have right now.
Main.adb:
with Adder_Spec; use Adder_Spec;
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
begin
Put_Line(Integer'Image(Add(3,4)));
end Main;
Yay most useless program ever. Now, knowing I'm supposed to have a spec for the dll, I came up with the aforewith'd Adder_Spec.ads:
package Adder_Spec is
function Add(A : Integer; B : Integer) return Integer;
private
pragma Import(Ada, Add, "Add");
end Adder_Spec;
Now, like I said I've tried this a bunch of different ways, sometimes omitting the third import parameter, other times omitting the import altogether, sometimes keeping the import but not separating it into the private part, you name it. I've also tried playing with the compile command several ways, but here's the most recent one:
gnatmake Main.adb -bargs -shared -largs -lAdder
With this particular command it spits out an "Undefined reference to 'Add'" error. If I add the -v flag, it doesn't provide much more useful information.
Checking gnatmake --help shows me that the default mode is gnat 2012, if that makes any difference. It probably shouldn't, as I've tried compiling with the flags for 2005 and 95, too.
So... can anybody savvy enough spot the problem? Thanks in advance.
If you want to say
package Adder_Spec is
function Add(A : Integer; B : Integer) return Integer;
private
pragma Import(Ada, Add, "Add");
end Adder_Spec;
when importing the DLL, then you have to say
package Adder is
function Add(A : Integer; B : Integer) return Integer;
pragma Export (Ada, Add, "Add");
end Adder;
when building it.
GNAT’s default linker name for the generated Add would be (I think) adder__add; you should be able to see what it is using nm Adder.o.
I suggest that you follow the instructions in "Ada Plug-ins and Shared Libraries" (part 1, part 2) from AdaCore.
I've used the technique described there, and it worked quite fine.
I am trying to compile this code: https://github.com/RanaExMachina/ada-fuse
Unfortunately when building I get this error:
fuse-system.ads:147:04: size clause not allowed for variable length type
This seems to be a problem because in the code it tries to set the size of a record which has a generic type as an entry. This seems to be a new error as the developer didn't had that problem back when he wrote that 2.5 years ago. Unfortunately he isn't able to help me on short notice but I have to get that library going. I am a bit helpless in fixing this issue however.
Basically it seems to me that I have to somehow tell gnat how big that type is going to be, which - contrary to gnat's believe - is a priori knowable: it is an access type. Either in the record or the generic type definition.
The relevant parts are:
fuse-main.ads:
package Fuse.Main is
package IO is
new Ada.Direct_IO (Element_Type);
type File_Access is access IO.File_Type;
fuse-system.ads:
generic
type File_Access is private;
package Fuse.System is
...
type File_Info_Type is record
Flags : Flags_Type;
Fh_Old : Interfaces.C.unsigned_long;
Writepage : Interfaces.C.int;
Direct_IO : Boolean := True;
Keep_Cache : Boolean := True;
Flush : Boolean := True;
Nonseekable : Boolean := True;
Fh : File_Access;
Lock_Owner : Interfaces.Unsigned_64;
end record;
type File_Info_Access is access File_Info_Type;
pragma Convention (C, File_Info_Type);
for File_Info_Type'Size use 32*8;
My gnat version is: 4.9.2-1 (debian jessie)
You know that File_Access is an access type, but within Fuse.System the compiler doesn’t; all it knows is that it’s definite and supports assignment and equality. The actual could be hundreds of bytes.
To tell the compiler that it is an access type, try something like this (I compressed it into one package for my convenience, on Mac OS X, hence the 64-bit pointer size; it compiles OK):
with Ada.Text_IO;
package Fuse_Tests is
generic
type File_Type is limited private;
type File_Access is access File_Type;
package Fuse_System is
type File_Info_Type is record
Fh : File_Access;
end record;
for File_Info_Type'Size use 64;
end Fuse_System;
type File_Access is access Ada.Text_IO.File_Type;
package My_Fuse_System is new Fuse_System
(File_Type => Ada.Text_IO.File_Type,
File_Access => File_Access);
end Fuse_Tests;
Or, an alternative suggested in the comments:
with Ada.Text_IO;
package Fuse_Tests is
generic
type File_Type;
package Fuse_System is
type File_Access is access File_Type;
type File_Info_Type is record
Fh : File_Access;
end record;
for File_Info_Type'Size use 64;
end Fuse_System;
package My_Fuse_System is new Fuse_System
(File_Type => Ada.Text_IO.File_Type);
-- if needed ...
subtype File_Access is My_Fuse_System.File_Access;
end Fuse_Tests;
I have 3 files: other.ads, other.adb, and test.adb.
other.ads:
package Other is
type Thing is tagged record
Stuff : String(1..4);
end record;
procedure Say (T : in Thing);
end Other;
other.adb: not shown for brevity, and not necessary for the example.
test.adb:
with Other;
procedure Test is
T : Other.Thing := new Other.Thing;
begin
T.Stuff := "test";
T.Say;
end Test;
I get this error:
test.adb:4:23: expected type "Thing" defined at other.ads:2
test.adb:4:23: found type access to "Thing" defined at line 4
If I have these files instead:
other.ads:
package Other is
type Thing is tagged record
Stuff : String(1..4);
end record;
type Ref is access all Thing;
procedure Say (T : in Thing);
end Other;
test.adb:
with Other;
procedure Test is
T : Other.Ref := new Other.Thing;
begin
T.Stuff := "test";
T.Say;
end Test;
Then it compiles and runs fine.
Why can't I specify new Other.Thing to be of type Other.Thing?
If you declare a variable in Java, the way you set it up depends on whether the variable’s type is primitive. int foo reserves space for an integer. Thing foo, on the other hand, reserves space for a reference (pointer) to a Thing, and you use new to reserve space for the Thing itself; Thing foo = new Thing.
Ada isn’t like that (nor is C or C++, for that matter); when you say Foo : Thing, the compiler reserves space for the Thing right there (probably on the stack). So your first example can just read
T : Other.Thing;
begin
T.Stuff := “test”;
The time when you use the new keyword in Ada is when you need an access value for some reason, as you have forced in your second example; you’ve declared T as Ref, which is declared as access all Thing.
Note that in your second example, when you say
T.Stuff := “test”;
this is actually shorthand for
T.all.Stuff := “test”;
and some people like to put the .all in explicitly.
The type of T is an access type that points to an object of type Other.Thing because you specified a allocator using new. You can also simply declare an object of type Other.Thing an initialize it using an aggregate.
with Ada.Text_IO;
procedure Test is
package Other is
type Thing is tagged record
Stuff : String(1..4);
end record;
end Other;
S : Other.Thing := (Stuff => "test");
T : access Other.Thing := new Other.Thing;
begin
Ada.Text_IO.Put_Line(S.Stuff);
T.Stuff := "test";
Ada.Text_IO.Put_Line(T.Stuff);
end Test;
For example in Pascal, if I had a library that I'm compiling to DLL:
library Blah;
procedure AddToNum;
begin
Num := Num + 1;
end;
procedure PrintNum;
begin
WriteLN(Num);
end;
Exports AddToNum;
Exports PrintNum;
var
Num: Integer;
begin
Num := 0
end.
Ideally, the user could just call the AddToNum and PrintNum routines and it would do as such. However, you actually have to pass in arguments, and that means the user has to keep track of ALL of the variables I'd be using. What is the ideal method to do this? Pointers or something?
I'm looking for the variable to be the same between functions calls, much like some sort of "global"
Move your DLL code (the actual code that runs) into a separate unit (for instance, DLLCode.pas), declare the variable at the top of the implementation section, and have your .DPR file just use that unit. All of the actual code goes in DLLCode.pas, and visibility of the variable follows the normal Pascal scoping rules.
Here's a sample DLL (DLLSample.dpr and DLLCode.pas), and a test console application that uses it. All of the code compiles and properly executes under Delphi 2007 and Windows 7 64-bit.
DllSample.dpr:
library DLLSample;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
uses
SysUtils,
Classes,
DLLCode in 'DLLCode.pas';
{$R *.res}
begin
end.
DllCode.pas:
unit DLLCode;
interface
procedure AddToNum; stdcall;
procedure PrintNum; stdcall;
exports
AddToNum,
PrintNum;
implementation
// Num is only visible from here down, and starts with a value of zero when the
// DLL is first loaded. It keeps it's value until the DLL is unloaded, which is
// typically when your app is exited when using static linking, or when you
// FreeLibrary() when dynamically linking.
var
Num: Integer = 0;
procedure AddToNum;
begin
Inc(Num); // Same as Num := Num + 1;
end;
procedure PrintNum;
begin
WriteLn(Num);
end;
end.
DllTestApp.dpr:
program DLLTestApp;
{$APPTYPE CONSOLE}
uses
SysUtils;
// Links to the procedures in the DLL. Note that the DLL has to be
// in the same folder, or located somewhere on the Windows PATH. If
// it can't be found, your app won't run.
procedure AddToNum; external 'DLLSample.dll';
procedure PrintNum; external 'DllSample.dll';
begin
PrintNum; // Print initial value
AddToNum; // Add to it twice
AddToNum;
PrintNum; // Print new value
ReadLn; // Wait for Enter key
end.
This outputs:
0
2
in a console window and waits for you to hit Enter to close it.