Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Interface und liste


_frank_ - Mi 08.08.07 12:49
Titel: Interface und liste
Moin,
ich bastel grade mit interfaces rum, funktioniert soweit auch schon ganz gut...
jetzt brauche ich die möglichkeit eine variable Liste dieser Plugin-Interfaces anzulegen mit entsprechenden Zusatzdaten.
Ich hab schon ne Weile rumgesucht, aber so richtig weiter bin ich nicht gekommen. es gab einen Thread wo auf TInterfacelist verwiesen wurde, nur leider gibts das nicht bei mir (D3). auch glaube ich nicht, das mithilfe dieser zusatzdaten (wie das dll-handle etc.) mit gespeichert werden können.
Somit bleibt mir wohl nur die Selbstimplementierung...

mein Versuch:


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:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
  TPluginRec=record
    dllName,pName:string;
    hDll:integer;
    iPlg:IPlugin;
  end;
  PPluginRec=^TPluginRec;
  TPluginList=class
    FList:TList;
    public
      constructor Create;
      destructor Destroy;override;
      procedure AddPlugin(dllName:string;hDll:integer;const Plugin:IPlugin);

      function GetPlugin(index:integer):TPluginRec;
      function count:integer;
      procedure Delete(index:integer);
  end;

constructor TPluginList.Create;
begin
  inherited;
  FList:=TList.create;
end;

destructor TPluginList.Destroy;
var i:integer;
begin
  for i:=FList.count-1 downto 0 do
    delete(i);
  FList.free;
  inherited;
end;

procedure TPluginList.AddPlugin(dllName:string;hDll:integer;const Plugin:IPlugin);
var pr:PPluginRec;
begin
  new(pr);
  pr^.iPlg:=Plugin;
  pr^.hDll:=hdll;
  pr^.dllName:=dllname;
  FList.Add(pr);
end;

function TPluginList.count:integer;
begin
  result:=FList.count;
end;

function TPluginList.GetPlugin(index:integer):TPluginRec;
begin
  result:=PPluginRec(Flist.Items[index])^;
end;

procedure TPluginList.Delete(index:integer);
var pr:PPluginrec;
begin
  pr:=PPluginRec(FList.Items[index]);
  pr.iPlg:=nil//plugininterface freigeben
  freelibrary(pr.hDll); //dll freigeben
  dispose(PPluginRec(FList.Items[index])); //speicher des records freigeben
end;

procedure TForm1.FormCreate(Sender: TObject);
type
  TProcInitPlg = function (Handle:Integer): IPlugin; stdcall;
var
  fProc: TProcInitPlg;
begin
  pList:=TPluginList.create;
  // Application-Interface laden
  app := TApp.Create(
    TMemoWrapper.Create(Memo1),
    TMenuWrapper.Create(MainMenu1),
    TToolbarWrapper.Create(Toolbar1)
  );
  // Bibliothek laden und Interface holen
  hDll := LoadLibrary(PCHAR(extractfilepath(paramstr(0)) + 'project2.dll'));
  @fProc := GetProcAddress(hDLL, 'InitPlugin');
  if @fProc <> nil then
  begin
    //iPlg := fProc(hDll);
    pList.AddPlugin('project2',hdll,fProc(hDll));
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
    pr:TPluginrec;
begin
  for i:=0 to pList.count-1 do
  begin
    pr:=pList.GetPlugin(i);
    pr.iplg.Execute(app);
  end;
  // Interface verwenden
//  if Assigned(iPlg) then
//    iplg.Execute(app);
end;


das ganze funktioniert zwar, aber ich weis nicht, ob alles korrekt freigegeben wird, da in der Delete-Methode (vom destructor aufgerufen) nur die erste Zeile ausgeführt wird, danach ist das Programm zuende. rufe ich die Delete-Methode manuell auf, werden alle zeilen ausgeführt.
hab das auch mit d7 laufen lassen, da bekomme ich bei der zweiten Zeile eine Zugriffsverletzung. :gruebel: beim manuellen delete geht er alle zeilen durch und danach poppt das CPU-Fenster auf (worin ich aber nicht wirklich was erkennen kann...).

Gruß Frank


HeikoDD - Fr 10.08.07 15:45

TInterfaceList ist ein Abkömmling von TInterfacedObject und stellt das Interface IIInterfaceList zur Verfügung. Intern verwendet das Objekt ein TThreadList-Objekt um eine Liste von IUnknow Interfaces zu verwalten. Dabei Castet es jedes Item von IUnknown zu Pointer und zurück. Was ein Interface intern auch ist, ein Pointer. (Naja eigentlich ist es ein Recordpointer mit einer speziellen Struktur, aber es ist einfach nur ein Pointer)

Gut so, das?


_frank_ - Fr 10.08.07 16:39

ich komme also nicht drumherum, eine TinterfaceList zu implementieren?
keine möglichkeit, das ohne separates Interface-Objekt zu realisieren?

wenn ich dich richtig verstanden habe, sähe die implementation so in etwa aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
  TInterfaceList = class(TInterfacedObject, IInterfaceList)
  private
    FList: TThreadList;
  public
    //methoden von IInterfaceList
  end;


leider kenn D3 auch IInterfacelist nicht (war ja klar..)
würde das gerne mithilfe einer "normalen verwaltung" á la TList+Record/Class realisieren.
nur bisher macht mir da wohl der referenzzähler nen Strich durch die rechnung...vielleicht hab ich nur irgendwo etwas vergessen (wie das const beim Funktionsaufruf bei den Anfangsversuchen :) ).

gibt es eine möglichkeit den referenzzähler einzusehen?

Gruß Frank


HeikoDD - Fr 10.08.07 22:02

ole ... Du weist das TList nur als Vorfahre genutzt werden sollte, weil es noch Methoden gibt die überschrieben werden müssen? (siehe Delete)

ich würde das eher so lösen:


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:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
type iPlugIn = interface(IUnknown)
['{1089AB37-AAB1-4D5B-A2BB-1C406ED68A5B}']
       function getDLLName:wideString;
       function getPlugName:wideString;
       function getHandle:tHandle;
       function getSource:iPlugin;

       property DLLName : WideString read getDLLName;
       property PlugName : WideString read getPlugName;
       property Handle: tHandle read getHandle;
       property Source: iPlugin read getSource;
     end;


type tPluginInfo = class(tInterfacedObject, iPluginInfo)
       protected
         fDLLName: WideString;
         fPlugName: WideString;
         fHandle: tHandle;
         fPlug:IPlugin;

         function getDLLName:wideString;
         function getPlugName:wideString;
         function getHandle:tHandle;
         function getSource:iPlugin;

       public
         constructor Create(Const DLLName, PlugName: WideString; Handle:tHandle; Plugin:IPlugin);
         destructor Destroy; override;
     end;

function CreatePlugInfo(DLLName, PlugName: WideString; Handle:tHandle; Plugin:IPlugin):IPlugInfo;
begin
  result := tPlugInfo.Create(DLLName, PlugName, Handle, Plugin);
end;

constructor tPluginInfo.Create(Const DLLName, PlugName: WideString; Handle:tHandle; Plugin:IPlugin); override;
begin
  inherited;
  fDLLName := DLLName;
  fPlugName := PlugName;
  fHandle := Handle;
  fPlug := Plugin;
end;

destructor tPluginInfo.Destroy;
begin
  try
    fPlug := nil//wichtig wegen der referenz
  finally
    inherited;
  end;
end;

function tPluginInfo.getDLLName:wideString;
begin result := fDLLName; end;

function tPluginInfo.getPlugName:wideString;
begin result := fPlugName; end;

function tPluginInfo.getHandle:tHandle;
begin result := fHandle; end;

function getSource:iPlugin;
begin result := fPlug; end;


jetzt kannst Du über "CreatePlugInfo" bequem einen Eintrag erzeugen, ihn in einer TInterfaceList verwalten und fertch ist es ... ich weiß jetzt aber net ob D3 überhaupt so eine Klasse kennt ... dumm das. :-)

darf ich vorstellen IInterface

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:
  IInterfaceList = interface
  ['{285DEA8A-B865-11D1-AAA7-00C04FB17A72}']
    function Get(Index: Integer): IUnknown;
    function GetCapacity: Integer;
    function GetCount: Integer;
    procedure Put(Index: Integer; Item: IUnknown);
    procedure SetCapacity(NewCapacity: Integer);
    procedure SetCount(NewCount: Integer);

    procedure Clear;
    procedure Delete(Index: Integer);
    procedure Exchange(Index1, Index2: Integer);
    function First: IUnknown;
    function IndexOf(Item: IUnknown): Integer;
    function Add(Item: IUnknown): Integer;
    procedure Insert(Index: Integer; Item: IUnknown);
    function Last: IUnknown;
    function Remove(Item: IUnknown): Integer;
    procedure Lock;
    procedure Unlock;
    property Capacity: Integer read GetCapacity write SetCapacity;
    property Count: Integer read GetCount write SetCount;
    property Items[Index: Integer]: IUnknown read Get write Put; default;
  end;


hilft das weiter ...


_frank_ - Fr 10.08.07 22:17

jetzt wo ich genauer drüber nachdenke, hast du recht ;) gerade was das delete betrifft...
dürfte aber imho nichts mit dem fehler zu tun haben...

wie ich oben schon geschrieben habe, gibt es TInterfaceList nicht in D3.
habe bisschen mit _AddRef und _Release rumexperimentiert. dabei ist mir aufgefallen, dass zum zeitpunkt, wo ich addref aufrufe dann 2 Instanzen existieren (einmal vom Addref und einmal von der aufrufenden Methode, wo das interface von der init-Procedure zurückgegeben wird [dieses wird sicher beim beenden der proc gekillt]).
ich weis leider keine möglichkeit den referenzzähler ohne addref oder release auszulesen...gibts da was?
wenn ich die nil-Zuweisung im delete weglasse, kommt erst eine exception beim dispose (dll freigeben funktioniert wohl, jedoch wird eine showmessage danach nicht angezeigt).

kann man irgendwie überprüfen, ob eine dll/Object freigegeben wurde?

Gruß Frank


HeikoDD - Mo 13.08.07 15:56
Titel: RE
Ach so: FINGER AUS DER REFERENZIERUNG.

Jedes Objekt das von TInterfacedObject abgeleitet wurde gibt auch den Referenzzähler als Eigenschaft aus. (Property RefCount)

Weise ich ein Interface einer anderen Variable oder Funktion zu, wird dessen Counter erhöht um anzuzeigen das das Interface verwendet wird. Kehrt das Interface zurück oder auf NIL gesetzt, wird der Counter erniedrigt und damit angezeigt das es nicht mehr benötigt wird. Fällt der Counter dabei auf 0 wird automatisch das zugrundeliegende Objekt gelöscht bzw. es wird angezeigt das der Speicher freigegeben werden kann.

Ich hoffe die kleine Source hilft Dir Dein Problem zu lösen:

IPluginInfo ist ein Interface was die Schnittstelle zum Plugin bildet,
aber auch die Verwaltung übernimmt.

TPlugins ist ein sehr einfaches Beispiel für eine Möglichkeit der Verwaltung.


_frank_ - Mo 13.08.07 23:32

leider bringt deine Demo bei mir auch eine Zugriffsverletzung beim freigeben der liste (wie bei mir) :(
mir wurde schon von einigen namhaften Programmiern von Interfaces abgeraten oder ich sollte die auf COM-Art laden (RegSvr32 bzw. registerServer-methode). Dies will ich aber vermeiden, da dies zu sehr ins System eingreift...

Trotzdem Danke

naja, ich hab das mit den interfaces so ziemlich aufgegeben. Werde mein Plugininterface wohl auf simple Funktions-Pointer aufbauen, die das Plugin von der Haupt-Anwendung abfragen kann.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
function GetFunctionPointer(ID:Integer):Pointer;
begin
  case ID of
    Func_AddToToolbar:result:=@AddToToolbar;
    ...
  end;
end;


der Zeiger auf diese allwissende Funktion wird dem Plugin beim Init mitgegeben, welche sich den Pointer natürlich merkt. hab da schon bisschen rumprobiert, ist relativ übersichtlich und einfacher zu handhaben. in der hauptanwendung brauch ich dann eigentlich nur das dllHandle zu halten (bräuchte ich noch nichtmal ne separate klasse oder ein record, werde aber schon eine listenklasse machen, was die plugins verwaltet, läd, entläd und infos abfragt).
Der rest lässt sich dann von der dll abfragen (version,sonstige infos, dllName (GetModuleName) ).
Somit lässt sich das auch ohne größeren Aufwand erweitern, ist zwar kein OOP aber ist nicht so viel Overhead und somit auch schneller implementiert. Sollte auch stabiler laufen, naja, werd ich sehen :)
bei den Pointern kann das Plugin zwar nur zwischen nil und funktionspointer unterscheiden und muss das "Casting" selbst übernehmen, aber es sollte ja wissen, was es eine Zeile weiter oben angefordert hat :)

Von daher sollte man halt nur vermeiden, die funktionen nachträglich zu ändern (parameter, resulttyp), wenn man sie im pluginsystem integriert hat, den MM von delphi außen vor lassen (PChar statt strings, keine klassen, etc) und per stdcall hantieren.

Leider hab ich noch keinen Weg gefunden, um Methoden zu übergeben (@Memo1.Clear funktioniert natürlich so nicht)...vielleicht gibts diesbezüglich noch tricks.

Diese Ausführung nur für diejenigen, die sich auch überlegen, ein einfaches aber mächtiges Pluginsystem zu erstellen.

Gruß Frank


HeikoDD - Di 14.08.07 10:38

Hier ist die Falle:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure TPlugins.Add(Const Name:String);
var p : iPLuginInfo;
begin
  p := LoadPlugin(Name);
  p._AddRef;
  fItems.add(Pointer(p));
end;


Die Falle liegt beim Casten von Interface zu Pointer. Der Compiler ruft _Release auf, weil er davon ausgeht das die Instanz nicht mehr benötigt wird. Das ist ein Sonderfall wo man in die Refernzierung direkt eingreifen muss. Da hier der Compiler einfach Blind ist.

Und Dein Programmierer konnte Dir das nicht verraten?


_frank_ - Di 14.08.07 11:37

hab ihm deinen code nicht gegeben, hab mir den nur angeschaut.
hatte aber in meinem source schon mit _Addref/_Release rumgespielt, ohne erfolg.

werde das aber noch testen, rein aus interesse :)

Gruß Frank