Jump to content
LonelyPixel

C# Sample Plugin

Recommended Posts

erwin
Laut Beschreibung holt sich der CCW für seine QueryInterface-Methode die nötigen Informationen aus den .NET-Metadaten, die ja anders als bei unverwaltetem Code vorliegen. So kann man zur Laufzeit ermitteln, von welchen Schnittstellen ein Objekt ableitet und welche GUIDs dabei angegeben wurden.

Letzteres will wohl glauben. Aber wie sollen denn solche Informationen wie:

 

wenn die QueryInterface() des IDVBViewerPlugin-Interfaces für IOSDPlugin aufgerufen wird gib DIESE Interfacereferenz zurück

 

wenn die QueryInterface() des IAnderesInterface-Interfaces für IOSDPlugin aufgerufen wird gib JENE Interfacereferenz zurück

 

wenn die QueryInterface() des INochAnderesInterface-Interfaces für IOSDPlugin aufgerufen wird gib NOT_IMPLEMENTED zurück

 

usw. usf.

 

in die Metadaten hineinkommen? Wo erfolgt denn die Verknüpfung von DIES, JENES, NOT_IMPLEMENTED mit den Metadaten. Das kannst du doch nur allein wissen das im ersten Fall DIES zuständig ist und im zweiten JENES.

 

erwin

Share this post


Link to post
LonelyPixel

Wieder: Soweit ich das verstanden habe...

 

[Guid("7888D3B9-B02F-4ABE-924C-929A9F520775")]
interface IDVBViewerPlugin;

[Guid("E453A119-7E6F-41D9-B482-A50AA210C01F")]
interface IOSDPlugin : IDVBViewerPlugin;

[Guid("45658682-468F-4484-A275-0A8FB12300B3")]
interface IPlugin : IDVBViewerPlugin;

class BaseOSDSetupPlugin : IDVBViewerPlugin, IOSDPlugin, IPlugin;

 

BaseOSDSetupPlugin.QueryInterface(IID_IOSDPlugin) muss zur Laufzeit nachschauen, ob BaseOSDSetupPlugin eine Schnittstelle implementiert, die die angegebene GUID hat. Ist das der Fall, wird ein Objekt der entsprechenden Schnittstelle zusammengesetzt. Ist es das nicht, wird E_NOINTERFACE zurückgegeben. Ob QueryInterface(IID_IOSDPlugin) nun auf einer Instanz von A, B oder C aufgerufen wird, ist völlig egal. Solange diese Instanz auch IOSDPlugin implementiert, kann es konvertiert werden. So wird das in COM wohl funktionieren. Die QueryInterface-Methode ist auch so gesehen nicht in den ISonstwas-Schnittstellen enthalten, sondern wird immer aus IUnknown vererbt. Alle COM-Schnittstellen sind von IUnknown abgeleitet und jedes Mal sind die drei Methoden QueryInterface, AddRef und Release (in dieser Reihenfolge, definiert in "Unknwn.h" im Platform SDK) am Anfang der konkreten Schnittstelle. In der MSDN wird auch vom Wechseln zwischen den Schnittstellen gesprochen ("Use IUnknown methods to switch between interfaces on an object, add references, and release objects."). Da man mittels Reflection rausfinden kann, wie die Klassenstuktur ist und welche Attribute wo angegeben sind, sehe ich da kein Problem drin. Es muss halt nur implementiert sein und .NET macht das mit seinem CCW.

 

Ich steh nur grade vor dem Problem, dass ich eine kompatible Schnittstelle in C++ definieren will und er sagt mir, dass ich keine abstrakte Klasse instantiieren könnte. Als ob ich das wollte... C++ ist nicht ganz meine größte Stärke.

Share this post


Link to post
erwin
...und er sagt mir, dass ich keine abstrakte Klasse instantiieren könnte. Als ob ich das wollte... C++ ist nicht ganz meine größte Stärke.

Wenn Du nicht weiter kommst, zeig mal ein wenig code.

 

BaseOSDSetupPlugin.QueryInterface(IID_IOSDPlugin) muss zur Laufzeit nachschauen, ob BaseOSDSetupPlugin eine Schnittstelle implementiert, die die angegebene GUID hat.

Nein!

BaseOSDSetupPlugin.QueryInterface(IID_IOSDPlugin) muss zur Laufzeit nachschauen, ob es eine Implementation der Schnittstelle IID_IOSDPlugin KENNT und diese dann zurückliefern.

Es muss diese Schnittstelle gar nicht selber implementieren.

Vielleicht eine Konfusion in der Begrifflichkeit: COM-Objekt ist nicht gleich C# Objekt. Das COM-Objekt kann aus mehreren C# (C++) Objekten bestehen, die jeweils ein bestimmtes Interface implementieren. Die Verwebung zu einem COM-Objekt stellen dann eben diese QueryInterface()-Methoden bereit. Man kann also in einem COM-Objekt mittels QueryInterface von Interface zu Interface springen ("Use IUnknown methods to switch between interfaces on an object, add references, and release objects."). In der Realisierung wechselt man dann aber von einer C#-Objektinstanz zu einer anderen. BTW, es ist falsch zu sagen (bei der obigen Definition) BaseOSDSetupPlugin implementiere das IOSDPlugin-Interface. Tut es nicht, es implementiert ein Interface welches sich aus den drei angegeben zusammensetzt. Und dies ist ein neues Interface. Würde BaseOSDSetupPlugin.QueryInterface(IID_IOSDPlugin) eine Referenz auf sich selbst zurückgeben wäre dies falsch!

 

Vielleicht kommt hier auch die Verwirrung her was der CCW leistet. Die Beispiele die ich gefunden habe waren immer der Machart: ein C#-Objekt - ein COM-Objekt. Und hier kann dann CCW tatsächlich eine Referenz auf sich selbst zurückgeben. Das kann man also leicht automatisieren.

 

Hier ist der Fall aber

Der DVBViewer erhält das "IDVBViewerPlugin" Interface vom Plugin und kann über QueryInterface zum Beispiel das "iOSDPlugin" Interface des Plugins holen.

 

IDVBViewerPlugin.QueryInterface(IID_IOSDPlugin)

 

erwin

Share this post


Link to post
LonelyPixel

So, jetzt hab ich was in C++ zum Testen fertig gebracht.

 

MfcTest1.zip

 

Es ist ein MFC-Programm, das beim Starten die C#-DLL lädt und sich über InitPlugin2 eine IDVBViewerPlugin-Instanz holt. Darauf wird dann die Methode Name() aufgerufen, die einen BSTR zurückgibt. Den kann man dann im Debugger lesen. (Ich habe leider keine funktionierende Übersetzung von BSTR in LPSTR gefunden, sodass man einen Debugger braucht, um den Erfolg des Tests zu prüfen.)

 

Ich habe meine C#-DLL dafür nur an der Stelle angepasst, dass der Rückgabewert von InitPlugin2 nicht als IUnknown gemarshalt wird, sondern die Angabe völlig entfernt. Ansonsten gab es irgendwelche Probleme und der Debugger hat beim InitPlugin2-Aufruf angehalten.

 

Ich hab nur grade keinen DVBViewer zur Hand, um diese Änderung nochmal auszuprobieren. Aber ihr könnt euch ja schonmal das C++-Programm zu Gemüte führen. Es enthält übrigens auch vollständig ausformulierte Schnittstellendefinitionen für die ersten 3 Schnittstellen. So in der Art müsste der Rest dann wohl auch aussehen, nehm ich an (@erwin).

 

Im Code sind auch auskommentierte frühere Versuche enthalten, z.B. von InitPlugin2 ein IUnknown abzurufen und davon mit QueryInterface ein IDVBViewerPlugin abzurufen, das hat aber nicht so richtig geklappt. Ob die ganze uuid-Schreibweise überhaupt stimmt, weiß ich nicht, die hab ich aus MSDN und SDK-Header-Dateien zusammengeklaut und so lange dran gedreht, bis der Compiler es aufgefressen hat.

Share this post


Link to post
LonelyPixel
BTW, es ist falsch zu sagen (bei der obigen Definition) BaseOSDSetupPlugin implementiere das IOSDPlugin-Interface. Tut es nicht, es implementiert ein Interface welches sich aus den drei angegeben zusammensetzt. Und dies ist ein neues Interface.

Was ist dann das hier in Delphi?!

 

TBaseOSDPlugin = class(TBaseDVBViewerPlugin, iOSDPlugin);
TBaseOSDSetupPlugin = class(TBaseOSDPlugin, iPlugin);
...

 

Kann mir mal jemand erklären, was da eigentlich passiert?

Share this post


Link to post
erwin
Was ist dann das hier in Delphi?!

 

TBaseOSDPlugin = class(TBaseDVBViewerPlugin, iOSDPlugin);
TBaseOSDSetupPlugin = class(TBaseOSDPlugin, iPlugin);
...

 

Kann mir mal jemand erklären, was da eigentlich passiert?

Sorry habe das nicht explizit erwähnt. Ich habe aus der Sicht von COM-Interfaces (nicht einer konkreten Programmiersprache wo die Begrifflichkeit anders ist) argumentiert. Also aus der Sicht wie die entsprechenden Methodentabellen im Hauptspeicher auszusehen haben.

 

erwin

Share this post


Link to post
erwin
Es enthält übrigens auch vollständig ausformulierte Schnittstellendefinitionen für die ersten 3 Schnittstellen. So in der Art müsste der Rest dann wohl auch aussehen, nehm ich an (@erwin).

In C++ kannst Du ableiten (für C# meinen ja einige es ginge nicht)

 

class __declspec(uuid("7E0D7CB9-BD1B-4779-9503-BB6E402ADFD4")) ISystem : IUnknown
{

// ISystem
virtual HRESULT __stdcall CloseAllTimers();
virtual HRESULT __stdcall NewOSDItem(void *obj);
virtual HRESULT __stdcall NewOSDItemList(void *obj);
};

 

ansonsten "Well done"

 

 

erwin

Share this post


Link to post
LonelyPixel

Ja schon, nur anhand der Delphi-Definition und ebenso meiner C#-Definition der Klassen würde ich mal behaupten, dass hier Klassen mehrere Schnittstellen implementieren. Für eine Indirektionsschicht wie den CCW wäre es ja auch kein Problem, für eine Objektinstanz mehrere verschiedene Abbildungen (in SQL würde man Views dazu sagen) zu erzeugen, die dann ein entsprechendes Binärlayout haben. Mit QueryInterface könnte man dann verschiedene Schnittstellen eines Objekts abrufen. Genauso muss es doch auch mit IUnknown sein, dem object des COM. Wenn man ein IUnknown bekommt, kann man ja nichts damit anfangen. Wenn man es aber in ein IPlugin oder sonstwas "castet" (hier: QueryInterface anwendet), wird es zu etwas anderem und man kann dessen Methoden aufrufen.

 

Außerdem: "QueryInterface muss nur eine Implementierung kennen, nicht selbst eine sein" - Wo kommt so eine andere Implementierung dann her? Ich habe nur ein Objekt und möchte jetzt davon eine andere Schnittstelle sehen. Habe ich danach plötzlich 2 Objekte?

Share this post


Link to post
LonelyPixel
In C++ kannst Du ableiten (für C# meinen ja einige es ginge nicht)

Das hab ich jetzt mal absichtlich vermieden, um eine explizite Struktur dastehen zu haben.

 

Übrigens bringt C++ bereits eine IUnknown-Definition mit, weshalb meine mit _ anfängt. Vielleicht könnte man auch die nehmen.

Edited by LonelyPixel

Share this post


Link to post
erwin
Außerdem: "QueryInterface muss nur eine Implementierung kennen, nicht selbst eine sein" - Wo kommt so eine andere Implementierung dann her? Ich habe nur ein Objekt und möchte jetzt davon eine andere Schnittstelle sehen. Habe ich danach plötzlich 2 Objekte?

Genau an diesem Problem hänge ich z.Z. bei meinem C++ Port, jedenfalls in dieser 1zu1 Umsetzung aus Delphi. Hatte ja schon gesagt das da dann eine Infrastrukturmethode GetInterface() zum Zuge kommt. Und ich nehme an dass diese dann das macht was du als

für eine Objektinstanz mehrere verschiedene Abbildungen (in SQL würde man Views dazu sagen) zu erzeugen,

beschrieben hast, also für ein (Delphi) Objekt welches mehrere Interfaces (im Delphi-Sinne) implementiert, die aufgedrösellten Interface-Instanzen (im COM Sinne) zurück gibt. Für C++ gibt es dass nicht. Ich muss dann wohl wie du vermutest wirklich separate IOSDPlugin Objektinstanzen anlegen. Du kannst noch auf den CCW hoffen vielleicht gibt da ja was in der Art.

 

erwin

Share this post


Link to post
LonelyPixel

Ich habe mein Beispiel jetzt insgesamt aktualisiert, den C#-Teil und den C++-Teil. Die C#-Definitionen konnte ich nach ersten Tests etwas vereinfachen, außerdem habe ich im TestPlugin neben IDVBViewerPlugin.Name auch IPlugin.Apply implementiert, um das Abrufen mehrerer Schnittstellen zu testen. Im C++-Teil habe ich alten Experimentiercode entfernt, die vordefinierte IUnknown-Schnittstelle verwendet und Schnittstellenableitung hinzugefügt. Das Beispiel demonstriert jetzt den Aufruf von LibTyp und von InitPlugin2 nacheinander. Den BSTR kann man einfach in einen CString-Konstruktor werfen, der konvertiert das dann implizit in einen geeigneten LPSTR zurück... Basiert alles ein bisschen auf MFC, ohne das müsste man ein paar Sachen zusätzlich definieren.

 

Der QueryInterface-Aufruf liefert aber leider immer E_NOINTERFACE zurück, egal für welche Schnittstelle. Da bin ich noch am Schauen, wie man das zum Laufen bekommt.

 

Also hier die aktuelle Version, C#-DLL und C++-Testprogramm:

plugin.zip

 

Ich hab übrigens noch eine interessante Seite zu COM vs. .NET gefunden. Sie stellt die Verbesserungen der CLR gegenüber COM dar. Gleich das Codebeispiel L2 (in zwei Teile unterteilt) zeigt eindrucksvoll den Aufwand, den man in C++ und C# mit der Fehlererkennung hat. C# ist da deutlich übersichtlicher und lenkt nicht jede zweite Zeile mit nebensächlichem Kram ab. Die Fehlerbehandlung ist in beiden Versionen noch nicht vorhanden. Nur so am Rande... :)

 

(Edit: Link zur Seite kopiert, aber nicht eingefügt...)

(Edit2: Attachment aktualisiert)

Edited by LonelyPixel

Share this post


Link to post
LonelyPixel

QueryInterface liefert immer E_NOINTERFACE zurück, außer für IUnknown, da funktioniert der Aufruf. Also auch sich selbst scheint das COM-Objekt nicht zu kennen.

 

Wenn ich die Rückgabe von InitPlugin2 von vornherein nicht als IDVBViewerPlugin sondern als IPlugin betrachte, knallt es mit einer Zugriffsverletzung beim Aufruf einer Methode von IPlugin. Das Abrufen aus der DLL geht noch. Das Objekt scheint also per Vorgabe eine Abbildung der im C#-Code erstgenannten Schnittstelle zu sein.

 

Aufgrund dieser Seite gehe ich nach wie vor davon aus, dass ein CoObject durch QueryInterface mehrere verschiedene Schnittstellen anbieten kann. Das verhält sich in etwa so, als hätte ich ein C#- oder Java-Objekt, das mehrere Schnittstellen implementiert, und ich caste das jeweils in eine der Schnittstellen. (Genauso wird in C# auch mit COM-Objekten verfahren, statt QueryInterface aufzurufen.) Die QueryInterface-Implementierungen, die ich gesehen habe, erzeugen ein neues Objekt der gewünschten Schnittstelle (mit switch/case und cast), rufen AddRef auf und geben das neue Objekt zurück. Dieser Vorgang wird wohl vom C-Compiler unterstützt und könnte vielleicht so aussehen, dass nur die relevanten Funktionszeiger in eine neue vtable kopiert werden. Das Objekt bleibt ja dasselbe, nur die Abbildung ändert sich. (Hm, nur wo sind bei einem Objekt eigentlich die Instanzdaten gespeichert?)

 

Der uuid-Kram, den ich in C++ geschrieben habe, scheint schon so zu stimmen. Den verwenden andere auch und provozierte Fehler klingen plausibel. Wenn ich die IIDs manuell definiere und QueryInterface übergebe, passiert genau das gleiche.

 

Trotzdem stehe ich mit meinem COM-Entwurf momentan wieder mal vor einem großen Felsen, auf den mich vermutlich nur andere COM-Experten raufziehen können. Mein letzter Thread im MSDN-Forum war wohl zu kompliziert, ich lerne gerade schneller, als die antworten können...

Share this post


Link to post
JMS

Ok, das mit dem Ableiten ziehe ich zurück, scheint doch zu gehen. Muss ich mal suchen, wo ich da ein Problem hatte (irgendwo im DirectShow). Egal: SORRY!!!

 

Dafür als Goody: ergänzt man ein ComImportAttribute auf den Schnittstellen, so läuft zumindest der Test durch (i.e. QueryInterface geht, Aufrufe auch an IPlugIn kommen sauber an):

...
[Guid( "7888D3B9-B02F-4ABE-924C-929A9F520775" )]
[InterfaceType( ComInterfaceType.InterfaceIsIUnknown )]
[ComImport]
public interface IDVBViewerPlugin
...

 

Debuggen mit der Technologie wird extrem lästig. Da das DllExport Tool die Assembly verändert, passt die PDB nicht mehr dazu. Evtl. sollte man über einen anderen Ansatz nachdenken.

 

Jochen

Share this post


Link to post
LonelyPixel

Aha. Reicht das ComImport an jeder Basisschnittstelle, oder muss es auch z. B. bei IOSDPlugin : IDVBViewerPlugin angegeben werden?

 

Man kann dem ilasm auch den Parameter /debug mitgeben. Dann erstellt der auch wieder eine PDB-Datei. Die Frage ist nur, ob die noch was bringt. Noch eine Frage ist allerdings, wie man eine verwaltete DLL in einer unverwalteten Anwendung überhaupt debuggen will.

Share this post


Link to post
JMS

Ich hab's vorsichtshalber mal bei allen gemacht.

Share this post


Link to post
LonelyPixel

Ich hab mir mittlerweile eine weitere Testumgebung eingerichtet, die eine einfache (von DVBViewer unabhängige) COM-Klasse und einen ähnlichen MFC-Client enthält. Daran kann ich das freier ausprobieren.

 

Das ComImportAttribute scheint bei jeder Schnittstelle benötigt zu werden, da sie sonst nicht durch QueryInterface verfügbar ist. Das Abrufen einer Schnittstelle geht jetzt also, aber was dabei rauskommt, führt wahlweise zu kaputtem Stack oder Access Violation.

Share this post


Link to post
LonelyPixel

Verdammt. Jetzt hab ich alles soweit zusammen, dass es läuft. COM-Objekte aus der DLL abrufen, Methoden auf den COM-Objekten aufrufen, Casten in Unterklassen und dort auch wieder Methoden aufrufen, perfekt.

 

Aaaber: Das ganze läuft nur dann, wenn ich auf C#-Seite keine Schnittstellen ableite. Wenn ich jede Schnittstelle wie eine Basisschnittstelle definiere und alle Methoden vollständig und der Reihe nach angebe. Und wenn ich (damit das mit den Unterschnittstellen funktioniert) in beiden Sprachen voneinander abweichende Funktionssignaturen angebe (was eigentlich falsch ist). Sobald ich in C# mit abgeleiteten Schnittstellen anfange, bekomme ich bei jedem Methodenaufruf auf dem Objekt der abgeleiteten Schnittstelle Stackfehler. Auf C++-Seite habe ich weiterhin abgeleitete Schnittstellen, da geht es wohl. Jemand ne Idee? :)

Share this post


Link to post
JMS

Ich kann Dir nur sagen, was ich dabei eben gelernt habe: wenn eine COM Schnittstelle NICHT abgeleitet ist, MUSS ein InterfaceTypeAttribute gesetzt werden - ok, wenn man nicht zufällig den Default trifft. Wenn sie abgeleitet ist, DARF AUF KEINEN FALL ein InterfaceTypeAttribute gesetzt werden. Das macht dann wohl den CCW kaputt - eigentlich mit etwas Nachdenken logisch.

 

Aber das war bei Dir alles sauber.

 

Jochen

Share this post


Link to post
LonelyPixel

Keine besonders guten Nachrichten, aber auch keine allzu schlechten. Die schlechte zuerst:

 

http://social.msdn.microsoft.com/Forums/en...ce-1bbc81d79e07

Letzter Absatz der Antwort:

A final word of warning about COM and .NET. The first COM-visible interface exposed by a COM-visible type is used as the default COM interface. You can change that through an attribute. While COM and .NET support interface inheritance it is not supported through the CCW. Therefore if you are trying to implement a COM interface defined in unmanaged code that uses inheritance then you'll have to include all inherited members in your .NET version of the COM interface.

Das mit dem eleganten Ableiten von Schnittstellen können wir also wohl vergessen. Jochen, da hattest du anscheinend doch recht. Schade.

 

Jetzt aber die gute Nachricht (glaub ich):

 

Die Schnittstellen dürfen durchaus voneinander ableiten und man kann sie in C# dann auch in etwa so verwenden. Das macht dem CCW nichts aus, er interessiert sich halt einfach nicht dafür. (Ziemlich blöd, dass die das einfach vergessen haben. So schwer wäre das doch wirklich nicht gewesen.) Auch die Implementierung mehrerer voneinander unabhängiger Schnittstellen für eine Klasse funktioniert und ich kann auf COM-Client-Seite eine Schnittstelle abrufen und sie mit QueryInterface in die andere (nicht typverwandte) Schnittstelle casten. Der CCW kriegt also durchaus mit, dass ein Objekt die andere Schnittstelle auch implementiert. Alle Methodenaufrufe haben dabei erwartungsgemäß, ohne Stackfehler und Zugriffsverletzungen funktioniert. Es gelten folgende Bedingungen:

 

* Alle Methoden der übergeordneten Schnittstelle in C# müssen am Anfang der Schnittstelle reinkopiert werden. Dadurch wird ein passendes Binärlayout erzeugt. Das ist der Punkt, den der CCW eigentlich erledigen sollte.

* Da die Schnittstellen abgeleitet sind und nun gleiche Methodennamen enthalten, sollten die überschriebenen Methoden mit "new" definiert werden. Das ändert nichts, verhindert aber die Warnung, die einem genau das nahelegt. Die Modifizierer virtual und override, die man alternativ bei Klassen verwenden kann, sind bei Schnittstellen nicht zulässig. (Wozu auch.)

* Da jetzt alle Schnittstellen "Basisschnittstellen" sind, muss für alle das InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown) angegeben werden. Das ist aber nicht weiter tragisch, dann ist es wenigstens überall einheitlich.

 

Im Endeffekt werden die Schnittstellendefinitionen in C# dadurch etwas bis erheblich unübersichtlicher, je nachdem, wie intensiv das mit der Ableitung getrieben wurde. Sollte der CCW das irgendwann mal richtig unterstützen, kann man die Definitionen wieder vereinfachen.

 

So, jetzt wo wir eigentlich alle glücklich sind... da verschwindet DVBViewer immer noch einfach so nach dem Aufruf von InitPlugin2. Vielleicht liegt es dismal ja einfach nur an der noch unvollständigen Definition der Schnittstellen in C#? Oder daran, dass eine der Methoden nicht das zurückgibt, was von ihr erwartet wird?

Share this post


Link to post
JMS

Wenn es um die Schnittstellen der Anwendung geht wäre es vermutlich sicherer, aus der idl eine TLB zu machen (oder sich die besorgen) und die InterOps dann automatisch erzeugen zu lassen. Dann ist zumindest die Seite raus aus dem Problem.

 

Jochen

Share this post


Link to post
JMS
* Da jetzt alle Schnittstellen "Basisschnittstellen" sind, muss für alle das InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown) angegeben werden. Das ist aber nicht weiter tragisch, dann ist es wenigstens überall einheitlich.

Falls da auch Schnittstellen aus der eben erwähnten IDL dabei sind, die scheinen alle IDispatch oder Dual zu sein. Wenn es allerdings direkt nach dem InitPlugin2 abstürzt hat es vermutlich eher mit den implementierten Schnittstellen zu tun. Vielleicht fehlt ja irgend eine... Müßte man mit einem funktionierenden PlugIn vergleichen.

 

Jochen

Share this post


Link to post
LonelyPixel

Die IDL enthält nur das, was DVBViewer sowieso schon exportiert. Die sind IDispatch, ja, weil sie ja aus anderen Prozessen funktionieren müssen. Die ganzen internen Schnittstellen (die nur IUnknown sind) sind nur in den Pascal-Quellen des MyPrograms-Plugins enthalten. Und um den Vorgang etwas zu vereinfachen, hab ich mir grade ein Tool zusammengestöpselt, das die Methodenliste einer Pascal-Schnittstelle nimmt und sie in C# übersetzt, unter Berücksichtigung von Typ- und Signaturanpassungen.

 

Welche Schnittstellen das, was InitPlugin2 zurückliefert, implementieren muss, habe ich aus dem MyPrograms-Plugin (das jetzt die einzige Referenz zur DVBViewer-Plugin-Entwicklung ist) übernommen. Die sind zwar vollständig definiert, sie enthalten aber 1. Methoden, die noch undefinierte Schnittstellen als Parameter annehmen oder zurückgeben sollen und 2. sind natürlich nicht alle Methoden implementiert. Ich muss im MyPrograms-Plugin noch nachschauen, was implementiert werden muss oder wie ein Standard-Rückgabewert aussehen würde. Da mach ich mich morgen mal dran.

 

Bis dahin ist hier mal der Interface-Übersetzer. Vielleicht noch nicht fehlerfrei (hat nur 90 Minuten gedauert), aber man muss sowieso über alles drüberschauen. Wie das mit den Properties läuft, weiß ich noch gar nicht.

http://unclassified.de/tmp/DVBViewer/InterfaceConverter.exe

 

Und hier noch der aktuelle Stand des DvbTestPlugin:

http://unclassified.de/tmp/DVBViewer/DvbTestPlugin.7z

Ich habe noch ein paar Schnittstellen hinzugefügt, aber DVBViewer verschwindet immer noch beim Laden.

Share this post


Link to post
JMS

Ich würde an Deiner Stelle die Ableitungen mal ganz entfernen - überall. Es kann sein, dass ein C# Cast dann nicht direkt geht (e.g. IOSDPlugin p; (IDVBViewerPlugin)p), aber das ist erst einmal irrelevant, da alle Klasse in dem Szenarien eh alle Schnittstellen explizit unterstützen werden (e.g. statt nur IOSDPlugin zu implementieren, impletierst Du so wie so IOSDPlugin, IDVBViewerPlugin).

 

...

[GuidAttribute("E453A119-7E6F-41D9-B482-A50AA210C01F")]

[interfaceType(ComInterfaceType.InterfaceIsIUnknown)]

[ComImport]

public interface IOSDPlugin : IDVBViewerPlugin

{

#region IDVBViewerPlugin inherited

new void Init();

[PreserveSig]

new string Name();

new void SetApplication(ISystem OSD);

new void Terminate();

#endregion IDVBViewerPlugin inherited

...

Share this post


Link to post
LonelyPixel
Ich würde an Deiner Stelle die Ableitungen mal ganz entfernen - überall.

Und das bringt dann - was?

 

Ich hab mir mit dem Interface Converter überlegt, ich könnte den doch eigentlich auch grad noch so erweitern, dass er nicht nur Methodenlisten übersetzt, sondern ganze Schnittstellen aus dem Pascal-Datei erkennt und die mitsamt der GUID übersetzt. Dann könnte er nämlich auch die abgeleiteten Schnittstellen gleich mit einfügen. Und an den Stellen, wo ich die Benennung blöd finde, könnte ich die Parameternamen einmalig ändern, bevor sie dann überall hinkopiert werden. Dann könnte man da einfach die ganze Pascal-Datei reinwerfen und es kommt eine C#-Datei raus. Ist auch bei Aktualisierungen einfacher. Und die Zeit, die dieses Programm braucht, dürfte am Ende sogar weniger sein, als alles von Hand zu machen. Konzentrationsfehler noch nicht berücksichtigt. (Und ob die C#-Interfaces dann ableiten sollen oder nicht wäre dann nur noch eine Programmoption.)

Share this post


Link to post
JMS

Weil das Binärlayout der Schnittstellen so nicht stimmt. Deine abgeleitet Klasse hat folgende Methoden nach dem ComImport:

 

IUnkown (von .NET automatisch erzeugt)

0: QueryInterface

1: AddRef

2: Release

 

IDVBViewerPlugin (Basisklasse)

3: Init

4: Name

5: SetApplication

6: Terminate

 

IOSDPlugin (Deine new Methoden)

7: Init

8: Name

9: SetApplication

10: Terminate

 

IOSDPlugin (was eigentlich da hin gehört)

11: OnClick

12: OnDeInitControls

13: OnDoesRender

...

 

Wenn nun DVBViewer beispielsweise auf IOSDPlugIn OnDoesRender aufrufen würde, so würde es nach SEINER binären Definition die Methode mit der Nummer 9 aufrufen (3 für IUnknown, 4 für die Basisschnittstelle dann die dritte Methode relativ). Damit landet es aber bei DIR in SetApplication - mit nicht nur der falschen Anzahl von Parametern (Stack Corruption), sondern auch mit semantisch falschen Objekten (Access Violation).

 

Beantwortet das Deine Frage?

 

Jochen

Edited by JMS

Share this post


Link to post
LonelyPixel

Wenn das so stimmen würde, könnte ich ja auch gleich darauf verzichten, die Methoden mit reinzukopieren. Die CCW-Darstellung sieht aber so aus:

 

IUnkown (von .NET automatisch erzeugt)

0: QueryInterface

1: AddRef

2: Release

 

IDVBViewerPlugin (Basisklasse)

3: Init

4: Name

5: SetApplication

6: Terminate

 

IOSDPlugin (Deine new Methoden)

7: Init

8: Name

9: SetApplication

10: Terminate

 

IOSDPlugin (was eigentlich da hin gehört)

11: OnClick

12: OnDeInitControls

13: OnDoesRender

...

Share this post


Link to post
JMS

Wie kommst Du darauf / wie verifizierst Du das? Du darfst das semantisch .NET / C# Layout nicht mit dem Algorithmus verwechseln, den .NET zur Erzeugung des CCW auf Basis der Schnittstelle, der Basisschnittstelle und dem InterfaceTypeAttribute erzeugt.

 

Jochen

 

<Zusatz>Du hast Recht, es scheint für den CCW keinen Unterschied zu machen. Sorry.</Zusatz>

Edited by JMS

Share this post


Link to post
LonelyPixel

So, diesmal war ich beim Debuggen ein kleines bisschen erfolgreicher. Aber nicht viel. Ich habe am DvbTestPlugin noch ein paar Stabilisierungen durchgeführt (Code entfernt, der möglicherweise Probleme verursachen kann) und die komplette Klasse mit dem verglichen, was MyPrograms tut, um ggf. fehlende Rückgabewerte hinzuzufügen.

 

Sobald die Meldung "InitPlugin2 called" erscheint, übergebe ich DVBViewer an meinen Rechtsanwa.. - äh Debugger. (Bei Visual Studio: Debugmodule manuell auswählen, "verwaltet" ist dann angekreuzt.) Der zeigt dank pdb-Datei auch den IL-Code der DLL an. (Zum ursprünglichen C#-Code gibt es keine Verbindung mehr, aber besser CIL als ASM.) Die Funktion InitPlugin2 wird verlassen. Dann geht's durch ein paar System-DLLs zurück und schließlich wieder ins Modul DVBViewer.exe. Da dauert es nicht mehr lange und es wird eine Zugriffsverletzung gemeldet. Mangels Debug-Symbolen fällt mir die Verfolgung der Aufrufe da schwer. Für die DVBViewer-Entwickler sollte es aber sehr leicht sein, rauszufinden, wobei diese Zugriffsverletzung ausgelöst wird. Vielleicht bekommen wir so raus, ob irgendeine Schnittstelle oder ein Rückgabewert nicht stimmt.

 

Edit: DvbTestPlugin an der gewohnten Stelle

Edited by LonelyPixel

Share this post


Link to post
JMS

Du könntest natürlich einmal den Debugger im Mixed Mode (Managed & Native) anwerfen und die Win32 Exception Access Violation aktivieren. Wenn dann der Fehler im Native Mode ausgelöst wird und die fehlerhafte Methode irgendwas wie CALL [edx + yyy] sagt, kann man evtl. versuchen zu raten, was denn der Viewer tut, was nicht geht.

 

Kannst Du denn soweit ordentlich debuggen, dass Du mal auf alle Methoden des PlugIns einen Breakpoint setzt und zumindest siehst, ob der Viewer vorher nicht doch noch irgendeine Metode aufruft, die vielleicht was falsches meldet?

 

Jochen

Share this post


Link to post
LonelyPixel

Ich weiß nicht sicher, wie du das meinst, glaube aber, den Debugger bereits in diesem Mixed Mode laufen zu haben, sonst könnte ich ja nicht den IL-Code debuggen. Ich habe jetzt mal die Win32 Access Violation aktiviert, was jedoch dazu geführt hat, dass beim Fortsetzen des Programms der Debugger in einen tiefen Schlaf verfallen ist, aus dem ihn nur noch der Tod befreien konnte.

 

In den anderen DLL-Funktionen waren bereits überall MessageBox-Aufrufe, da passiert aber nichts.

 

Die Stelle, an der die Ausnahme ausgelöst wird, kann ich nicht sehen, da mir der Debugger in dem Moment weder den Call Stack noch eine sinnvolle aktuelle Position mit ASM-Code anzeigt.

 

Ich bin wirklich der Meinung, dass es mit dem Delphi-Debugger (ich hoffe sowas gibt's) wesentlich einfacher wäre, die genaue Fehlerursache rauszufinden. Ich stocher hier nur im Dunkeln rum, das ist ne ziemlich ineffiziente Arbeit.

 

Ob Debug-Symbole für den DVBViewer Release Build nützlich wären, weiß ich allerdings nicht, da das Programm ja mit einem Laufzeitpacker (UPX) komprimiert ist. Ich kann es zwar auspacken und starten, aber ob da wieder dasselbe rauskommt...

Share this post


Link to post
JMS

Und wenn Du nur Native Debugging machst?

 

Jochen

 

<Zusatz>Ich habe leider nur den DVBViewerGE, nicht die Vollversion. Der scheint InitPlugin2 nicht zu verwenden, darum kann ich es nicht selbst prüfen.</Zusatz>

Edited by JMS

Share this post


Link to post
JMS

Noch eine letzte Idee: Du könntest die beiden PlugIn Klassen (ja, auch die abstrakte Basisklasse) mit dem ComVisble(true) Attribute versehen. Ich glaube zwar nach den Vorabtests nicht, dass der CCW das braucht, aber eigentlich ist das Pflicht (da in der AssemblyInfo.cs korrekterweise ComVisble(false) als Default verwendet wird).

 

Jochen

Share this post


Link to post
LonelyPixel

ComVisible(true) bringt auch nichts.

 

Okay, mit der Disassembler-Ansicht das hat wohl nicht gestimmt. Ich seh schon, wo der Fehler auftritt. Aber Call Stack ist nicht. Hier der Dump:

 

0012FA90  mov		 ah,0FAh 
0012FA92  adc		 al,byte ptr [eax] 
0012FA94  movs		byte ptr es:[edi],byte ptr [esi] 
0012FA95  pushfd		   <--- Hier steht der Zeiger drauf
0012FA96  ins		 dword ptr es:[edi],dx 
0012FA97  add		 byte ptr [esi],ch 
0012FA99  push		edi  
0012FA9A  outs		dx,byte ptr [esi] 
0012FA9B  add		 al,byte ptr [eax] 
0012FA9D  add		 byte ptr [esi+2],ch 
0012FAA0  call		60152F27

 

Die Fehlermeldung in Visual Studio:

Unbehandelte Ausnahme bei 0x0012fa95 in DVBViewer.exe: 0xC0000005: Zugriffsverletzung beim Schreiben an Position 0x840012f6.

Share this post


Link to post
JMS

Das der Fehler beim SCHREIBEN auftritt ist in der Tat unerwartet - da hast Du ja kaum Einfluss 'drauf.

 

Ich habe mal ein bißchen herumgespielt, kann es aber wie gesagt nicht wirklich testen. Wenn Du Lust hast UND die Visual C++ 2008 SP1 Laufzeitumgebung auf dem Rechner hast, kannst Du ja mal einen anderen Ansatz kurz antesten.

 

Lade Dir mal mein Beispiel - wie gesagt ein kleiner Teil (! das Wesentliche ist .NET !) davon ist VC++ 2008 SP1, daher kann es sein, das es voll kracht. Kannst ja erst mal Dein MFCTest1.exe dagegen laufen lassen.

 

Das wäre ins PlugIn Verzeichnis vom Viewer zu kopieren. Dann schlage ich folgendes Vorgehen vor:

 

1. Starte Visual Studio

2. File => Open => Project und dann die EXE vom DVBViewer auswählen!

3. Im Solution Explorer die Debug Properties auf Managed Only setzen!

4. Debugging normal starten (F5)

 

Mein Test schreibt so grob ins Output Fenster, was er tut (nur der .NET Teil, wenn ich die Exports verbockt habe - Pech gehabt). Ich vermute mal, dass es genauso abstürzt, denn eigentlich sieht alles, was Du gemacht hast, soweit in Ordnung aus. Aber wer weiß - 5 Minuten Aufwand lohnt immer.

 

Viel Glück

 

Jochen

Share this post


Link to post
JMS

Hmm 0012FA90 seht aber nicht wie eine DLL aus, könnte IL Code sein. Wie sieht denn der Stack Trace dazu aus? Wenn irgendwo ein Aufruf aus der DVBViewer.EXE kommt, was macht DER Stack Frame denn als letztes?

 

Jochen

Share this post


Link to post
LonelyPixel

Lauf Reflector fehlt deinem Beispiel noch die Datei JMS.DVBViewer.PlugInBase.dll. Und so richtig habe ich nicht verstanden, was ich mit dem Teil machen soll. Ich hab hier grad nur VS2005 installiert, da gäb's vermutlich sowieso Probleme.

 

Wie gesagt, Call Stack ist keiner verfügbar. Die Liste ist leer, es steht nur eine Adresse ohne Modulname drin. Ich warte noch auf eine Aussage der DVBViewer-Entwickler.

Share this post


Link to post
JMS
Lauf Reflector fehlt deinem Beispiel noch die Datei JMS.DVBViewer.PlugInBase.dll.

Ja, aber der lügt in diesem Fall. Die benötigten Assemblies sind als Win32 Ressourcen integriert und werden bei Bedarf automatisch geladen.

 

Und so richtig habe ich nicht verstanden, was ich mit dem Teil machen soll.

Wie beschrieben: als PlugIn kopieren und die Schritt 1. - 4. zum Debuggen ausführen und das Output Fenster beobachten. Ich kann das halt nicht. Kannst auch gerne mal in Deinem MFCTest1 Testprogramm diese DLL laden und schauen, ob sie korrekt auf die dort verwendeten Tests reagiert (Output Fenster enthält nur Daten, wenn Managed Debuggung an ist).

 

Ich hab hier grad nur VS2005 installiert, da gäb's vermutlich sowieso Probleme.

In der Tat - wenn Du die VC++ 2008 SP1 Runtime nicht zufällig aus anderem Grunde installiert hast (%SystemRoot%\winsxs\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.21022.8_none_bcb86ed6ac711f91 oder statt 21022.8 eine spätere Version mit anderen Guids, wichtig ist das VC90.CRT - 21022.8 wird von dem Code referenziert).

 

Ich warte noch auf eine Aussage der DVBViewer-Entwickler.

Ok, verstehe ich.

 

Jochen

Edited by JMS

Share this post


Link to post
LonelyPixel

Kennst du DbgView (oder DebugView) von Microsoft, früher SysInternals? Das macht auch nichts anderes, als die Ausgabe im Debugger anzuzeigen, die letztlich mit der WinAPI-Funktion OutputDebugString ausgegeben wird. Reicht das vielleicht schon aus?

Share this post


Link to post
JMS

Ich verstehe die Frage nicht. Du hast doch Visual Studio 2005, wozu brauchst Du denn noch einen anderen Debugger oder ein Logging Tool? Reicht für fast alles aus (außer mixed Debugging unter x64, aber mit 2010 wird es das ja auch geben, wie es heute schon der WinDbg von Microsoft aus dem WDK kann). Abgesehen davon korrigiere ich mich: ich habe .NET Trace (mit TRACE Conditional) verwendet, das sollte in der Tat OutputDebugString verwenden (wenn nicht anders konfiguriert). Aber das ist ja alles nicht der Punkt sondern die VC2008SP1 Runtime, oder?

 

Jochen

Share this post


Link to post
erwin
Okay, mit der Disassembler-Ansicht das hat wohl nicht gestimmt. Ich seh schon, wo der Fehler auftritt. Aber Call Stack ist nicht. Hier der Dump:

 

0012FA90  mov		 ah,0FAh 
0012FA92  adc		 al,byte ptr [eax] 
0012FA94  movs		byte ptr es:[edi],byte ptr [esi] 
0012FA95  pushfd		   <--- Hier steht der Zeiger drauf
0012FA96  ins		 dword ptr es:[edi],dx 
0012FA97  add		 byte ptr [esi],ch 
0012FA99  push		edi  
0012FA9A  outs		dx,byte ptr [esi] 
0012FA9B  add		 al,byte ptr [eax] 
0012FA9D  add		 byte ptr [esi+2],ch 
0012FAA0  call		60152F27

 

Die Fehlermeldung in Visual Studio:

Ich hab mal InitPlugin2 debugged und zwar in folgender Konfiguration: In reinem C++ hab ich InitPlugin2() exportiert. Da InitPlugin2() ein IDVBViewerPlugin zurückliefern muss, hab ich eine IDVBViewerPlugin-Proxy Implementation bereitgestellt die die notwendigen Methoden:

 

AddRef()

Release()

QueryInterface()

Init()

Name()

SetApplication()

Terminate()

 

,aber keine weiteren, implementiert. Diese haben nur logging-Funktionalität. Diese werden allerdings nie aufgerufen. Stattdessen genau was LonelyPixel auch hat (also deine C#-Architektur ist erstmal nicht für diesen Fehler verantwortlich). Nach InitPlugin2() Aufruf der Absturz an genau derselben Stelle wie oben. Der Stackpointer steht scheinbar an einer völlig sinnlosen Stelle und dann wird versucht mit pushfd darauf zu schreiben.

 

@Lars_MQ: Frage: ruft der DVBV vor QueryInterface() wirklich keine andere als die obigen Methoden auf. Kann es wirklich nicht sein das die Deklaration

 

function InitPlugin2: IDVBViewerPlugin; stdcall;

 

nicht 100% korrekt ist. Die Delphi-Samples funktionieren dann evt. deshalb weil sie auch von anderen Interfaces ableiten und somit evt. gerufenen Methoden die von den obigen abweichen trotzdem implemtiert haben.

 

erwin

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...