DLLs einbinden

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

DLLs einbinden

Beitrag von Theozh »

Hallo,

die Frage, wie man DLLs einbindet, wird in verschiedenen Tutorien behandelt.
z.B. http://wiki.lazarus.freepascal.org/Laza ... ic_library
Doch fehlen mir da irgendwelche Informationen weil damit einfach nicht zum Ziel komme.
Ich hatte gehofft, dass das eigentlich eine FAQ ist und entsprechend ausführlich für Neueinsteiger erklärt wird.
(Win7 32bit, Lazarus 0.9.30.4)

Die Informationen, die ich von der DLL habe sind folgende:

CUSBaccess *FCWInitObject() ;
Initialisiert den funktionalen Zugriff auf die API-Funktionen.

int OpenDevice() ;
int FCWOpenDevice(CUSBaccess *obj) ;
Sucht die angeschlossenen USB-Geräte und öffnet diese. Die Anzahl der gefundenen Geräte wird zurückgegeben.

... und viele weitere Funktionen.

Der Lazarus-Code

Code: Alles auswählen

unit cleware3_unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;
 
var
  Form1: TForm1;
  USB: Pointer;
 
implementation
  const USBaccessDLL = 'USBaccess.dll';
  function InitObject: Pointer; stdcall; external USBaccessDLL name 'FCWInitObject';
  function OpenCleware(Obj: Pointer): Integer; stdcall; external USBaccessDLL;
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.Button1Click(Sender: TObject);
begin
   USB := InitObject;
   Button1.Caption := FloatToStr(OpenCleware(USB));
end;
 
end.


Was ist falsch daran?
Beim Compilieren kommen verschiedene Fehlermeldungen:
z.B. dass die Funktion FCWInitObject in der DLL nicht gefunden wurde.
Heisst das: 1) die DLL wurde gefunden aber die Funktion darinnen nicht? oder 2) die DLL wurde nicht gefunden?
Die DLL ist sowohl im Applikationsverzeichnis, sowie im Systemverzeichnis '...windows/system32' vorhanden.

Habe sowohl 'stdcall' als auch 'cdecl' versucht...
Stimmt etwas mit den Informationen zur DLL nicht?

Mit LabView konnte ich vor einiger Zeit die DLL ansprechen und alles hatte funktioniert.
Doch warum funktioniert es hier mit Lazarus nicht?
Ich bin sicher, es ist ein Anfängerfehler. Vermutlich fehlt irgendwo eine kleine Zeile, ein Komma oder sonst etwas...?!

Danke für jegliche Hinweise. Theo.

MAC
Beiträge: 770
Registriert: Sa 21. Feb 2009, 13:46
OS, Lazarus, FPC: Windows 7 (L 1.3 Built 43666 FPC 2.6.2)
CPU-Target: 32Bit

Re: DLLs einbinden

Beitrag von MAC »

Hallo, und willkommen hier im Lazarusforum.
Ich habe gerade recht wenig zeit weswegen ich nur mal knapp antworte...

Versuch mal die Definitionen über implementation zu deklarieren.

Code: Alles auswählen

{...}
var
  Form1: TForm1;
  USB: Pointer;
 
  const USBaccessDLL = 'USBaccess.dll';
  function InitObject: Pointer; stdcall; external USBaccessDLL name 'FCWInitObject';
  function OpenCleware(Obj: Pointer): Integer; stdcall; external USBaccessDLL;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
{...}

Code: Alles auswählen

Signatur := nil;

Antrepolit
Beiträge: 340
Registriert: Di 12. Sep 2006, 08:57
OS, Lazarus, FPC: Winux (L 0.9.xy FPC 2.2.z)
CPU-Target: xxBit
Kontaktdaten:

Re: DLLs einbinden

Beitrag von Antrepolit »

Setz mal einen Underscore in den String vor dem Namen: '_FCWInitObject '.

Und natürlich wird die DLL gefunden. Denn wie die Fehlermeldung des Compilers schon sagt, wird nur die Funktion nicht gefunden.
Es wäre für die Fehlersuche übrigens einfacher, die DLL dynamisch einzubinden.
Grüße, Antrepolit

care only if your os is really burning

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

Re: DLLs einbinden

Beitrag von Theozh »

Danke für den freundlichen Willkommensgruß.
Leider funktioniert weder die Verschiebung der Definition noch der "_"...
Dynamisch einbinden? Da hatte ich gelesen, dass das etwas komplizierter sei...
Warum also kompliziert, wenn es statisch einfacher gehen müsste.
Werde es aber mal nachlesen und probieren.

Bora4d
Beiträge: 290
Registriert: Mo 24. Dez 2007, 13:14
OS, Lazarus, FPC: WinXP-Pro-Sp3, Xubuntu 12.04, (Laz 1.1-SVN Mai2012, FPC 2.6.1 / 2.6.0-Linux)
CPU-Target: AMD64X2

Re: DLLs einbinden

Beitrag von Bora4d »

Wenn ich deine Funktionsnamen anschaue dann benutzt du ein Gerät/Schnittstelle. Benutz doch einfach ihre Pascal Schnittstelle. Du findest es hier unten "USBaccess V3.1.3 mit Delphi":
http://www.cleware.net/download.html

Benutzeravatar
theo
Beiträge: 10468
Registriert: Mo 11. Sep 2006, 19:01

Re: DLLs einbinden

Beitrag von theo »

Bora4d hat geschrieben:Wenn ich deine Funktionsnamen anschaue dann benutzt du ein Gerät/Schnittstelle. Benutz doch einfach ihre Pascal Schnittstelle. Du findest es hier unten "USBaccess V3.1.3 mit Delphi":
http://www.cleware.net/download.html


Wollt ich auch gleich melden. ;-)
Dort heisst die Ftk z.B.
function FCWInitObject; external USBaccessLib name '_FCWInitObject@0';

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

Re: DLLs einbinden

Beitrag von Theozh »

Danke @Bora4d und @theo, Ihr seid aber fix ;-)
Sorry, dass ich Euch nicht gleich die Quelle genannt habe.

Die Unit hatte ich auch zur Verfügung, allerdings wollte ich die Pascal-Unit nicht einbinden, um mich vor Überraschungen und Inkompatibilitäten zu bewahren. Ist denn Lazarus wirklich 1:1 kompatibel mit Delphi?
Zweiter Grund: ich weiss (noch) nicht wie's Einbinden geht. Aber werde mich früher oder später damit beschäftigen müssen ;-)
Dritter Grund: das mit dem Einbinden der DLL schien ja prinzipiell auch die einfachste Lösung zu sein.
Ich verstehe aber immer noch nicht warum die Funktion "_FCWInitObject@0" und nicht wie im Programmier-Interface-Manual angegeben "FCWInitObject" heisst (muss ich aber vielleicht auch nicht verstehen).

Bei mir heißt die Definition nun:
function InitObject():Pointer; stdcall; external USBaccessDLL name '_FCWInitObject@0';
Frage: kann man auf die Angabe von 'stdcall' verzichten?

Nun habe ich aber eine weitere Funktion, die laut Programmier-Interface-Manual einen Pointer auf double und integer enthält.

int FCWGetTemperature (CUSBaccess *obj, int deviceNo, double *Temperature, int *timeID) ;

Um diese Werte auszulesen sieht mein Code nun wie folgt aus:

Code: Alles auswählen

...
type
  PTemperature = ^double;
  PTimeID = ^integer;
 
var
  Form1: TForm1;
  USB: Pointer;
  DeviceNo: integer;
  Temperature: PTemperature;
  TimeID: PTimeID;
 
  const USBaccessDLL = 'USBaccess.dll';
  function InitObject():Pointer; stdcall; external USBaccessDLL name '_FCWInitObject@0';
  function OpenCleware(Obj: Pointer):word; stdcall; external USBaccessDLL name '_FCWOpenCleware@4';
  function GetTemperature (Obj: Pointer; DeviceNo: integer; Temperature: PTemperature; TimeID: PTimeID): integer; external USBaccessDLL name '_FCWGetTemperature@16';
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.Button1Click(Sender: TObject);
begin
   USB := InitObject;
   Button1.Caption := FloatToStr(OpenCleware(USB));
   Button1.Caption := FloatToStr(GetTemperature(USB,0,Temperature,TimeID));
end;
...

Der Code wird zwar fehlerfrei kompiliert, drücke ich aber den Button1, stürzt das Programm ab.
"Projekt ... hat Exeption-Klasse "External: SIGSEGV" ausgelöst."
Was ist denn da bei mir schon wieder falsch?

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: DLLs einbinden

Beitrag von Socke »

Theozh hat geschrieben:Frage: kann man auf die Angabe von 'stdcall' verzichten?

Einfache Antwort: Nein.

Ausführlich:
Die sogenannte Aufrufkonvention (engl. calling convention) legt fest, wie die Funktionsparameter an eine Funktion übergeben werden (Reihenfolge, etc.). stdcall ist unter Windows weit verbreitet (zum Beispiel bei Windows selbst); der Free Pascal Compiler verwendet eine eigene Aufrufkonvention (diese wird Register genannt). Damit die Parameter richtig übergeben werden, musst du entweder bei jeder Funktion die Aufrufkonvention angeben oder dem Compiler mitteilen, dass er eine andere verwenden soll.

Code: Alles auswählen

{$CALLING STDCALL} // Aufrufkonvention auf stdcall umstellen
function InitObject():Pointer; external USBaccessDLL name '_FCWInitObject@0';
{$CALLING DEFAULT} // zurücksetzen; jetzt muss wieder bei jeder Funktion sdtcall angegeben werden
function InitObject():Pointer; stdcall; external USBaccessDLL name '_FCWInitObject@0';

Viele Programmierer sind der Meinung, dass es ein besserer Programmierstil ist, wenn man die Aufrufkonvention immer mit angibt. Damit hat man auf einen Blick alle Informationen über die Schnittstelle.

Theozh hat geschrieben:Der Code wird zwar fehlerfrei kompiliert, drücke ich aber den Button1, stürzt das Programm ab.
"Projekt ... hat Exeption-Klasse "External: SIGSEGV" ausgelöst."
Was ist denn da bei mir schon wieder falsch?

Die Variablen Temperature und TimeID sind Zeiger die irgendwo in den Speicher zeigen. Du musst zuerst Speicher reservieren und den Zeiger darauf in diesen Variablen speichern:

Code: Alles auswählen

// Zeiger auf Nil setzen, damit Dispose() immer funktioniert
Temperature := nil;
TimeID := nil;
try
  // Speicher reservieren
  New(Temperature);
  New(TimeID);
  Button1.Caption := FloatToStr(GetTemperature(USB,0,Temperature,TimeID));
finally
  // Speicher freigeben
  Dispose(Temperature);
  Dispose(TimeID);
end;

Das ganze geht auch einfacher. Hier überlässt du die Verwaltung des Speichers dem Free Pascal Compiler und übergibst der Funktion Zeiger auf die Variablen

Code: Alles auswählen

var
  Temperature: Double;
  TimeID: Integer;
begin
// ...
// @Temperature liefert einen Zeiger auf Temeperature
  Button1.Caption := FloatToStr(GetTemperature(USB, 0, @Temperature, @TimeID));
end;
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
theo
Beiträge: 10468
Registriert: Mo 11. Sep 2006, 19:01

Re: DLLs einbinden

Beitrag von theo »

Zusätlich zu Socke's Antwort: Vllt. solltest du allgemein etwas sorgfältiger sein.
z.B. ein Rückgabewert eines Word oder Integer mit FloatToStr umzuwandeln, ist so eine Sache...
Führt vllt. nicht direkt zu einem Problem, aber dieser Arbeitsstil führt in der Summe zu Problemen.

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

Re: DLLs einbinden

Beitrag von Theozh »

Danke, @Socke und @theo.
Jetzt hab ich's zum Laufen gebracht. Das war ein bisschen eine Zangengeburt, wenn ich bedenke wie lange ich für so "etwas Einfaches" gebraucht habe. Man will ja nicht gleich fragen, ohne selbst zu probieren. Und ein richtig gutes Tutorial für Lazarus / Free Pascal habe ich noch nicht gefunden (Ich denke da an etwas so Übersichtliches wie z.B. SelfHTML für HTML und Perl). Aber ohne Eure Hilfe hätte mir das vermutlich in meinem Fall auch nicht viel geholfen. Danke!
Nach einigen Unerklärlichkeiten, scheint es jetzt zu laufen. Nach erfolgreicher Kompilierung hatte sich der Debugger plötzlich reproduzierbar aufgehängt.
Lösung war letztendlich, das USB-Gerät zu entfernen und wieder einzustecken. Vermutlich "Schnittstelle vermurkst"?!

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

Re: DLLs einbinden

Beitrag von Theozh »

...ich brauch mal wieder Eure Hilfe.
Gleiches Thema "DLL einbinden"... andere Baustelle:

Das ist ein Ausschnitt aus der Dokumentation zu einer Funktion der DLL:

Code: Alles auswählen

int SDCM_Init (int *iComPortNr, int iSearchComPort)
 
Inputs
Variable: iComPortNr
Type: int
Description: Returns COM-Port where device was found
Call: By reference
 
Variable: iSearchComPort
Type: int
Description: Specifies the COM/USBPort number to search
(#1...#999). For automatic search set to 0.
Call: By value
 
Return Value:
Type: Integer
Description: Integer 0 = OK, -1 = no device found


Wenn ich versuche, die Funktion zu definieren:

Code: Alles auswählen

 
type TPinteger = ^integer;
var iComPortNr: TPinteger;
...
  iComPortNr:= nil;
  FUNCTION SDCM_Init(iComPortNr: TPinteger; iSearchComport: integer):integer; stdcall; external 'SDCM_std.dll' name 'SDCM_Init';
 


Kommt beim Starten des sonst fehlerfreien kompilierten Programms die Fehlermeldung:
"Der Prozedureinsprungpunkt 'SDCM_Init' wurde in der DLL nicht gefunden."

auch "... _SDCM_Init;", "SDCM_Init@4;" oder ähnliches hilft nicht.
Was mache ich jetzt schon wieder falsch?

In der Doku gibt es noch den Hinweis:
NOTE: Some compilers like MS Visual Basic or Borland Delphi needs a
ordinal number additional to the function name to identify a function in a dll.

Gut, die Funktion "SDCM_Init" hat laut Doku die Nummer 26. Wo soll ich jetzt die 26 noch hinpacken?
Zuletzt geändert von Lori am Di 27. Aug 2013, 12:23, insgesamt 1-mal geändert.
Grund: Highlighter

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

Re: DLLs einbinden

Beitrag von Theozh »

...OK, bin einen kleinen Schritt weiter...
Bei folgenden beiden Zeilen gibt es keine Fehlermeldung mehr:

Code: Alles auswählen

 
  function SDCM_Init(iComPortNr: TPinteger; iSearchComport: integer):integer; stdcall; external 'SDCM_std.dll' name '_SDCM_Init@8';   // name '_SDCM_Init@8'
 
@8 muss es heißen, nicht @4
oder

Code: Alles auswählen

 
  function SDCM_Init(iComPortNr: TPinteger; iSearchComport: integer):integer; stdcall; external 'SDCM_std.dll' index 26;   // index 26
 


Nun stürzt aber das Programm bei der folgenden Prozedur ab:

Code: Alles auswählen

 
procedure TForm1.Button1Click(Sender: TObject);
var i: integer;
begin
  iSearchComport:= 0;
  iComPortNr:= nil;
  i:= SDCM_Init(iComPortNr, iSearchComport);
  LabeledEdit1.Text:= IntToStr(i);
end;     

Auch falls das Gerät nicht gefunden werden kann, sollte doch zumindest eine "-1" zurückgeliefert werden, oder etwa nicht?

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: DLLs einbinden

Beitrag von Socke »

Theozh hat geschrieben:

Code: Alles auswählen

 
type TPinteger = ^integer;
var iComPortNr: TPinteger;
...

Pointer-Typen werden typischerweise nur mit einem großem P am Anfang benannt (PInteger anstatt TPInteger). Bei der Portierung von C-Bibltiotheken würde ich immer die Typen aus der Unit cunits verwenden; damit ist sichergestellt, dass du immer kompatible Datentypen hast (cint entspricht einem int in C) -- egal welche Prozessorarchtektur oder welches Betriebsystem du hast.

Theozh hat geschrieben:Nun stürzt aber das Programm bei der folgenden Prozedur ab:

Wie ist denn die Fehlermeldung? Im Zweifelsfall kann das ganz einfach an falsch deklarierten Funktionen liegen.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Theozh
Beiträge: 99
Registriert: So 1. Jul 2012, 10:56

Re: DLLs einbinden

Beitrag von Theozh »

Hi, Socke, danke für die Empfehlung mit dem großen P, ich dachte an Type Point Integer, also TPinteger. Wenn Pinteger die übliche Nomenklatur ist, werde ich in Zukunft die verwenden.

Fehlermeldung:
Project XY hat Exception-Klasse "External: SIGSEGV" ausgelöst.
Bei Adresse 1000A9B2

Das Assemblerfenster sagt:
sdcm_str!_SDCM_Init@8 (341)
1000A9B2 897500 mov %esi,0x0(%ebp)
1000A9B5 8b355c910110 mov 0x1001915c,%esi
...

Aber mir sagt das leider nix...
Liegts an der DLL oder an FreePascal oder an mir?

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: DLLs einbinden

Beitrag von Socke »

Theozh hat geschrieben:Aber mir sagt das leider nix...
Liegts an der DLL oder an FreePascal oder an mir?

Das liegt an dir. Du hast vergessen, Speicherplatz für den Parameter iComPortNr zu reservieren. Dazu ganz hilfreich: Ein Buch zur Programmierung (egal welche native Programmiersprache) und das Schlagworte "Zeiger" oder "Pointer" sowie "Call by Reference".

Für deine Zwecke sollte es einfacher gehen: du musst der Funktion eine Referenz (Zeiger) auf eine Integer-Variable übergeben:

Code: Alles auswählen

var i: integer;
  iComPortNr: Integer; // Kein Zeiger!
begin
  iSearchComport:= 0;
  iComPortNr := 0;
  i:= SDCM_Init(@iComPortNr, iSearchComport); // Referenz auf iComPortNr übergeben
  // iComPortNr enthält jetzt einen Wert, der durch die Funktion gesetzt wurde.
end;
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Antworten