Entwickler-Ecke

Sonstiges (Delphi) - DLL-Kompatible Dynamische Arrays


Martok - Mi 06.01.10 04:36
Titel: DLL-Kompatible Dynamische Arrays
Aloha!

Ein kleines Plugininterface müsste eigentlich so etwas wie dynamische Arrays übertragen. Also einen Datenblock, der eine variable Anzahl von Werten übermittelt.

Das ganze soll natürlich auch in anderen Sprachen ansprechbar sein, also geht ein einfaches dynamisches Array nicht. Ich hab da schon etwas gegrübelt, aber viel weiter als ein Interface zu definieren, und dem Plugin eine Factory mit zu übergeben bin ich da nicht gekommen... das konnte ich bis jetzt schön umgehen, und würde es auch gern dabei belassen.

Momentan sieht die Struktur ungefähr so aus:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
  TSingleArray = array[0..0of Single;
  PSingleArray = ^TSingleArray;
  
  TDynArray = record
    Count: integer;
    Data: PSingleArray;
  end;

Das klappt momentan recht gut, indem das einfach "irgendwo" initialisiert wird, weil alle Verwendungen dann an einer Stelle vorbeikommen, wo ich aufräumen kann. Aber schön ist was anderes, und die Speicherlecks sind in späteren Stufen schon absehbar.

Andere kreative Ideen? Achja, einigermaßen schnell sollte zumindest das Erzeugen auch sein...

Danke schonmal,
Sebastian


Flamefire - Mi 06.01.10 12:15

Hm. Was hällt dich davon ab, in der DLL eine Funktion wie folgt zu definieren?

Delphi-Quelltext
1:
procedure foo(count:Integer;memory:Pointer)                    


Aufruf dürfte klar sein.

Ansonsten wäre eher sinnvoll:

Delphi-Quelltext
1:
2:
3:
4:
  TDynArray = record
    Count: integer;
    Data: Array of Single;
  end;

Data ist schon ein Pointer auf den Inhalt des Arrays. Problem hier ist nur die Redundanz und Mehraufwand beim Verwalten von Count.

Der Aufbau eines dyn.Arrays in Delphi ist:

Delphi-Quelltext
1:
2:
3:
4:
5:
TDynArray<T>=record
  RefCount:Cardinal;
  Count:Cardinal;
  Data: Array[0..Count-1of <T>; //<--Hier zeigt der Pointer der Array Variable hin.
end;


Also wäre die obere Procedure noch am Besten.
in Delphi definieren als:

Delphi-Quelltext
1:
procedure foo(count:Integer;Memory:TBytes);stdcall;external...                    


und in anderen Sprachen dann eben z.b. als:

C#-Quelltext
1:
function foo(int Count,PByte Memory);                    


BenBE - Mi 06.01.10 14:36

Abhängig davon, in welche Richtung die Daten ausgetauscht werden müssen, wäre auch die häufig in der WinAPI anzutreffende Variante mit der Übergabe eines Puffers (und ggf. Offset) auf den Datenbereich überlegenswert.

Alternativ bietet es sich, je nach Anzahl der Strukturen) auch an, einfach den Memory-Manager an die fremde Anwendung\das Plugin herauszureichen (Record TMemoryManager in System.pas IIRC) und so dem Plugin dynamisch zu erlauben, sich den nötigen Speicher zu holen.


Martok - Mi 06.01.10 15:51

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Abhängig davon, in welche Richtung die Daten ausgetauscht werden müssen, wäre auch die häufig in der WinAPI anzutreffende Variante mit der Übergabe eines Puffers (und ggf. Offset) auf den Datenbereich überlegenswert.

In jede Richtung, das ist ja das Problem. Mal muss das Plugin Daten loswerden, mal kriegt es welche geliefert. Ersteres ginge so wie user profile iconFlamefire vorschlägt, aber das Problem ist grade die andere Richtung. Und die Tatsache, dass ich mir gerne das doppelt umkopieren sparen würde, sonst könnte ja einfach der Caller das Ding wieder freigeben.

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Alternativ bietet es sich, je nach Anzahl der Strukturen) auch an, einfach den Memory-Manager an die fremde Anwendung\das Plugin herauszureichen (Record TMemoryManager in System.pas IIRC) und so dem Plugin dynamisch zu erlauben, sich den nötigen Speicher zu holen.

Funktioniert das vernünftig? Dann wäre das durchaus denkbar.

Dann kann ich aber gleich ein ordentlich referenzgezähltes IDynArray bauen.


BenBE - Mi 06.01.10 16:17

Die Variante mit dem Übergeben des Speichermanagers funktioniert sehr stabil und wird in vielen Plugin-Systemen von C-Programmen durchaus regelmäßig so praktiziert. Schau dir ggf. mal VirtualDub an; die machen das so und das Teil läuft SEHR stabil.

Gegenüber der Nutzung von Interfaces hat das zudem den Vorteil, dass man den Overhead, den die Verwaltung der Referenzen mit sich bringt vermeidet und das Plugin zudem intern bereits sich den Speicher aus dem Pool der Hauptanwendung holen kann und damit nicht nur Daten über das Interface problemlos verwaltet werden können, sondern auch zusätzliche Puffer, die intern im Plugin verwendet werden. Somit erspart man sich auch bei extrem verschachtelten Strukturen das Umkopieren, weil das Plugin die benötigten Strukturen ohne Umwege direkt im Speicher alloziieren kann.


Martok - Mi 06.01.10 16:53

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Die Variante mit dem Übergeben des Speichermanagers funktioniert sehr stabil und wird in vielen Plugin-Systemen von C-Programmen durchaus regelmäßig so praktiziert. Schau dir ggf. mal VirtualDub an; die machen das so und das Teil läuft SEHR stabil.

Ah stimmt, hab auch grade erstmal genau gelesen was du geschrieben hast ;) TMemoryManager sind ja nur die 3 Funktionspointer, nicht der IMemoryManager der das nochmal verpackt. Hab wohl zuviel mit Interfaces gearbeitet...

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Gegenüber der Nutzung von Interfaces hat das zudem den Vorteil, dass man den Overhead, den die Verwaltung der Referenzen mit sich bringt vermeidet

Jap, das würde sich hier lohnen. RefCounting werd ich zwar eh machen müssen, aber das kann man dann genau da machen wo es gebraucht wird.

Das klingt erstmal gut, ich meld mich dann nochmal ;)