Autor Beitrag
OlafSt
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 458
Erhaltene Danke: 90

Win7, Win81, Win10
Tokyo, VS2017
BeitragVerfasst: Mo 11.12.17 16:43 
Hallo Freunde,

ich habe hier eine alte Software, die langsam aber sicher umstrukturiert werden soll. Wir sind nun an dem Punkt "DLL" angekommen.

Alles eigentlich kein Hexenwerk, wäre da nicht die erforderliche Flexibilität. Ich weiß heute noch nicht, welcher Kunde in 12 Jahren für seine Zusatzinfos zum Auftrag welche Parameter braucht... Die erforderliche Lösung ist also eine, die für sehr lange Zeit (mehrere Jahre bis Jahrzehnte) Bestand haben wird und entsprechend gut durchdacht sein muß.

Meine Idee ist, das die DLL beim Hauptprogramm die Parameter anfordert - aber ich hab keinen Schimmer, wie ich das konkret machen sollte. Erschwerdend kommt hinzu, das die neuen DLLs allesamt mit Delphi Tokyo gebaut werden, das Hauptprogramm aber noch Delphi 5 ist (und auch eine Weile noch bleiben wird).

Ich bin für Ideen zu haben, zur Verfügung stehen INI-Files, direkte Aufrufe, Interfaces in begrenzter Form (soweit D5 sie eben unterstützt), eine SQL-Datenbank... Wie habt ihr so ein Problem gelöst ?

_________________
Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
OlafSt Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 458
Erhaltene Danke: 90

Win7, Win81, Win10
Tokyo, VS2017
BeitragVerfasst: Di 12.12.17 09:43 
Oha, da scheinen die alten Hasen aber auch überfragt :D

Meine erste Überlegung ist:

Es gibt ja im wesentlichen 3 Typen von Parametern: Integers, Floats und Strings und von jeder dieser Typen kann es Null bis viele geben. Also machen wir folgende Routinen:

ausblenden Delphi-Quelltext
1:
2:
3:
function GetIntegerParamCount: integer;
function GetFloatParamCount: integer;
function GetStringParamCount: integer;


und dazu passend:

ausblenden Delphi-Quelltext
1:
2:
3:
procedure SetIntegerParam(Index: integer; Value: integer);
procedure SetFloatParam(Index: integer; Value: double);
procedure SetStringParam(Index: integer; Value: PAnsiChar);


PAnsiChar deshalb, weil Delphi 5 keinerlei Unicode beherrscht und durchgehend noch mit ShortString arbeitet.

Über GetProcAddress kann ich dann herausfinden, das meine DLL gar keine Integers braucht, weil GetProcAddress('GetIntegerParamCount') einfach nil zurückgibt.

Blöderweise muß man dann genau wissen, welcher Parameter an welcher Stelle zu sein hat. Kundennummer und Rechnungsnummer zu verwechseln kann dann merkwürdig enden ;)

Also nicht optimal, ich hätte gern, das die DLL in de EXE nach dem Parameter fragt. Schwierig.

_________________
Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 18710
Erhaltene Danke: 1621

W10 x64 (Chrome, IE11)
Delphi 10.2 Ent, Oxygene, C# (VS 2015), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 12.12.17 09:59 
Sehr viel sinnvoller ist die Verwendung von Interfaces. Da kannst du beliebige Interfaces über eine feste Schnittstelle jagen.

Bei uns kann man in das Hostprogramm auch DLLs einklinken, die dann wiederum für andere DLLs Interfaces bereitstellen. Alles ohne an der Schnittstelle etwas zu ändern.

Das geht, weil du Interfaces ja allgemein weitergeben kannst und dann prüfen kannst, ob bestimmte Interfaces davon implementiert werden.
OlafSt Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 458
Erhaltene Danke: 90

Win7, Win81, Win10
Tokyo, VS2017
BeitragVerfasst: Di 12.12.17 10:22 
:shock: :shock: :shock:

Etwa so:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
type
  IDLLInterface = interface {GUID}
    function GetIntegerParam(const ParamName: PAnsiChar): Integer;
  end;


In der DLL:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure SetParamInterface(TheInterface: IDLLInterface);
begin
  {...}
end;

exports
  SetParamInterface;


In der EXE:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
{...}
   DI:=TDLLInterface.Create;
   SPI:=GetprocAddress('SetParamInterface');
   if Assigned(SPI) then
     SPI(DI);


Nun kann die DLL über dieses Interface Variablen aus der EXE herausfragen, wie es ihr beliebt.

Habe ich das so einigermaßen richtig konstruiert ?

_________________
Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 18710
Erhaltene Danke: 1621

W10 x64 (Chrome, IE11)
Delphi 10.2 Ent, Oxygene, C# (VS 2015), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 12.12.17 10:46 
Ja, prinzipiell schon, nur dass wir das ganze noch deutlich weiter getrieben haben. Bei uns gibt es eine Standardschnittstelle. Wenn eine DLL eingeklinkt wird, bekommt diese analog zu deinem Code ein Interface. Dieses Interface enthält eine Funktion, mit der beliebige Interfaces angefragt werden können. Dafür wird die GUID des Interfaces übergeben, dazu ein Parameter-Interface und als Rückgabewert bekommt man ein IInterface.

In der DLL gibt es nun eine Klasse, die eine Klassenfunktion Get<T> anbietet, der man das gewünschte Interface als generischen Typ sowie den Parameter gibt. Diese extrahiert nun die GUID, fragt damit die Hostanwendung an und konvertiert das zurückgegebene Interface in den Typ T. Auf diese Weise kann man nach außen hin komplett typsicher arbeiten.

Natürlich steckt da noch viel mehr Code dahinter, z.B. verschiedene Ebenen, saubere Shutdown-Funktionen und auch eine Rückrichtung usw., aber das ist das Grundprinzip.

Als Pseudo-Beispiel in der DLL:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
var
  User: IUser;
begin
  if THostApp.Get<IUser>(1, User) then
    ShowMessage(Format('Benutzer 1 heißt %s', [User.Name]));
OlafSt Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 458
Erhaltene Danke: 90

Win7, Win81, Win10
Tokyo, VS2017
BeitragVerfasst: Mi 20.12.17 23:25 
Ich habe das nun so implementiert, wie ich es schon angeführt hatte. Das ganze ist ja eine Einbahnstraße (Daten an DLL, nix zurück - jedenfalls noch nicht):

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
type
    ITokyoDLLInterface = interface
      [GUID]
      function GetIntegerParam(const ParamName: PAnsiChar): integer;
      function GetFloatParam(const ParamName: PAnsiChar): double;
      function GetStringParam(const ParamName: PAnsiChar): PAnsiChar;
    end;


Für Delphi 5 ist in der endgültigen Implementierung noch etwas Brimborium nötig, weil es noch kein TInterfacedObject kennt, man also das Reference Counting selbst machen muß:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
type
    {$IFDEF VER130}
    TTokyoDLLInterface = class(TObject, IUnknown, ITokyoDLLInterface)
    {$ELSE}
    TTokyoDLLInterface = class(TInterfacedObject, ITokyoDLLInterface)
    {$ENDIF}
    private
    protected
    public
      {$IFDEF VER130}
      //Manual Reference counting
      function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
      function _AddRef: Integer; stdcall;
      function _Release: Integer; stdcall;
      {$ENDIF}
      function GetIntegerParam(const ParamName: PAnsiChar): integer;
      function GetFloatParam(const ParamName: PAnsiChar): double;
      function GetStringParam(const ParamName: PAnsiChar): PAnsiChar;
    end;


PAnsiChar muß ich verwenden, weil D5 noch keine LongStrings kennt (nur die mit 255 Zeichen und Längenbyte in Byte 0). Auch die Generics hätte ich gern verwendet :wink:

Die D5-Anwendung füllt beim Start schon ein paar Parameter in seine internen Listen ein, die ohnehin von faktisch jeder DLL benötigt werden. Situationsbedingt werden dann weitere Parameter hinzugefügt. Ich habe das ganze so programmiert, das ein bereits bestehender Parameter dann überschrieben wird. Alles in allem bin ich mit der Lösung sehr zufrieden und wenn es mal einen Weg aus der DLL zurück in die Anwendung geben muß, kann man das gleiche Prinzip ja erneut verwenden.

Vielen Dank für die Anregungen ! Problem solved.

_________________
Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
TiGü
Hält's aus hier
Beiträge: 9
Erhaltene Danke: 1



BeitragVerfasst: Do 21.12.17 11:05 
Kennt Delphi5 schon den Typ Widestring?
Das macht dir das Leben gewiss leichter.
Ansonsten bist du irgendwann an dem Punkt, an dem du völlig unklare Fehler aufspüren musst. Pointergeraffel ist schnell die Quelle allen Übels.

Das ist die Delphi-Kapselung für einen BSTR (msdn.microsoft.com/d...21069(v=vs.85).aspx).
Dadurch kannst du auch problemlos DLLs in anderen COM-kompatiblen Sprachen (C++, C#) erstellen.

docwiki.embarcadero...._(Delphi)#WideString
docwiki.embarcadero....de/System.WideString
OlafSt Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 458
Erhaltene Danke: 90

Win7, Win81, Win10
Tokyo, VS2017
BeitragVerfasst: Mi 27.12.17 13:00 
Daran gedacht hatte ich schon, nur bisher keine Zeit, das mal zu versuchen.

D5 kennt den Typ WideString bereits (muß wohl auch, wenn man COM unterstützt). Ich habe das alles auf WideString ungestellt, was dann einiges an StrPas-Aufrufen in der D5-Implementation überflüssig machte.

Gefällt mir nun deutlich besser, danke fürs erinnern :idea:

_________________
Lies, was da steht. Denk dann drüber nach. Dann erst fragen.