Recent

Author Topic: how to use a class from a DLL  (Read 6827 times)

balazsszekely

  • Guest
Re: how to use a class from a DLL
« Reply #30 on: December 21, 2022, 06:31:47 am »
When you undecorate the function names (using MSVC's undname utility) you'll get the following list:

Code: [Select]
public: __cdecl COriel_USB::COriel_USB(void) __ptr64
public: __cdecl COriel_USB::~COriel_USB(void) __ptr64
private: int __cdecl COriel_USB::Connect(void) __ptr64
private: int __cdecl COriel_USB::Disconnect(void) __ptr64
public: char * __ptr64 __cdecl COriel_USB::GetLibraryVersion(void) __ptr64
private: int __cdecl COriel_USB::Initialize(void) __ptr64
public: int __cdecl COriel_USB::Query(char const * __ptr64,char * __ptr64,int) __ptr64
private: int __cdecl COriel_USB::QueryString(char const * __ptr64,char * __ptr64,int) __ptr64
public: int __cdecl COriel_USB::Read(char * __ptr64) __ptr64
public: int __cdecl COriel_USB::Send(char const * __ptr64,int) __ptr64
private: int __cdecl COriel_USB::SendString(char const * __ptr64) __ptr64
public: void __cdecl COriel_USB::__autoclassinit2(unsigned __int64) __ptr64
public: int __cdecl COriel_USB::getCommandDelay(char const * __ptr64) __ptr64
private: void __cdecl COriel_USB::stringToUpper(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > & __ptr64) __ptr64

That means that Initialize is not public and thus shouldn't be used manually.
Nice! :) I wonder why the private functions appear in the exported function list? It makes no sense to me.
« Last Edit: December 21, 2022, 06:45:31 am by GetMem »

Warfley

  • Hero Member
  • *****
  • Posts: 2054
Re: how to use a class from a DLL
« Reply #31 on: December 21, 2022, 12:38:08 pm »
The reality however is that C++ code across module boundaries is used a lot. The whole of Qt is build around that. Also LLVM supports linking against MSVC C++ code on Windows and GCC C++ code on *nix.
Yes, but these are special features of the respective compilers, and is explicetly documented for LLVM, with it's limitations (i.e. the documentation specifically states that there might be bugs with the inheritance because it is not fully compatible). This is not a language feature but a tooling feature.

And QT is actually a great example for this, because if you want to use QT in a different ecosystem, e.g. with the intel C++ compiler, you need to rebuild the whole QT SDK to ensure the compatibility, because the "standard" QT binaries are incompatible with the intel c++ ecosystem.

LLVM puts a great effort in being compatible with GCC and MSVC, but this is more the exception than the rule. Microsoft makes pretty much no efforts to be compatible with GCC and vice versa. And those are just the three big ones, theres also Intel C++ and Embarcaderos C++ Builder, which are again a story on their own.
Only things that are defined in the standard will work on all those systems, things that are not defined in the standard may work on some systems reliable, but not on others.

Nice! :) I wonder why the private functions appear in the exported function list? It makes no sense to me.
Because private does not imply internal linkage. In fact it can't do that, because a function implementation can spread over multiple compilation unit. Take the following example:
test.h
Code: C  [Select][+][-]
  1. class Foo {
  2.   int bar();
  3.   int fooBar() {
  4.     return bar() * 2;
  5.   }
  6. };
test.cpp
Code: C  [Select][+][-]
  1. #include "test.h"
  2.  
  3. int Foo::bar {
  4.   return 42;
  5. }
If a file now includes test.h then the Foo::fooBar method will be located in the compilation of that file, but the Foo::bar method will be located in the test.cpp compilation unit. So for the fooBar method to call the bar method, they need to be linked together, which requires external linkage. Therefore, even though they are declared as private, they have external linkage, to accomodate for having the methods spread over multiple compilation units.

It's a bit of a mess with the C ELF system of compilation units, as it is much less well thought through than in basically any other language (and is something that C++ tries to fix for the past 30 years with namespaces, modules, etc.).
« Last Edit: December 21, 2022, 12:41:32 pm by Warfley »

Rik

  • Jr. Member
  • **
  • Posts: 84
Re: how to use a class from a DLL
« Reply #32 on: December 21, 2022, 02:55:47 pm »
Would you please show what exactly you tried.
Code: Pascal  [Select][+][-]
  1. program toriel;
  2.  
  3. {$mode objfpc}
  4.  
  5. {$ifndef cpux86_64}
  6.   {$error Code only supported for X86_64}
  7. {$endif}
  8.  
  9. uses
  10.   Crt,sysutils;
  11.  
  12. const
  13.   LibName = 'Oriel_USB.dll';
  14.  
  15. procedure COriel_USB_Ctor(This: Pointer); cdecl; external LibName name '??0COriel_USB@@QEAA@XZ';
  16. procedure COriel_USB_Dtor(This: Pointer); cdecl; external LibName name '??1COriel_USB@@QEAA@XZ';
  17. function COriel_USB_GetLibraryVersion(This: Pointer): PChar; cdecl; external LibName index 5; //name '?GetLibraryVersion@COriel_USB@@QEAAPEADXZ';
  18. function COriel_USB_Send(This: Pointer; strCommand: PChar; bStaticDelay: LongBool): LongBool; cdecl; external LibName index 10; //name '?Send@COriel_USB@@QEAAHPEBDH@Z';
  19. function COriel_USB_Read(This: Pointer; strReturn: PChar): LongBool; cdecl; external LibName name '?Read@COriel_USB@@QEAAHPEAD@Z';
  20. function COriel_USB_Query(This: Pointer; strCommand: PChar; strReturn: PChar; bStaticDelay: LongBool = false): LongBool; cdecl; external LibName index 7; //name '?Query@COriel_USB@@QEAAHPEBDPEADH@Z';
  21. function COriel_USB_getCommandDelay(This: Pointer; command: PChar): LongInt; cdecl; external LibName index 13;//name '?getCommandDelay@COriel_USB@@QEAAHPEBD@Z';
  22.  
  23.  
  24. var
  25.   b: LongBool;
  26.   p1,p2: PChar;
  27.   oriel: Pointer;
  28.  
  29. begin
  30.   oriel:= GetMem(1);
  31.   p1:= GetMem(1);
  32.   p2:= GetMem(1);
  33.   COriel_USB_Ctor(oriel);
  34.   p1:= COriel_USB_GetLibraryVersion(oriel);
  35.   Writeln(p1);
  36.   b:= COriel_USB_Send(oriel,'GOWAVE 800'+#10,False);
  37.   if b then WriteLn('GOWAVE 800: OK') else WriteLn('GOWAVE 800: failed');
  38.   b:= COriel_USB_Send(oriel,'GOWAVE 500'+#10,False);
  39.   if b then WriteLn('GOWAVE 500: OK') else WriteLn('GOWAVE 500: failed');
  40.   b:= COriel_USB_Query(oriel,'WAVE?',p2,True);
  41.   if b then WriteLn('Wavelength = '+p2+' nm') else WriteLn('Wavelength query failed ('+p2+')');
  42.   COriel_USB_Dtor(oriel);
  43.   WriteLn('all done');
  44.  
  45.   repeat
  46.   until KeyPressed;
  47.  
  48.   FreeMem(p2);     // --+
  49.   FreeMem(p1);     //    |  causes an 'External: ACCESS VIOLATION' error
  50.   FreeMem(oriel);  // --+
  51.  
  52. end.

The above (almost) works and returns:
Quote
Cornerstone C++ DLL version 3.01
GOWAVE 800: OK
GOWAVE 500: OK
Wavelength = 500.006 nm
all done
Preallocating the PChar, as suggested by Thaddy, solved the problem with COriel_USB_Query (reading data from the monochromator).
Now communication with the monochromator works fine, both write and read.
But any of the FreeMem at the end (lines 48-50) causes an 'External: ACCESS VIOLATION' error.

Warfley

  • Hero Member
  • *****
  • Posts: 2054
Re: how to use a class from a DLL
« Reply #33 on: December 21, 2022, 03:28:51 pm »
Would you please show what exactly you tried.
Code: Pascal  [Select][+][-]
  1. program toriel;
  2.  
  3. {$mode objfpc}
  4.  
  5. {$ifndef cpux86_64}
  6.   {$error Code only supported for X86_64}
  7. {$endif}
  8.  
  9. uses
  10.   Crt,sysutils;
  11.  
  12. const
  13.   LibName = 'Oriel_USB.dll';
  14.  
  15. procedure COriel_USB_Ctor(This: Pointer); cdecl; external LibName name '??0COriel_USB@@QEAA@XZ';
  16. procedure COriel_USB_Dtor(This: Pointer); cdecl; external LibName name '??1COriel_USB@@QEAA@XZ';
  17. function COriel_USB_GetLibraryVersion(This: Pointer): PChar; cdecl; external LibName index 5; //name '?GetLibraryVersion@COriel_USB@@QEAAPEADXZ';
  18. function COriel_USB_Send(This: Pointer; strCommand: PChar; bStaticDelay: LongBool): LongBool; cdecl; external LibName index 10; //name '?Send@COriel_USB@@QEAAHPEBDH@Z';
  19. function COriel_USB_Read(This: Pointer; strReturn: PChar): LongBool; cdecl; external LibName name '?Read@COriel_USB@@QEAAHPEAD@Z';
  20. function COriel_USB_Query(This: Pointer; strCommand: PChar; strReturn: PChar; bStaticDelay: LongBool = false): LongBool; cdecl; external LibName index 7; //name '?Query@COriel_USB@@QEAAHPEBDPEADH@Z';
  21. function COriel_USB_getCommandDelay(This: Pointer; command: PChar): LongInt; cdecl; external LibName index 13;//name '?getCommandDelay@COriel_USB@@QEAAHPEBD@Z';
  22.  
  23.  
  24. var
  25.   b: LongBool;
  26.   p1,p2: PChar;
  27.   oriel: Pointer;
  28.  
  29. begin
  30.   oriel:= GetMem(1);
  31.   p1:= GetMem(1);
  32.   p2:= GetMem(1);
  33.   COriel_USB_Ctor(oriel);
  34.   p1:= COriel_USB_GetLibraryVersion(oriel);
  35.   Writeln(p1);
  36.   b:= COriel_USB_Send(oriel,'GOWAVE 800'+#10,False);
  37.   if b then WriteLn('GOWAVE 800: OK') else WriteLn('GOWAVE 800: failed');
  38.   b:= COriel_USB_Send(oriel,'GOWAVE 500'+#10,False);
  39.   if b then WriteLn('GOWAVE 500: OK') else WriteLn('GOWAVE 500: failed');
  40.   b:= COriel_USB_Query(oriel,'WAVE?',p2,True);
  41.   if b then WriteLn('Wavelength = '+p2+' nm') else WriteLn('Wavelength query failed ('+p2+')');
  42.   COriel_USB_Dtor(oriel);
  43.   WriteLn('all done');
  44.  
  45.   repeat
  46.   until KeyPressed;
  47.  
  48.   FreeMem(p2);     // --+
  49.   FreeMem(p1);     //    |  causes an 'External: ACCESS VIOLATION' error
  50.   FreeMem(oriel);  // --+
  51.  
  52. end.

The above (almost) works and returns:
Quote
Cornerstone C++ DLL version 3.01
GOWAVE 800: OK
GOWAVE 500: OK
Wavelength = 500.006 nm
all done
Preallocating the PChar, as suggested by Thaddy, solved the problem with COriel_USB_Query (reading data from the monochromator).
Now communication with the monochromator works fine, both write and read.
But any of the FreeMem at the end (lines 48-50) causes an 'External: ACCESS VIOLATION' error.

The problem is your memory management. First lets start with p1:
Code: Pascal  [Select][+][-]
  1.   p1:= GetMem(1);
  2.   [...]
  3.   p1:= COriel_USB_GetLibraryVersion(oriel);
  4.   Writeln(p1);
  5.   [...]
  6.   FreeMem(p1);
What you are doing here is you first allocate 1 byte of memory for p1, then you throw this away and replace it with the result of COriel_USB_GetLibraryVersion, which first creates a memory leak (because you never free the result from GetMem before overriding it). Then you try to free the memory behind the pointer you recieved from the library.
There are two options for that pointer, either the library allocated the memory using the default libc memory manager, in this case you can free it by using the cmem memory memory manager. But this only works if that pointer belongs to unmanaged memory within the library, if it is, for example, a pointer to the std::string data of a class field, than the destructor of field will take care of the memory cleaning, in which case you would cause a double free.
The other option is that it points to a static text in the DATA segment or the stack, in which case you can't free that memory at all. (If I had to guess I would think that this is the most likely cause)
Potential third option is that it uses a custom allocator, but I think this is rather unlikely

For finding out what to do with the data you should look in the documentation of the COriel_USB_GetLibraryVersion function and what it says about freeing that memory.

Second is p2
Code: Pascal  [Select][+][-]
  1.   p1:= GetMem(1);
  2.   p2:= GetMem(1);
  3.   [...]
  4.   b:= COriel_USB_Query(oriel,'WAVE?',p2,True);
  5.   if b then WriteLn('Wavelength = '+p2+' nm') else WriteLn('Wavelength query failed ('+p2+')');
  6.   [...]
  7.   FreeMem(p2);     // --+
Here you allocate the memory in pascal, and also free that memory, so far so good. But the problem here is that you only allocate 1 byte of memory. But from your output the COriel_USB_Query writes "500.006" into it which (together with the #0 char) is 8 bytes. So you have an out of bounds error here. For this simply increase the buffer size. Or better, use a stack buffer:
Code: Pascal  [Select][+][-]
  1. var
  2.   buff: array[0..1024] of Char;
  3. ...
  4.   b:= COriel_USB_Query(oriel,'WAVE?',@buff[0],True);
  5.   if b then WriteLn('Wavelength = '+@buff[0]+' nm') else WriteLn('Wavelength query failed ('+@buff[0]+')');

Lastly you have oriel:
Code: Pascal  [Select][+][-]
  1.   oriel:= GetMem(1);
  2.   [...]
  3.   COriel_USB_Ctor(oriel);
  4.   [...]
  5.   COriel_USB_Dtor(oriel);
  6.   [...]
  7.   FreeMem(oriel);  // --+
Here you also only allocate 1 byte. The problem here is that you don't know the size of the COriel class datastructure. Therefore it will probably also be an out of bounds here. The best way forward here would be to use a size which you assume will be big enough. I would guess that it will probabl 1024 bytes (as with p2 above) should be enough. But this is something that is very hard to estimate. This is why usually you need a wrapper function for allocating the memory of said class.

So to summarize, make the getmems for p2 and oriel larger (or use stack buffers with array[0..1023] or so) and check how the memory from COriel_USB_GetLibraryVersion must be managed. If I had to guess I would assume that it is probably some static memory and therefore does not need to be freed at all

PascalDragon

  • Hero Member
  • *****
  • Posts: 6387
  • Compiler Developer
Re: how to use a class from a DLL
« Reply #34 on: December 22, 2022, 07:37:26 am »
Lastly you have oriel:
Code: Pascal  [Select][+][-]
  1.   oriel:= GetMem(1);
  2.   [...]
  3.   COriel_USB_Ctor(oriel);
  4.   [...]
  5.   COriel_USB_Dtor(oriel);
  6.   [...]
  7.   FreeMem(oriel);  // --+
Here you also only allocate 1 byte. The problem here is that you don't know the size of the COriel class datastructure. Therefore it will probably also be an out of bounds here.

Since it was me who suggested that one: I picked a size of 1, because MSVC does the same here as no fields are listed in header. Depending on how the class is implemented a bigger size to be safe would indeed be best.

Rik

  • Jr. Member
  • **
  • Posts: 84
Re: how to use a class from a DLL
« Reply #35 on: January 02, 2023, 11:04:36 am »
This is the adapted code:
Code: Pascal  [Select][+][-]
  1. program toriel;
  2.  
  3. {$mode objfpc}
  4.  
  5. {$ifndef cpux86_64}
  6. {$error Code only supported for X86_64}
  7. {$endif}
  8.  
  9. uses
  10.   Crt,sysutils;
  11.  
  12. const
  13.   LibName = 'Oriel_USB.dll';
  14.  
  15. procedure COriel_USB_Ctor(This: Pointer); cdecl; external LibName name '??0COriel_USB@@QEAA@XZ';
  16. procedure COriel_USB_Dtor(This: Pointer); cdecl; external LibName name '??1COriel_USB@@QEAA@XZ';
  17. function COriel_USB_GetLibraryVersion(This: Pointer): PChar; cdecl; external LibName index 5; //name '?GetLibraryVersion@COriel_USB@@QEAAPEADXZ';
  18. function COriel_USB_Send(This: Pointer; strCommand: PChar; bStaticDelay: LongBool): LongBool; cdecl; external LibName index 10; //name '?Send@COriel_USB@@QEAAHPEBDH@Z';
  19. function COriel_USB_Read(This: Pointer; strReturn: PChar): LongBool; cdecl; external LibName name '?Read@COriel_USB@@QEAAHPEAD@Z';
  20. function COriel_USB_Query(This: Pointer; strCommand: PChar; strReturn: PChar; bStaticDelay: LongBool = false): LongBool; cdecl; external LibName index 7; //name '?Query@COriel_USB@@QEAAHPEBDPEADH@Z';
  21. function COriel_USB_getCommandDelay(This: Pointer; command: PChar): LongInt; cdecl; external LibName index 13;//name '?getCommandDelay@COriel_USB@@QEAAHPEBD@Z';
  22.  
  23.  
  24. var
  25.   b: LongBool;
  26.   i: LongInt;
  27.   oriel,buf: array[0..1023] of Char;
  28.   s: AnsiString;
  29.  
  30. begin
  31.  
  32.   for i:= 0 to 1023 do oriel[i]:= '|'; // fill the array to be able to check what is used of it
  33.  
  34.   COriel_USB_Ctor(@oriel);
  35.   s:= COriel_USB_GetLibraryVersion(@oriel);
  36.   Writeln(s);
  37.   b:= COriel_USB_Send(@oriel,'GOWAVE 800'+#10,False);
  38.   if b then WriteLn('GOWAVE 800: OK') else WriteLn('GOWAVE 800: failed');
  39.   b:= COriel_USB_Send(@oriel,'GOWAVE 500'+#10,False);
  40.   if b then WriteLn('GOWAVE 500: OK') else WriteLn('GOWAVE 500: failed');
  41.   b:= COriel_USB_Query(@oriel,'WAVE?',@buf,True);
  42.   SetString(s,PChar(@buf[0]),Length(buf));
  43.   s:= Copy(s,1,SizeOf(s)-1);
  44.   if b then WriteLn('Wavelength = '+s+' nm') else WriteLn('Wavelength query failed ('+s+')');
  45.  
  46.   WriteLn('oriel array:');
  47.   for i:= 0 to 1023 do Write(oriel[i]); // anything different from '|' was changed
  48.   WriteLn('');
  49.  
  50.   COriel_USB_Dtor(@oriel);
  51.   WriteLn('all done');
  52.  
  53.   repeat
  54.   until KeyPressed;
  55. end.
Seems to work fine.
Lines 32 and 50-51 are just out of curiosity to see what is used of the oriel array and as a kind of confirmation that is is lange enough:
Quote
Cornerstone C++ DLL version 3.01
GOWAVE 800: OK
GOWAVE 500: OK
Wavelength = 500.006 nm
oriel array:
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||↕☺ ☻   @Ç◄↕   ☺☻ ☺||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||p♣▼☺    ||||||||└t▲☺    0u▲☺    ||||   |@
                    ||||||||||||||||||||||||||||||||||||||||||||0r▲☺    ár▲☺
||||||||||||||||||||||||▄       ||||||||||||||||||||||||É       ☺   ||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
all done

MarkMLl

  • Hero Member
  • *****
  • Posts: 8571
Re: how to use a class from a DLL
« Reply #36 on: January 02, 2023, 11:32:12 am »
I'd just like to thank everybody for an interesting thread, this C++ business rankles.

What model is the Oriel monochromator? I notice from Googling that older ones used an RS232 interface, so there's a real chance that current USB layer is actually very thin and that investigating what sort of interface the OS thought was connected would be instructive.

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

Rik

  • Jr. Member
  • **
  • Posts: 84
Re: how to use a class from a DLL
« Reply #37 on: January 02, 2023, 11:51:38 am »
What model is the Oriel monochromator?

It is a Oriel 74125 Cornerstone (2011).
There were 3 versions I believe: RS232, USB and GPIB controlled.
We have the USB version. The RS232 version would have saved me a lot of work in controlling it in FreePascal / Lazarus.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8571
Re: how to use a class from a DLL
« Reply #38 on: January 02, 2023, 12:19:02 pm »
It is a Oriel 74125 Cornerstone (2011).
There were 3 versions I believe: RS232, USB and GPIB controlled.
We have the USB version. The RS232 version would have saved me a lot of work in controlling it in FreePascal / Lazarus.

In general GPIB isn't too bad either, I've got a handy little interface that looks rather "Hayes Modem-ish" which I use on a 'scope and logic analyser.

I don't know what's available for Windows these days, but there certainly used to be utilities which would capture the data from a USB port. This is obviously a long shot particularly now that you're making good progress towards using the provided DLL, but it /might/ be worth looking at something like that purely to add to your understanding.

However the first thing I'd try- for interest if nothing else- would be finding a Linux laptop, looking what (if any) kernel module is loaded when the monochromator is plugged into it (dmesg output), and then recording the output from  lsusb -v  which is fairly exhaustive.

The telling thing is that if the manufacturer admits that documentation is available but sparse it implies that he bought in the product but not the design expertise. As such, adding USB is likely to be a thin layer: either a serial interface like an FTDI chip (but with a custom vid:pid pair) or something to make it look like a HID device.

Judging by https://github.com/bicarlsen/oriel-cornerstone-260#usb_connection this is a bit of an FAQ...

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