Recent

Author Topic: Passing a Class to library  (Read 1437 times)

Zvoni

  • Hero Member
  • *****
  • Posts: 3230
Passing a Class to library
« on: April 24, 2023, 02:15:33 pm »
Hi Folks,
quick question: is there anything to consider if i want to pass a class from/to a library (and back)?

The Forum-Search threw up some very old threads, where the general consensus was: Don't pass classes/objects from/to libraries.

Background:
I'm currently in the design-phase of a database-app, and i want the Frontend to be DB-Connection-agnostic (only the connection!).
So i thought passing a TSQLQuery-Instance from the Frontend to the Library,
kind of like "Here's the instance of a TSQLQuery. Fill it up with data from the Database, and give it back"

I'm also not sure about "passing back via a var/out argument" vs. "passing it back as Result-type" (because of memory-leaks, calling-convention, who has to cleanup/free what).

Yes, i'm aware that i could "simulate" it in the frontend by passing an array of a specific record-type and avoid passing a "object", but this looks "ugly",
nevermind that the recordtype has to be known in both: frontend and library, and i'm losing flexibilty through that (say adding a result-column)

Note: no DB-bound controls will be used. I'm doing everything by hand
« Last Edit: April 24, 2023, 02:17:43 pm by Zvoni »
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

MarkMLl

  • Hero Member
  • *****
  • Posts: 8525
Re: Passing a Class to library
« Reply #1 on: April 24, 2023, 02:32:25 pm »
I think it depends on what one means by "pass" and "library".

If it's an ordinary statically-linked library, i.e. everything compiled/linked together, I don't see an immediate problem.

If it's a DLL/so, then you need to take into account that it might not be using the same heap as the main program so it is extremely inadvisable to do anything which might result in object creation or destruction across that library interface (and that includes auto-destruction of objects owned by e.g. a stringlist).

You also have the potential issue that it is possible for the structure (including padding) of an object to get out of step if the caller and callee are compiled separately. For that reason a favour robust magic number tests, but they aren't foolproof: if not updated with every compilation then it's possible for padding to get out of step even if there is no explicit structure test, and if updated with every compilation it's impossible to reload a library (e.g. in response to a HUP).

I've referred to objects across a dynamic boundary with no adverse effects whatsoever, e.g. to merge a menu structure prepared by Lazarus and stored in a .so into a program's main form.

I'd add that while it's possible in principle to call from a dynamic library back into the main program by name, I /might/ have seen problems on Linux x86_64. However I've never had time to investigate properly, and even if it was real it might have been fixed (intentionally or otherwise) by now.

HTH.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Warfley

  • Hero Member
  • *****
  • Posts: 2037
Re: Passing a Class to library
« Reply #2 on: April 24, 2023, 02:35:24 pm »
I think you can under one condition, if the program that uses the library and the library were built using the same FPC version (and I think if you don't use -O4, which can cause field re-ordering in the classes).

Aside from that I think there is still the problem that different versions of FPC may handle classes (memory layout, VMT, RTTI, etc.) differently, which can break compatibility across versions.

Zvoni

  • Hero Member
  • *****
  • Posts: 3230
Re: Passing a Class to library
« Reply #3 on: April 24, 2023, 02:50:35 pm »
Mark, Warfly,
thx.

Both, frontend (exe) and library (dll) will be written in FPC/Lazarus, with the same FPC/Laz-versions.
My current thoughts regarding the Library go the road of dynamically loaded via LoadLibrary and grabbing the exported (flat) "interface"-Functions, assigning the function-pointers to local Frontend-Variables

Target OS will be Windows 64 Bit, optionally Mac and Linux (not a current consideration right now)

in a nutshell: i want to "blackbox" the Database-Connection, so if in the future i want to switch, say, from SQLite to MySQL, i just need to provide a "new" dll.
In a way, this also has to do with my thread some months ago about supporting multiple Database-Formats.
I think it was wp with his "blackboxing"-approach that put me on that road --> it was 440bx

btw: I think i remember there are some pitfalls when passing strings (as in "type String" --> memory-managed) incl. some mention about sharemem or such
« Last Edit: April 24, 2023, 03:11:03 pm by Zvoni »
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

MarkMLl

  • Hero Member
  • *****
  • Posts: 8525
Re: Passing a Class to library
« Reply #4 on: April 24, 2023, 02:59:16 pm »
Strings, dynamic arrays etc. are also on the heap. So don't create/destroy or change their length.

One can certainly start off with something like this in both the main and library .lpr, but I don't think using cmem is necessary provided that one is careful:

Code: Pascal  [Select][+][-]
  1. (* Note manual addition of cmem below, this is required to allow strings and    *)
  2. (* objects to be shared/transferred between the main program and a shared       *)
  3. (* library. Note also relative position of HeapTrc, if cmem is used it is not   *)
  4. (* possible to specify this at the project level (i.e. use FPC's -gh option).   *)
  5.  
  6. uses
  7. {$ifdef USE_CMEM }
  8.   cmem, {$ifdef USE_HEAPTRC } HeapTrc, {$endif }
  9. {$endif }
  10.   {$IFDEF UNIX}{$IFDEF UseCThreads}
  11.   cthreads,
  12.   {$ENDIF}{$ENDIF}
  13.   Interfaces, // this includes the LCL widgetset
  14. ...
  15.  

I normally describe the API (I won't say interface here to avoid confusion) using a .inc file which can then be used as the foundation of .pas files for both static and dynamic linkage. See the thread I started recently on MIDI for an example.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

cdbc

  • Hero Member
  • *****
  • Posts: 2572
    • http://www.cdbc.dk
Re: Passing a Class to library
« Reply #5 on: April 24, 2023, 03:04:54 pm »
Hi
It can work... I think  ::)
I've just a few days ago, played with app <- class/interface -> lib.
I declared an Interface and an abstract class to "implement it", that is I created a concrete descendant class to do the actual implementation.
Then I created a unit that can import/export memorymanagers to/from lib,
used it in both app and lib. The class/interface unit gets used in both app and lib too, oh and the interface is {$interfaces corba}, that way I control the release. Until now I've used shortstrings in class/interface, and it works when I use the app memorymanager in both. I'll have to play some more...
i'll zip a couple of units for you to look at  %) Maybe it can be of use to you...
Hang tight... Gone zippin'
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

Zvoni

  • Hero Member
  • *****
  • Posts: 3230
Re: Passing a Class to library
« Reply #6 on: April 24, 2023, 03:16:37 pm »
Strings, dynamic arrays etc. are also on the heap. So don't create/destroy or change their length.
Hmm.....so if i understood you correctly, i can have Strings, dynamic arrays of whatever on both sides, but i "pass" a pointer to it,
and whoever is the "last" one (exe or dll) has to cleanup the mess...err...memory.. :)

Would even make sense, since IIRC a loaded dll runs in the same process-space as the exe (or was that for COM-dll's?)
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

cdbc

  • Hero Member
  • *****
  • Posts: 2572
    • http://www.cdbc.dk
Re: Passing a Class to library
« Reply #7 on: April 24, 2023, 03:33:16 pm »
Hi
Right, here's a zip for you, let me know what you think  %)
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

MarkMLl

  • Hero Member
  • *****
  • Posts: 8525
Re: Passing a Class to library
« Reply #8 on: April 24, 2023, 03:37:11 pm »
You can pass a pointer to e.g. a dynamic array even if it's on a different heap (on the assumption that multiple heaps will still be in the same process space), but don't attempt to change its size.

You can use fixed-size arrays (or a pointer to the content of a dynamic array plus an explicit parameter giving the number of elements), you can use "traditional" Pascal strings. One way of looking at it is that if you stick to TP-style programming at the API level you'll be safe, but I normally play it safer and also avoid returning a function result that's anything other than a scalar (I know that a record or string[80] result /should/ be on the stack, but GOK what notion some future compiler maintainer will get into his head).

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Warfley

  • Hero Member
  • *****
  • Posts: 2037
Re: Passing a Class to library
« Reply #9 on: April 24, 2023, 05:00:19 pm »
It is very dangerous to pass pointers to managed datastructures like strings or arrays, because when dereferencing you might still trigger the lazy copy mechanisms:
Code: Pascal  [Select][+][-]
  1. procedure Foo(p: PString);
  2. begin
  3.   // This triggers lazy copy
  4.   p^[1] := 'A';
  5. end;
  6.  
  7. var
  8.   s1, s2: String;
  9. begin
  10.   ReadLn(s1);
  11.   s2 := s1;
  12.   Foo(@s2);
  13.   WriteLn(IntPtr(s1));
  14.   WriteLn(IntPtr(s2));
  15. end.
If Foo would be now from a library, this would trigger a new allocation of the memory that s2 points to within the library, even though it is "owned" by s2, which is not located in the library.

That said, as already stated by MarkMLI above, you can use cmem, this uses the c memory manager, which comes from the libc and therefore from a library, and thereby causes both the main program and the library to use the same memory manager, and therefore have the same heap.
That said, I think as soon as you use heaptrc (as in the example from MarkMLI), this doesn't work anymore. So you can cross manage heap memory with cmem, but not with cmem and heaptrc.

Also, needless to say, the internal representation of the strings or arrays may also vary between compiler versions, so passing strings or arrays, as pointer or directly may only work with the same compiler version.

Lastly, to be perfectly safe, the best way to pass strings and arrays do this is to simply pass them as element pointers and length:
Code: Pascal  [Select][+][-]
  1. procedure UseLibraryString(strData: PChar; strLength: SizeInt);
  2. var
  3.   str: String;
  4. begin
  5.   SetLength(str, strLength);
  6.   Move(strData^, str[1], strLength);
  7.   // Use str
  8. end;
  9.  
  10. procedure UseLibraryArray(arrData: PInteger; arrLength: SizeInt);
  11. var
  12.   i: Integer;
  13. begin
  14.   for i:=0 to arrLength -1 do
  15.     WriteLn(arrData[i]);
  16. end;

MarkMLl

  • Hero Member
  • *****
  • Posts: 8525
Re: Passing a Class to library
« Reply #10 on: April 24, 2023, 06:37:12 pm »
That's an interesting one. Allocating a (managed) string or dynamic array to even a static variable (i.e. not just a stringlist etc.) could result in reference counts etc. being changed.

Using cmem has definitely got a lot going for it. I can't remember exactly the context in which I used heaptrc, but the example I showed was buildable for both static and dynamic linkage... or I might just have put it in as a reminder of the mandated order.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

 

TinyPortal © 2005-2018