Entwickler-Ecke

Dateizugriff - Array of byte aus dll an Hauptprogramm übergeben


rushifell - So 18.09.11 15:11
Titel: Array of byte aus dll an Hauptprogramm übergeben
Hallo,

ich versuche mich gerade an der Programmierung von dll's und möchte ein Array of Byte von der Dll an mein Hauptprogramm übergeben. Ich denke, ich muss vermutlich den Umweg über Pchar gehen. Das Array of Byte wird in der dll mit Daten gefüllt.


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:
//code in der dll:
function setdata(var buf:Pchar):boolean;stdcall;
tmpbuf:Array [0..919of byte;
begin
...
  For i:=0 to 919 do
      buf[i]:=chr(tmpbuf[i]);
end;

//code im Hauptprogramm:
Function Readdata(var buf:Array of Byte):Boolean;
var tmpbuf:Pchar;
begin
...
GetMem(TmpBuf, (920 + 1) * SizeOf(Char));
      try
         getdata(TmpBuf);
         Move(TmpBuf[0], Buf[0], 920);
      finally
          FreeMem(TmpBuf);
      end;
...
end;


Es funktioniert anscheinend. Ist das so in Ordnung, oder muss es anders gelöst werden?

Gruß


Blup - Mo 19.09.11 10:05

Das Prinzip ist richtig, Speicher wird im Hauptprogramm reserviert, der Speicherinhalt kann in der DLL verändert werden.

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:
type
  TByteArray = packed array of Byte;
//
const
  DATASIZE = 220;
//
//code in der dll:
function getdata(const buf: TByteArray): Boolean; stdcall;
var
  tmpbuf: Array [0..(DATASIZE - 1)] of byte;
  i: Integer;
begin
  for i:= 0 to DATASIZE - 1 do
    buf[i]:= tmpbuf[i];
// alternativ  Move(TmpBuf[0], Buf[0], DATASIZE);
end;
//
//code im Hauptprogramm:
function Readdata(var buf: TByteArray):Boolean;
begin
  SetLength(buf, DATASIZE);
  getdata(buf);
end;


rushifell - Mo 19.09.11 20:00

Danke für Deine Antwort. Es ist schwer, etwas über das Thema zu finden. Hab am Sonntag den halben Tag gesucht und heute 2-3 Stunden und nicht viel gefunden, nur ein Beispiel über Strings und dll's von Luckie und ein Einsteigertutorial beim Delphi-Treff. Vielleicht such ich ja falsch ;-)

Zu meinem Beispiel hätte ich eine Frage: Ich habe irgendwo im Netz gelesen, dass man in Zeile 15 bei GetMem die Länge mit Sizeof(Char) multiplizieren muss/sollte. Kannst Du mir dazu was sagen?

Zu Deinem Beispiel. Vielen Dank. Bietet ByteArray Vorteile gegenüber Pchar? Muss ich keinen Speicher anfordern? Meine Buffergröße ist konstant 920 Bytes. Das ist wohl kein Problem?

Was ich nicht verstehe ist, dass das ganze doch gar nicht so extrem kompliziert aussieht, ich aber trotzdem keine Beispiele dafür gefunden habe.


Blup - Di 20.09.11 11:03

user profile iconrushifell hat folgendes geschrieben Zum zitierten Posting springen:

Zu meinem Beispiel hätte ich eine Frage: Ich habe irgendwo im Netz gelesen, dass man in Zeile 15 bei GetMem die Länge mit Sizeof(Char) multiplizieren muss/sollte. Kannst Du mir dazu was sagen?

AnsiChar und AnsiString sind Typen die für jedes Zeichen genau ein Byte benötigen.
In älteren Delphiversionen gilt:
Char = AnsiChar;
String = AnsiString;

WideChar und WideString benötigen für jedes Zeichen genau zwei Byte.
Ab Delphi 2009 gilt:
Char = WideChar;
String = WideString;

Du solltest dir überlegen welcher Datentyp notwendig ist.
Sollen tatsächlich Zeichenketten übergeben werden, dann definiere den Parameter z.B. als PAnsiChar oder PWideChar.

user profile iconrushifell hat folgendes geschrieben Zum zitierten Posting springen:

Bietet ByteArray Vorteile gegenüber Pchar? Muss ich keinen Speicher anfordern? Meine Buffergröße ist konstant 920 Bytes. Das ist wohl kein Problem?

Speicher muss immer in irgendeiner Form angefordert werden.
Wie die Anforderung erfolgt, hängt vom Datentyp ab.
In meinem Beispiel über SetLength.

Wenn dein Parameter immer eine feste Größe hat, ist es vieleicht sinnvoll dafür einen eigenen Datentyp zu deklarieren.
Dies kann ein Array fester Größe oder ein Record sein oder ein bischen von beidem.
z.B.:

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:
41:
42:
43:
type
  PMyParam = ^TMyParam;
  TMyParam = record
    Count: Longint; // 4 byte;
    Item: array[0..915of AnsiChar; // 916 * 1 byte
  end;
//
procedure getDaten1(MyParam: PMyParam);
begin
  MyParam^.Item[0] := 'T';
  MyParam^.Item[1] := 'e';
  MyParam^.Item[2] := 'x';
  MyParam^.Item[3] := 't';
  MyParam^.Count := 4;
end;
//
// erzeugt den gleichen Code wie getDaten1
procedure getDaten2(var MyParam: TMyParam); 
begin
  MyParam.Item[0] := 'T';
  MyParam.Item[1] := 'e';
  MyParam.Item[2] := 'x';
  MyParam.Item[3] := 't';
  MyParam.Count := 4;
end;
//
procedure holeDaten;
var
  myParam1: TMyParam; // Speicher wird beim betreten der Prozedur automatisch auf dem Stapel reserviert 
  myParam2: PMyParam;
begin
  getDaten1(@myParam1);
  getDaten2(myParam1);
//
  New(myParam2); // Speicher muss selbst auf dem Heap reserviert werden
// GetMem(myParam2, SizeOf(TMyParam)); ist gleichwertig
  try
    getDaten1(myParam2);
    getDaten2(myParam2^);
  finally
    Dispose(myParam2); // und wieder freigeben 
// FreeMem(myParam2);
  end;


user profile iconrushifell hat folgendes geschrieben Zum zitierten Posting springen:
Was ich nicht verstehe ist, dass das ganze doch gar nicht so extrem kompliziert aussieht, ich aber trotzdem keine Beispiele dafür gefunden habe.

Ist nicht kompliziert, erfordert aber Grundlagenwissen über Datentypen, Speicherverwaltung und Paramterübergabe.


rushifell - Di 20.09.11 22:50

Danke :beer:

Ich beschreibe mal, wofür ich das ganze brauche:
Ich programmiere ein Spiel, in dem die Level in einer Datei gespeichert sind. Ungepackt hat ein Level die Größe von genau 920 Bytes (deshalb die konstante Array-Größe). Es gibt die Möglichkeit, mit einem Editor eigene Levelsets zu erstellen und in einer Datei zu speichern. Im Spiel kann dann eines dieser Levelsets geladen werden. Nun möchte ich eine dll schreiben, in der Levelsets anderer Dateiformate (von Fremdanwendungen) in einen Memorystream geladen, konvertiert und in den Puffer geschrieben werden. Die einzelnen Level landen dann im Hauptprogramm, dem Spiel. Ursprünglich hatte ich die Unterstützung anderer Dateiformate in Units gesteckt. Eine dll bietet jedoch den Vorteil, dass beliebig viele zusätzliche Dateiformate eingebunden werden können und bei Bedarf recht einfach auch von anderen Leuten programmiert werden können. Die dll's werden einfach beim Programmstart aus dem Unterordner "plugins" eingelesen.

Ich habe den Code von Dir in meine dll eingebaut:
Die Variable Count brauche ich nicht, da die Puffergröße immer konstant ist.

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:
type
  PMyParam = ^TMyParam;
  TMyParam = record
    item: array[0..919of AnsiChar; // 920 * 1 byte
  end;

//code in der dll:
function setdata(var buffer :PMyParam):boolean;stdcall;
tmpbuf:Array [0..919of byte;
Begin
...
//hier wird tmpbuf mit Daten gefüllt
...
For i:=0 to 920-1 do
    buffer^.item[i]:=Chr(tmpbuf[i]);
End;


//code im Hauptprogramm:
Function Readdata(var buf:Array of Byte):Boolean;
Var TmpBuf:PMyParam;
Begin
...
    New(TmpBuf);
  try
    getdata(TmpBuf);
    For i:=0 to 919 do
        buf[i]:=Ord(TmpBuf^.item[i]);
  finally
    Dispose(TmpBuf);
  end;
...
End;


Eine Fragen hätte ich noch: Kann ich statt AnsiChar auch Byte als Typ für die Items benutzen, dann würde ich mir das Umwandeln von Byte in Char und zurück sparen. Ich brauche lediglich die Byte-Werte. Kann ich auch zusätzlich eine Variable vom Typ String in den Record packen?

Gruß


Blup - Mi 21.09.11 09:53

user profile iconrushifell hat folgendes geschrieben Zum zitierten Posting springen:
Kann ich statt AnsiChar auch Byte als Typ für die Items benutzen, dann würde ich mir das Umwandeln von Byte in Char und zurück sparen.

Klar gehen Byte, die sind sowieso die Basis von allem.
user profile iconrushifell hat folgendes geschrieben Zum zitierten Posting springen:
Kann ich auch zusätzlich eine Variable vom Typ String in den Record packen?

Strings sind problematisch, zum einen müsste man sich zwischen AnsiString oder WideString entscheiden.
Das Hauptproblem ist aber, String-Variablen sind Pointer. Diese verweisen auf einen weiteren Speicherbereich, wo ein Art Record mit der eigentliche Zeichenkette, die Länge der Zeichenkette, ein Referenzzähler und die Größe des dafür reservierten Speicherbereichs abgelegt ist. Wird dieser String in der DLL zugewiesen, wird der String vom Speichermanager der DLL verwaltet, das Programm selbst hat aber einen eigenen Speichermanager. Was dabei alles schief gehen kann, würde jetzt zu weit führen. Abgesehen davon könnte man so eine DLL nur schwer in anderen Programmiersprachen erstellen.
http://docwiki.embarcadero.com/RADStudio/XE2/de/String-Typen

Ich würde einen nullterminierte Zeichenkette fester Größe innerhalb des Records vereinbaren z.B.

Delphi-Quelltext
1:
2:
3:
4:
TMyGameRecord = record
  Name: array[0..15of AnsiChar;         // 16 Byte
  Playfield: array[0..390..22of Byte; // 40 * 23 = 920 Byte
end;


rushifell - Mi 21.09.11 17:28

Super :D
So ist es viel einfacher. Ein Level besteht genau gesagt aus einem 40 Byte großen Levelheader und einer 880 Byte großen Map. Mit 0..39 liegst Du jedoch genau richtig ;-)

Blup hat folgendes geschrieben:
Ich würde einen nullterminierte Zeichenkette fester Größe innerhalb des Records vereinbaren
So werd ich's auch machen. :zustimm:

Vielen dank nochmal für alles, auch für den guten Link.

Gruß