Recent

Author Topic: Universal Library Headers  (Read 22352 times)

Fred vS

  • Hero Member
  • *****
  • Posts: 1675
    • miXimum is the DJ's best friend
Universal Library Headers
« on: March 06, 2014, 11:03:26 pm »
Hello.

Because of the maturity and recent optimization of fpc, there is no more reason that nearly all the good shared libraries are in C.

fpc libraries can do all what c libraries do, but even more...
For example, a C library can not dynamically load other libraries.
fpc can.

C libraries are system-specific while fpc libraries can be used in lot of systems, with the same code.

I went to several foreign-languge fora asking help to translate a Pascal header-type but with no luck... (C,C++, Java, Python,... no answers...)

So i will try here...  :-[

That piece of code is the "classical-simple" way to dynamically load a external library:

Code: [Select]
unit mylib_h  /// the pascal header

interface

uses
   DynLibs;

type
TProc = procedure ;

var

  MyFunctionStr: function(Str: String): String; cdecl;
  MyFunctionInt: function(Int: Integer): Integer; cdecl;
  MyFunctionCar: function(Car: Cardinal): Cardinal; cdecl;
  MyFunctionMulti : function(Car: cardinal; Str: String; Proc: Tproc): Integer; cdecl;
 
  MyProc: procedure() ; cdecl;
  MyProcArg: procedure(Car: cardinal); cdecl;
  MyProcMulti : procedure(Car: cardinal; Str: String; Proc: Tproc); cdecl;
 
  MyFunctionInit : function(str : String) : boolean ;
  MyFunctionEnd : function() : boolean ;

function MyLibLoad(MylibFileName: String ; Ainit: string): integer;

procedure MyLibUnload() ;

implementation

function MyLibLoad(MylibFileName: String; Ainit : string): boolean;
begin
    Result := False;
    LibHandle := DynLibs.LoadLibrary(MylibFileName);
    if LibHandle <> DynLibs.NilHandle then
 begin
   Pointer(MyFunctionStr) :=  GetProcAddress(LibHandle, 'MyFunctionStr');
   Pointer(MyFunctionInt) :=  GetProcAddress(LibHandle, 'MyFunctionInt');
   Pointer(MyFunctionCar) :=  GetProcAddress(LibHandle, 'MyFunctionCar');   
   Pointer(MyFunctionMulti) :=  GetProcAddress(LibHandle, 'MyFunctionStr');
   Pointer(MyProc) :=  GetProcAddress(LibHandle, 'MyProc');
   Pointer(MyProcArg) :=  GetProcAddress(LibHandle, 'MyProcArg');   
   Pointer(MyProcMulti) :=  GetProcAddress(LibHandle, 'MyProcMulti');
   Pointer(MyFunctionInit) :=  GetProcAddress(LibHandle, 'MyFunctionInit');
   Pointer(MyFunctionEnd) :=  GetProcAddress(LibHandle, 'MyFunctionEnd');
   
   result := MyFunctionInit(Ainit) ;

  end;
 end;
 
procedure MyLibUnload();
begin
 MyFunctionEnd();
   if LibHandle <> DynLibs.NilHandle then
  begin
    DynLibs.UnloadLibrary(LibHandle);
    LibHandle := DynLibs.NilHandle;
  end;
end;

I will be very happy is somebody could translate that code into C, Java, Python, Basic,...

And it will be very useful for other developers too.

Many thanks.

 
« Last Edit: March 06, 2014, 11:07:44 pm by Fred vS »
I use Lazarus 1.8.0 32/64 and FPC 3.0.3 32/64 on Linux Mint Mate 17 32/64, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64 and Mac OS X Snow Leopard 32.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt, Carbon.

https://github.com/fredvs

Leledumbo

  • Hero Member
  • *****
  • Posts: 8112
  • Programming + Glam Metal + Tae Kwon Do = Me
Re: Universal Library Headers
« Reply #1 on: March 07, 2014, 05:53:15 pm »
Quote
For example, a C library can not dynamically load other libraries.
fpc can.
Actually, C can. But C doesn't have platform abstraction for this functionality in its standard libraries (neither standard Pascal does, but our beloved Modern Pascal implementation does have it).
Quote
C libraries are system-specific while fpc libraries can be used in lot of systems, with the same code.
If you count #ifdef-#elif-#else-#endif, then C libraries can also be built on many platforms with the same code :P
Quote
I will be very happy is somebody could translate that code into C, Java, Python, Basic,...
It can't. You have procedural variables returning and accepting Pascal strings. Other languages don't have any idea about its structure and there's a reference counting issue here. It won't even work correctly when called by Pascal programs, because string managers exist on both sides: the program and the library.

Fred vS

  • Hero Member
  • *****
  • Posts: 1675
    • miXimum is the DJ's best friend
Re: Universal Library Headers
« Reply #2 on: March 07, 2014, 09:54:33 pm »
@ Leledumbo => many thanks.  ;)

Quote
It can't. You have procedural variables returning and accepting Pascal strings

Aaaarg...  :'(

But im sure It can't does not exist for fpc...  :-X

So, to resume, procedural variables returning and accepting integer, float,... can do it.

Problem is with strings. And with characters or pointers ?
« Last Edit: March 07, 2014, 09:56:45 pm by Fred vS »
I use Lazarus 1.8.0 32/64 and FPC 3.0.3 32/64 on Linux Mint Mate 17 32/64, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64 and Mac OS X Snow Leopard 32.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt, Carbon.

https://github.com/fredvs

Leledumbo

  • Hero Member
  • *****
  • Posts: 8112
  • Programming + Glam Metal + Tae Kwon Do = Me
Re: Universal Library Headers
« Reply #3 on: March 08, 2014, 05:06:25 am »
Quote
But im sure It can't does not exist for fpc...  :-X
Every language implementations do have limitations, we have to accept the facts.
Quote
So, to resume, procedural variables returning and accepting integer, float,... can do it.

Problem is with strings. And with characters or pointers ?
With the automatic memory management. Most if not all language implementations understand and can call C functions, so if you want your library to be accessible by as many languages as possible, limit the interface to what C understands. It's however, perfectly possible and legal to export opaque pointer (and all required functionalities, especially (de)allocation) to be used by external code. Here's the relevant wiki article.

Fred vS

  • Hero Member
  • *****
  • Posts: 1675
    • miXimum is the DJ's best friend
Re: Universal Library Headers
« Reply #4 on: March 08, 2014, 07:41:52 pm »
@ Leledumbo, one more time you make my day...  ;)

Excellent news and lets  conquer the world...  ;D
I use Lazarus 1.8.0 32/64 and FPC 3.0.3 32/64 on Linux Mint Mate 17 32/64, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64 and Mac OS X Snow Leopard 32.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt, Carbon.

https://github.com/fredvs

Fred vS

  • Hero Member
  • *****
  • Posts: 1675
    • miXimum is the DJ's best friend
Re: Universal Library Headers
« Reply #5 on: March 08, 2014, 08:29:39 pm »
Hum, sorry, im already back...  :-[

Is that ok (i mean easy-exportable) ?

Code: [Select]
  MyFunctionStr: function(Str: Pchar): Pchar; cdecl;
[EDIT] : Hum, just find that from http://stackoverflow.com

Pascal code :
Code: [Select]
procedure DLL_Message(Location:PChar;AIntValue :integer);stdcall;
external 'DLLTest.dll';

C#  code:
Code: [Select]
DllImport(
            "DLLTest.dll",
            CallingConvention = CallingConvention.StdCall,
            CharSet = CharSet.Ansi,
            EntryPoint = "DLL_Message"
        )]
        public static extern void DLL_Message(
            [MarshalAs(UnmanagedType.LPStr)] string Location,
            int AIntValue
        );

PS : Ooops, poor C developers, your syntax is so complicated vs Pascal  :-X
« Last Edit: March 08, 2014, 08:51:41 pm by Fred vS »
I use Lazarus 1.8.0 32/64 and FPC 3.0.3 32/64 on Linux Mint Mate 17 32/64, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64 and Mac OS X Snow Leopard 32.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt, Carbon.

https://github.com/fredvs

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: Universal Library Headers
« Reply #6 on: March 08, 2014, 08:51:19 pm »
No don't do that. Study the Windows API and see how it returns pchars.
Your way is using the dlls memory manager to allocate memory for the PChar, this means that your application can not free that memory it needs to be freed by the dll. If you want your application to be able to free the pchars then you have to let the application allocate them too. So in your case go for a procedure something along the lines of

Code: [Select]
Function MyStrTest(inStr:Pchar; outStr:pChar; size:integer):integer;
const
  MyRealResult :string = 'This is a test for returning strings';
begin
  Result := length(myRealResult);
if size >= result then move(OutStr[0], MyRealResult[1], result);
end;
you call it like this
Code: [Select]
var
  MyLen : integer;
  MyStr : string;
begin
   MyLen := MyStrTest(nil, @MyStr[1], 0);
   SetLEngth(MyStr, mylen+1);
   FillChar(MyStr[1], MyLen+1,0);
   MyStrTest(nil, @MyStr[1], 0);
   ShowMessage(MyStr);
end
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

Fred vS

  • Hero Member
  • *****
  • Posts: 1675
    • miXimum is the DJ's best friend
Re: Universal Library Headers
« Reply #7 on: March 08, 2014, 08:53:44 pm »
@ Taazz : many thanks.

I will deeply study your code.
I use Lazarus 1.8.0 32/64 and FPC 3.0.3 32/64 on Linux Mint Mate 17 32/64, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64 and Mac OS X Snow Leopard 32.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt, Carbon.

https://github.com/fredvs

Fred vS

  • Hero Member
  • *****
  • Posts: 1675
    • miXimum is the DJ's best friend
Re: Universal Library Headers
« Reply #8 on: March 08, 2014, 08:57:02 pm »
Quote
this means that your application can not free that memory it needs to be freed by the dll

Hum, it is exactly what i want... I want that the dll takes care of everything...
Even freed the memory...
« Last Edit: March 08, 2014, 09:17:35 pm by Fred vS »
I use Lazarus 1.8.0 32/64 and FPC 3.0.3 32/64 on Linux Mint Mate 17 32/64, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64 and Mac OS X Snow Leopard 32.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt, Carbon.

https://github.com/fredvs

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: Universal Library Headers
« Reply #9 on: March 08, 2014, 09:10:09 pm »
Impossible, when is the memory to be freed? when is the string not used anymore? Can you know? Should you care? it is the application prerogative to keep it or free it when ever it wants to, otherwise you either provide a function to free that memory when ever the user wants or keep allocating more and more memory for the same data again and again until the library is unloaded.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

Fred vS

  • Hero Member
  • *****
  • Posts: 1675
    • miXimum is the DJ's best friend
Re: Universal Library Headers
« Reply #10 on: March 08, 2014, 09:21:13 pm »
Quote
Impossible, when is the memory to be freed? when is the string not used anymore? Can you know? Should you care? it is the application prerogative to keep it or free it when ever it wants to, otherwise you either provide a function to free that memory when ever the user wants or keep allocating more and more memory for the same data again and again until the library is unloaded.

OK, you've convinced me, time to perfectly understand and use your code now...  ;)

Many thanks my friend.
I use Lazarus 1.8.0 32/64 and FPC 3.0.3 32/64 on Linux Mint Mate 17 32/64, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64 and Mac OS X Snow Leopard 32.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt, Carbon.

https://github.com/fredvs

Fred vS

  • Hero Member
  • *****
  • Posts: 1675
    • miXimum is the DJ's best friend
Re: Universal Library Headers
« Reply #11 on: March 09, 2014, 12:19:37 am »
@ Taazz... Hum, i have some questions about your code... :-[

Is that code ok ?

In library :
Code: [Select]
MyStrTest(inStr:Pchar; outStr:pChar; size:integer):integer; cdecl; 
var
  MyRealResult :string;
begin
 MyRealResult :=   inStr + ' added by function ';
  Result := length(myRealResult);
if size >= result then move(OutStr[0], MyRealResult[1], result);
end;


And for the "end-user" program :
Code: [Select]

function  testLib(InputText : string) : string ;
var
 MyStrOut : string;
  MyLen : integer;

begin
   MyLen := MyStrTest( @InputText[1],@MyStrOut[1], 0);
   SetLEngth(MyStrOut, mylen+1);
   FillChar(MyStrOut[1], MyLen+1,0);

   MyStrTest(@InputText[1], @MyStrOut[1], 0);
 result := MyStrOut ;
end;

If it is ok, i do not understand size:integer parameter ?

Thanks
« Last Edit: March 09, 2014, 12:38:58 am by Fred vS »
I use Lazarus 1.8.0 32/64 and FPC 3.0.3 32/64 on Linux Mint Mate 17 32/64, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64 and Mac OS X Snow Leopard 32.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt, Carbon.

https://github.com/fredvs

Fred vS

  • Hero Member
  • *****
  • Posts: 1675
    • miXimum is the DJ's best friend
Re: Universal Library Headers
« Reply #12 on: March 09, 2014, 01:30:48 am »
Hello.
Hum, last question (and first question too) after that i promise not to borrow any more for universal library (but surely for something else  :-X ).

Is that a header for a universal library ? (and translation in other languages highly welcome ) :
Code: [Select]
unit mylib_h  /// the pascal header

interface

uses
   DynLibs;

type
TProc = procedure ;

var
 
    ////// for string as result ;
  MyFunctionStr: function(StrIn, StrOut: Pchar; size : ineger): integer; cdecl;
 
   //// for number as result
  MyFunctionInt: function(Int: Integer): cardinal; cdecl;
  MyFunctionCar: function(Car: Cardinal): float; cdecl;
  MyFunctionMulti : function(Car: cardinal; Str: Pchar; Proc: Tproc): Integer; cdecl;
 
  MyProc: procedure() ; cdecl;
  MyProcArg: procedure(Car: cardinal); cdecl;
  MyProcMulti : procedure(Car: cardinal; Str: Pchar; Proc: Tproc); cdecl;
 
  MyFunctionInit : function(str : Pchar) : boolean ;
  MyFunctionEnd : function() : boolean ;

function MyLibLoad(MylibFileName: Pchar ; Ainit:  Pchar): integer;

procedure MyLibUnload() ;

implementation

function MyLibLoad(MylibFileName: PChar; Ainit : Pchar): boolean;
begin
    Result := False;
    LibHandle := DynLibs.LoadLibrary(MylibFileName);
    if LibHandle <> DynLibs.NilHandle then
 begin
   Pointer(MyFunctionStr) :=  GetProcAddress(LibHandle, 'MyFunctionStr');
   Pointer(MyFunctionInt) :=  GetProcAddress(LibHandle, 'MyFunctionInt');
   Pointer(MyFunctionCar) :=  GetProcAddress(LibHandle, 'MyFunctionCar');   
   Pointer(MyFunctionMulti) :=  GetProcAddress(LibHandle, 'MyFunctionStr');
   Pointer(MyProc) :=  GetProcAddress(LibHandle, 'MyProc');
   Pointer(MyProcArg) :=  GetProcAddress(LibHandle, 'MyProcArg');   
   Pointer(MyProcMulti) :=  GetProcAddress(LibHandle, 'MyProcMulti');
   Pointer(MyFunctionInit) :=  GetProcAddress(LibHandle, 'MyFunctionInit');
   Pointer(MyFunctionEnd) :=  GetProcAddress(LibHandle, 'MyFunctionEnd');
   
   result := MyFunctionInit(Ainit) ;

  end;
 end;
 
procedure MyLibUnload();
begin
 MyFunctionEnd();
   if LibHandle <> DynLibs.NilHandle then
  begin
    DynLibs.UnloadLibrary(LibHandle);
    LibHandle := DynLibs.NilHandle;
  end;
end;

Many thanks.
I use Lazarus 1.8.0 32/64 and FPC 3.0.3 32/64 on Linux Mint Mate 17 32/64, Windows 10, Windows 7 32/64, Windows XP 32,  FreeBSD 64 and Mac OS X Snow Leopard 32.
Widgetset: fpGUI, MSEgui, Win32, GTK2, Qt, Carbon.

https://github.com/fredvs

User137

  • Hero Member
  • *****
  • Posts: 1791
    • Nxpascal home
Re: Universal Library Headers
« Reply #13 on: March 09, 2014, 02:45:13 am »
I see that you are using both 32 and 64-bit systems. Are you ok with integer type being 32 or 64 bit too, or would you prefer to replace them with longint? Because you use cardinal, which is strictly 32-bit, i'd assume you meant longint.

taazz

  • Hero Member
  • *****
  • Posts: 5363
Re: Universal Library Headers
« Reply #14 on: March 09, 2014, 06:12:52 am »

If it is ok, i do not understand size:integer parameter ?


No it is not, size must have the size of the outStr parameter if the size is greater or equal to the RealResult string size then copy data to the outStr memory, otherwise do not copy data, this is to avoid buffer overruns.

Code: [Select]
function  testLib(InputText : string) : string ;
var
 MyStrOut : string;
  MyLen : integer;

begin
   MyLen := MyStrTest( @InputText[1],@MyStrOut[1], 0);
   SetLEngth(MyStrOut, mylen+1);
   FillChar(MyStrOut[1], MyLen+1,0);

   MyStrTest(@InputText[1], @MyStrOut[1], Length(MyStrOut));
 result := MyStrOut ;
end;


You first call the function with zero size to calculate and return the size, you allocate enough memory to hold the result then you call it again with size set to the size of the variable accepting the data. Since PChars are a C compatible string it means that it needs a zero to mark the end of the string that is why I use MyLen+1 just to make sure that there is a end of string char.keep in mind that FPC string data type already has a null char at the end so you do not have to add it your self I put it there just as a reminder that it is required in case you are going to use pchars or array of chars instead of strings.

Hello.
Hum, last question (and first question too) after that i promise not to borrow any more for universal library (but surely for something else  :-X ).

Is that a header for a universal library ? (and translation in other languages highly welcome ) :
Code: [Select]
unit mylib_h  /// the pascal header

interface

uses
   DynLibs;

type
TProc = procedure ;

var
 
    ////// for string as result ;
  MyFunctionStr: function(StrIn, StrOut: Pchar; size : ineger): integer; cdecl;
 
   //// for number as result
  MyFunctionInt: function(Int: Integer): cardinal; cdecl;
  MyFunctionCar: function(Car: Cardinal): float; cdecl;
  MyFunctionMulti : function(Car: cardinal; Str: Pchar; Proc: Tproc): Integer; cdecl;
 
  MyProc: procedure() ; cdecl;
  MyProcArg: procedure(Car: cardinal); cdecl;
  MyProcMulti : procedure(Car: cardinal; Str: Pchar; Proc: Tproc); cdecl;
 
  MyFunctionInit : function(str : Pchar) : boolean ;
  MyFunctionEnd : function() : boolean ;

function MyLibLoad(MylibFileName: Pchar ; Ainit:  Pchar): integer;

procedure MyLibUnload() ;

implementation

function MyLibLoad(MylibFileName: PChar; Ainit : Pchar): boolean;
begin
    Result := False;
    LibHandle := DynLibs.LoadLibrary(MylibFileName);
    if LibHandle <> DynLibs.NilHandle then
 begin
   Pointer(MyFunctionStr) :=  GetProcAddress(LibHandle, 'MyFunctionStr');
   Pointer(MyFunctionInt) :=  GetProcAddress(LibHandle, 'MyFunctionInt');
   Pointer(MyFunctionCar) :=  GetProcAddress(LibHandle, 'MyFunctionCar');   
   Pointer(MyFunctionMulti) :=  GetProcAddress(LibHandle, 'MyFunctionStr');
   Pointer(MyProc) :=  GetProcAddress(LibHandle, 'MyProc');
   Pointer(MyProcArg) :=  GetProcAddress(LibHandle, 'MyProcArg');   
   Pointer(MyProcMulti) :=  GetProcAddress(LibHandle, 'MyProcMulti');
   Pointer(MyFunctionInit) :=  GetProcAddress(LibHandle, 'MyFunctionInit');
   Pointer(MyFunctionEnd) :=  GetProcAddress(LibHandle, 'MyFunctionEnd');
   
   result := MyFunctionInit(Ainit) ;

  end;
 end;
 
procedure MyLibUnload();
begin
 MyFunctionEnd();
   if LibHandle <> DynLibs.NilHandle then
  begin
    DynLibs.UnloadLibrary(LibHandle);
    LibHandle := DynLibs.NilHandle;
  end;
end;

as far as I know I do not see problems with data exchange between dll and application on the above code.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64