Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Problem beim Nutzen einer Funktion einer DLL


Shyran - Di 22.11.11 14:57
Titel: Problem beim Nutzen einer Funktion einer DLL
Huhu ihr lieben Hilfswilligen :-)

Aus einer Application Note von Silabs habe ich die Beschreibung beispielhaft folgender Funktion, die in einer mitgeliferten DLL enthalten ist:

======

Daraus ergab sich nun für mich folgender Code:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);

  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function CP210x_GetNumDevices(lpdwNumDevices: LPDWORD): integer; cdeclexternal 'CP210xManufacturing.dll';


procedure TForm1.Button1Click(Sender: TObject);
var
status : Cardinal;
dwNumDevices: DWORD;
begin
dwNumDevices:= 0;
status :=CP210x_GetNumDevices(Addr(dwNumDevices));
end;

end.


Aber schon direkt nach Start des Programmes hängt sich dieses auf. Ich vermute mal, ich habe beim Parameter übergeben etwas falsch gemacht, aber wie geht es richtig? Mehr über die DLL als solches weiß ich nicht, aufgrund der Deklaration in der Application Note vermute ich mal, dass diese in C geschrieben wurde.

Liebe Grüße,

-Shyran


Delete - Di 22.11.11 15:27

Was heißt "aufhängen"? Kommt eine Fehlermeldung oder was anderes brauchbares? Und iw kommst du auf die Aufrufkonvention cdecl?


Shyran - Di 22.11.11 16:07

Nun, aufhängen:


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
"VIDPIDder.exe funktioniert nicht mehr ....

Problemsignatur:
  Problemereignisname:  APPCRASH
  Anwendungsname:  VIDPIDder.exe
  Anwendungsversion:  0.0.0.0
  Anwendungszeitstempel:  4ecbab1c
  Fehlermodulname:  CP210xManufacturing.dll
  Fehlermodulversion:  6.0.6002.18327
  Fehlermodulzeitstempel:  4cb73436
  Ausnahmecode:  c0000135
  Ausnahmeoffset:  00009f7d
  Betriebsystemversion:  6.0.6002.2.2.0.256.6
  Gebietsschema-ID:  1031
  Zusatzinformation 1:  9d13
  Zusatzinformation 2:  1abee00edb3fc1158f9ad6f44f0f6be8
  Zusatzinformation 3:  9d13
  Zusatzinformation 4:  1abee00edb3fc1158f9ad6f44f0f6be8


Wenn Du mit dem Fehler etwas anfangen kannst, mehr hätte ich nicht parat.

stdcall hatte ich standardmäßig probiert, mit dem gleichen Ergebnis leider.

-Shyran


GuaAck - Di 22.11.11 21:12

Das VAR fehlt vermutlich, in lpdwNumDevices soll ja etwas zurück!!
Jetzt interpretiert die DLL den (zufällig vorhandenen Wert) on lpdwNumDevices als Adresse und verscuht "in die Wüchte" zu schreiben.

function CP210x_GetNumDevices(VAR lpdwNumDevices: LPDWORD): integer; cdecl; external 'CP210xManufacturing.dll';


Gruß
GuaAck


Delete - Di 22.11.11 21:59

Dann aber wohl so:

Delphi-Quelltext
1:
function CP210x_GetNumDevices(VAR lpdwNumDevices: DWORD): integer; cdeclexternal 'CP210xManufacturing.dll';                    


bummi - Di 22.11.11 23:21

wenn die DLL das tut was der Name vermute lässt, ist das gehupft wie gehechtelt

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
Procedure test(lpdwNumDevices: LPDWORD);
begin
   lpdwNumDevices^:= 20;
end;

Procedure test2(Var NumDevices: DWORD);
begin
   test(@NumDevices);
end;


procedure TForm1.Button1Click(Sender: TObject);
var
  t:Dword;
begin
  t := 1;
//  test(Addr(t));
  test2(t);
  Showmessage(IntToStr(t));
end;


Shyran - Mi 23.11.11 11:02

Guten Morgen,

erstmal herzlichen Dank für die bereits investierte Zeit und Mühe an euch um das Thema hier.

Ich habe nun Luckie's Prototyp genutzt wie oben geschrieben und habe ebenso mal bummi's Prozeduren eingebunden. Zumindest der Compiler schimpft nicht mehr, das Programm läuft durch. Allerdings muss ich jezt sagen, dass sich mir der Sinn gar nicht erschließt, speziell natürlich beim Beispiel bummi's ?

Die Funktion sollte mir doch wohl eigentlich die Anzahl (der Devices) zurückgeben, wenn ich dann zuvor in die Variable eine 20 hineinschreibe - wo ist da dann der Sinn?

ggf. stehe ich auch auf einem großen Gartenschlauch, für einen Stubs von selbigem würde ich erneut danke sagen :)

-Shyran

PS Noch eine Anfängerfrage: Was bedeutet die Schreibweise "lpdwNumDevices^:= 20;" ? Also der Akzent dort? Der accent circonflex ?
PPS: Anfängerfrage ignorieren, bissi Syntax verstehen half mir dabei schon :)


Blup - Mi 23.11.11 11:25

bummi wollte vermutlich nur darauf hinweisen, beide Deklarationen sind gültig:

Delphi-Quelltext
1:
2:
function CP210x_GetNumDevices(VAR dwNumDevices: DWORD): integer; cdeclexternal 'CP210xManufacturing.dll';
function CP210x_GetNumDevices(lpdwNumDevices: LPDWORD): integer; cdeclexternal 'CP210xManufacturing.dll';

Wenn der Variablen dwNumDevices vor dem Aufruf nichts zugewiesen wurde, könnte der Compiler aber eine Warnung ausgeben. Deshalb würde ich so deklarieren:

Delphi-Quelltext
1:
function CP210x_GetNumDevices(OUT dwNumDevices: DWORD): integer; cdeclexternal 'CP210xManufacturing.dll';                    


Shyran - Mi 23.11.11 12:04

Ah okay!

Wie muss ich dabei nun die eigentliche Funktion aufrufen? Also die CP210x_GetNumDevices ? Dort muss ich doch eine Adresse nun angeben, an der nach dem Aufruf der Funktion doch wohl dann diese Anzahl ->"This function returns the number of CP210x devices connected to the host." befindet, richtig?

Tante Edith sagt noch:

Ich probierte nun zunächst mal


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
procedure TForm1.Button1Click(Sender: TObject);
var
  t:Dword;
  i:Cardinal;
begin
  t := 1;
  test2(t);
  i:=CP210x_GetNumDevices(t);
  Edit1.Text:=IntToStr(i);
end;

wobei sich das Programm nach Buttonclick aufhängte.

Im Anschluss ersetzte ich das cdecl durch stdcall:


Delphi-Quelltext
1:
function CP210x_GetNumDevices(OUT dwNumDevices: DWORD): integer; stdcallexternal 'CP210xManufacturing.dll';                    


Immerhin tut der Knopf nun etwas, liefert mir aber als Ergebnis eine "0". Sind aber nachweislich 2 Geräte angeschlossen, lt. Gerätemanager.

-Shyran


jaenicke - Mi 23.11.11 12:58

Du gibst ja auch i statt t aus. ;-)

Das ist ja der Status, und die 0 dürfte CP210x_SUCCESS sein, also alles ok. ;-)

// EDIT:
Wie wäre es so?

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
procedure TForm1.Button1Click(Sender: TObject);
const
  CP210x_SUCCESS = 0;
var
  DeviceCount: DWord;
begin
  DeviceCount := 0;
  case CP210x_GetNumDevices(DeviceCount) of
    CP210x_SUCCESS:
      Edit1.Text := IntToStr(DeviceCount);
    // ...
  else
    Edit1.Text := '<error>';
  end;
end;


Shyran - Mi 23.11.11 13:11

Mensch ... ich sehe schon ich bin total verwirrt gewesen, aber nun fällt es mir wie Schuppen von den Augen - Du hast natürlich völlig Recht! Der Rückgabewert entspricht dem Status, wer er auch in der Application Note beschrieben ist und ist demnach - natürlich! - derzeit "0", weil alles in Ordnung ist und funktioniert.

Mir ist auch eben erst, als ich mir mal den Inhalt der Variablen im Debugger anschaut klar geworden "Mensch, in t steht ja verdächtigerweise eine 2" - guck an ... und dann, klar, wenn ich nun die Funktion simpel aufrufe per


Delphi-Quelltext
1:
  CP210x_GetNumDevices(t);                    


Steht natürlich in t exakt die gesuchte Anzahl nach Ausführung.

Wald ... Bäume ... herzlichen Dank, nun spiele ich mal weiter herum. Den Code-Schnippsel von Dir, jaenicke schnappe ichmir umgehend, heißen Dank!


Shyran - Mi 23.11.11 15:25

So nach und nach kämpfe ich mich durch die verschiedenen Funktionen der DLL, zwei weitere konnte ich nun selbständig einbinden und die funktionieren, nun sitze ich an:

======

Sprich natürlich Deklaration und im folgenden auch der Funktionsaufruf sin derneut meine Sorgen. Hab's mal bisher so versucht:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
function CP210x_GetDeviceProductString(Handle: THandle ; Product : Pointer; Length : Byte; ConvertToASCII : Boolean): integer; cdeclexternal 'CP210xManufacturing.dll';
[...]
procedure TForm1.Button2Click(Sender: TObject);
var
PartNum : byte;
PSLength : byte;
m_hUSBDevice: THandle;
status:integer;
p : Pointer;
begin
status := CP210x_Open(0, Addr(m_hUSBDevice));

status := CP210x_GetPartNumber(m_hUSBDevice, Addr(PartNum));

CP210x_GetDeviceProductString(m_hUSBDevice,p,PSLength,true);

end;


Ergebnis ist eine Zugriffsverletzung an Adresse sowieso.

LPVoid entspricht ja offenbar einem Zeiger in Delphi. Wo ist hier nun mein Denkfehler? Wie komme ich an den Productstring?


guinnes - Mi 23.11.11 15:31

Du brauchst ein LPByte für die Länge, also ein PByte


Shyran - Mi 23.11.11 16:29

Okay danke, logisch. Steht ja auch im Prototyp ... *seufz*.

Deklaration also geändert in:


Delphi-Quelltext
1:
function CP210x_GetDeviceProductString(OUT Handle: THandle ; Product : Pointer; Length : PByte; ConvertToASCII : Boolean): integer; cdeclexternal 'CP210xManufacturing.dll';                    


Wie komme ich nun an meine Daten dort?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
procedure TForm1.Button2Click(Sender: TObject);
var
PartNum : byte;
PSLength : PByte;
m_hUSBDevice: THandle;
status:integer;
pPString : Pointer;
s : String;
begin
status := CP210x_Open(0, Addr(m_hUSBDevice));
// now status = CP210x_SUCCESS

status := CP210x_GetPartNumber(m_hUSBDevice, Addr(PartNum));
// now status=CP210x_DEVICE_IO_FAILED
CP210x_GetDeviceProductString(m_hUSBDevice,pPString,PSLength,true);
// Was muss hier nun hin, um an den eigentl. String zu kommen und den in "s" zu schieben?


Was muss hier nun hin, um an den eigentl. String zu gelangen (und den in "s" zu schieben)?

Danke!


guinnes - Mi 23.11.11 16:48

1. Mach aus deinem Pointer einen PChar und
2. reserviere die xx Bytes für den PChar.
3. Die Reservierte Länge schreibst du in deinen PByte als Initwert.

( So oder so ähnlich wird das eigentlich auch in der Win-API gemacht )

Aus der Hilfe :
Zitat:
In bestimmten Fällen liegt eine lokale String-Variable vor, die durch den Aufruf einer Funktion initialisiert werden muß, die ihrerseits einen PChar als Parameter entgegennimmt. Eine mögliche Lösung ist die Erstellung eines lokalen array of char, das dann an die Funktion übergeben wird. Nach Ausführung der Funktion kann die Variable einem String zugewiesen werden:

// MAX_SIZE ist hier eine vordefinierte Konstante

var
i: Integer;
buf: array[0..MAX_SIZE] of char;
S: string;
begin
i := GetModuleFilename(0, @buf, SizeOf(buf)); // Behandelt @buf als PChar
S := buf;
// Anweisungen
end;


jaenicke - Mi 23.11.11 16:54

Die Länge ist hier ja mit CP210x_MAX_PRODUCT_STRLEN vorgegeben. Insofern ist wohl das am einfachsten:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
var
  ProductString: AnsiString;
  ProductStringLength: Byte;
begin
  ProductStringLength := CP210x_MAX_PRODUCT_STRLEN;
  SetLength(ProductString, ProductStringLength);
  case CP210x_GetDeviceProductString(m_hUSBDevice, PAnsiChar(ProductString), @ProductStringLength, True) of
    CP210x_SUCCESS:
      begin
        SetLength(ProductString, ProductStringLength); 
        ShowMessage(ProductString);
      end;
    // ...
  else
    ShowMessage('Fehler');
  end;
Ich würde aber den zweiten Parameter gleich als PAnsiChar deklarieren.


Shyran - Mi 23.11.11 17:06

Lieben Dank erstmal für die weiteren Stubse in die richtige Richtung, morgen stürze ich mich weiter auf's Thema, bis dahin herzlichen Dank!