Jump to content
LonelyPixel

C# Sample Plugin

Recommended Posts

LonelyPixel

Anbei befindet sich der erste Entwurf eines Beispiel-Plugins für DVBViewer, das ich anhand der verfügbaren Informationen geschrieben habe. Es ist noch lange nicht vollständig, es fehlen z.B. fast alle OSD-Schnittstellen, ich habe nur mal die ersten übersetzt. Aber zumindest den Ladeprozess kann ich damit schonmal mitverfolgen.

 

Ich habe alles in C# geschrieben. Dank umfangreicher Interop-Funktionalitäten ist es anscheinend kein großes Problem, eine verwaltete DLL zu schreiben, die aus unverwaltetem Code ganz normal verwendet werden kann. Damit die DLL auch Funktionen exportiert, habe ich mich eines IL-Hacks bedient, den man an ein paar wenigen Stellen im Web beschrieben findet. Dabei geht es darum, die DLL in CIL-Code zu disassemblieren, die Exporttabelle von Hand hinzuzufügen und den Code wieder in eine DLL zu assemblieren. Diesen Prozess habe ich mir mit einem kleinen Tool (NetDllExport) automatisiert, das im Archiv enthalten ist und in den Suchpfad kopiert werden muss. Es exportiert ganz pauschal alle öffentlichen statischen Methoden der Klasse DllExport, die es findet. Die compilierte Plugin-DLL befindet sich aber auch im Archiv. Die exportierten Funktionen können mit dem VS-Tool "depends" angezeigt werden.

 

Nun, ich habe das Plugin vom Typ "Plugin" gekennzeichnet. Es exportiert alle Funktionen, die laut der SDK-Dokumentation dafür erforderlich (?) sind. Die Parametertypen sind noch nicht vollständig definiert, aber die entsprechenden Funktionen werden wohl sowieso noch nicht aufgerufen. Nur leider tut es nicht besonders viel.

 

Es wird zuerst drei Mal die Funktion LibTyp aufgerufen, dann wird drei Mal PluginName aufgerufen, dann startet DVBViewer normal weiter.

 

Fast normal, denn sobald meine DLL geladen wird, wird kein anderes Plugin mehr geladen. Nicht das X10-Input-Plugin und nicht das BlueFuzz-OSD-Plugin. Sie erscheinen nicht an den gewohnten Stellen. Eine Liste geladener Plugins habe ich nirgends gefunden, so dass ich nicht weiß, ob mein leeres Plugin wenigstens geladen wurde. Aber irgendwas scheint meine DLL am Plugin-Ladeprozess in DVBViewer zu beschädigen. DVBViewer durch den Debugger zu jagen machte mich auch nicht schlauer. Erstens habe ich keine Debugsymbole dafür (ich konnte im Assembler-Output nichtmal interessante Funktionsgrenzen erkennen) und zweitens ist am entsprechenden Haltepunkt nichtmal meine DLL in der Aufrufliste aufgetaucht... Wenn ich meine DLL wieder entferne, startet DVBViewer wie gehabt.

 

Mag sich das mal jemand anschauen? :-)

 

PS: Och nee, ich darf hier mal wieder nix anhängen. Also gut, hier ist das Archiv: http://unclassified.de/tmp/DVBViewer/DvbTestPlugin.7z

Share this post


Link to post
Tjod
Och nee, ich darf hier mal wieder nix anhängen.

Am Speicherplatz sollte es jetzt nicht mehr scheitern. Aber 7-zip Wahrscheinlichkeit nicht gehen. Am besten mal mit .zip versuchen.

Share this post


Link to post
JMS

Ich hab's länger nicht mehr geprüft, aber funktioniert diese Art von COM InterOp richtig:

 

public interface IOSDPlugin : IDVBViewerPlugin

Meine dunkele Erinnerung ist, dass .NET beim Ableitungen von COM Interfaces mit den VTables durcheinanderkommt und man besser die Methoden der Basisschnittstelle hineinkopiert und keine Ableitung verwendet. Zudem hast Du auf das InterfaceTypeAttribute verzichtet. Ich weiß jetzt nicht, was für eine Art von Interfaces (IUnknown, IDispatch, IDispatch+Dual) der DVBViewer verwendet, aber prüfe das mal gegen die Voreinstellung.

 

Eine falsche VTable könnte solch unangenehmen Effekte erklären.

 

Jochen

Share this post


Link to post
erwin

Habs mal auf die Schnelle angesehen.

 

1. Zumindest müsste vom DVBV auch SetAppHandle() aufgerufen werden, d.h. hier ist schon was kaputt.

Schau dir mal die "calling convention" genauer an. Die muss "stdcall" sein.

 

2. Verpass deiner DLL eine Versions-Ressource. Die kannst Du dann unter Help/About/Versionsinfo sehen (wenn alles gut gegangen ist)

 

3. In Deinem COM-Teil fehlt jeglicher Bezug zum IUnknown-Interface (sehe ich zumindest nicht)

 

 

erwin

Edited by erwin

Share this post


Link to post
LonelyPixel

IUnknown sollte von COM Interop automatisch hinzugefügt werden.

 

Die Calling Convention müsste auch normalerweise stdcall sein, zumindest ist das wohl COM-Vorgabe.

 

Den Rest probier ich heute abend mal aus.

Share this post


Link to post
erwin
IUnknown sollte von COM Interop automatisch hinzugefügt werden.

ALLE COM-Interfaces, also auch die du in deinem Plugin bereit stellst, müssen auch das IUnknown anbieten. Z.B

 

	[Guid("7E0D7CB9-BD1B-4779-9503-BB6E402ADFD4")]
public interface ISystem
{
	void CloseAllTimers();
	object NewOSDItem();
	object NewOSDItemList();
}

Woher soll der Compiler wissen dass hier auch noch IUnknown dazugehört? Das Schlüsselwort "interface" bedeutet meiner C#-Kenntnis nach NICHT COM-Interface. Etwas anderes wäre z.B.

 

	public interface ISystem : IUnknown

 

Auch, denke ich, müssten die Methoden "virtual" sein.

 

 

Die Calling Convention müsste auch normalerweise stdcall sein, zumindest ist das wohl COM-Vorgabe.

 

Ich dachte an sowas hier:

 

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPStr)]
	public static string LibTyp()
	{
		callCount++;
		MessageBox.Show(callCount + ": LibTyp called");
		return "Plugin";
	}

 

evt, auch

 

public delegate static string LibTyp()

 

 

erwin

Share this post


Link to post
LonelyPixel

Also, nach einem halben Tag COM Interop in der MSDN bin ich nochmal etwas schlauer. :blush:

 

"interface" in C# bedeutet nicht COM-Schnittstelle. Aber beim veröffentlichen von Schnittstellen oder anderen Dingen werden die verwalteten Typen entsprechend angepasst. Schnittstellen implementieren z. B. implizit IUnknown und IDispatch. Ob das wohl stört, wenn IDispatch zusätzlich vorhanden ist? Man kann auch mit dem InterfaceTypeAttribute angeben, ob die Schnittstelle von IUnknown, IDispatch oder beiden ("dual", Vorgabe) ableitet. Ich habe meine Schnittstellendefinitionen jetzt mal auf IUnknown eingeschränkt:

 

[interfaceType(ComInterfaceType.InterfaceIsIUnknown)]

[Guid("...")]

public interface ...

 

Mit dem GuidAttribute wird dann außerdem verhindert, dass sich .NET COM Interop irgendwas ausdenkt (oder berechnet). Soweit ich das jetzt gelesen habe, werden Schnittstellen in COM nicht durch ihren Namen, stattdessen aber u. a. durch ihre GUID (UUID) identifiziert, weshalb diese Angabe wichtig ist.

 

In der MSDN ist das alles beschrieben, wenn man die Seite zum ComVisibleAttribute aufruft und dann "Zusammenfassung: Konvertieren einer Assembly in eine Typbibliothek" folgt. Dort ist aber überall die Rede davon, Typen für COM zu veröffentlichen und im System zu registrieren, oder eine Typbibliothek (*.tlb) zu erzeugen (z. B. mit Tlbexp.exe). Das brauchen wir hier aber alles nicht, oder?

 

Da stand übrigens mit keinem Wort erwähnt, dass man Schnittstellen für COM nicht ableiten sollte. Die machen das in ihren Beispielen ja selbst so. Bislang wird der COM-Interface-Teil ja noch gar nicht ausgeführt, es scheitert noch an den einfachen DLL-Funktionen.

 

Ich habe jetzt nochmal an anderer Stelle gelesen, dass die Standard-Aufrufkonvention von .NET stdcall ist. Die Beispiele, in denen irgendwas angegeben wurde, waren nur für cdecl. Außerdem darf das UnmanagedFunctionPointer-Attribut nur auf Delegates angewendet werden. Sagt mein Compiler. Das steht auch so in der Doku und außerdem habe ich im Web nur Anwendungsbeispiele in Bezug auf verwaltete Callback-Funktionen gefunden, die aus unverwaltetem Code aufgerufen werden können sollen.

 

Einen Delegaten für eine veröffentlichte Funktion definieren bringt meines Erachtens gar nichts. Delegaten sind das, was in C Funktionstypen/Funktionspointer sind. Wenn ich also eine Funktion mit GetProcAddress aus einer DLL lade, dann brauche ich einen Funktionstyp dafür, der die Signatur der Bibliotheksfunktion beschreibt. Da meine DLL aber keine Funktionspointer entgegennimmt, braucht sie keine delegates. (Und in dem Fall würde .NET Interop auch hier weiterhelfen.)

 

@erwin: Welche Methoden müssen virtual sein?

 

Code wird nachher aktualisiert.

Den NetDllExport-Aufruf verschiebe ich dann ins Solution-Verzeichnis, dann liegt alles an einem Ort.

NetDllExport wird nachher auch VS2008 unterstützen (bislang nur VS2005).

Share this post


Link to post
LonelyPixel

Das C#-Projekt ist jetzt aktualisiert. Gleicher Link wie im ersten Beitrag.

 

Ich habe

* die Interface-Definitionen aktualisiert,

* ein paar Datentypen für die DLL-Funktionen hinzugefügt,

* die Dateien übersichtlicher strukturiert,

* viel Dokumentation eingefügt (aber viel konnte ich auch nicht finden) und

* NetDllExport aktualisiert und ins Solution-Verzeichnis eingebunden.

 

Am Ergebnis ändert das alles aber nichts. Es bleibt bei den anfangs beschriebenen 3+3 Funktionsaufrufen, und danach geht kein Plugin mehr.

 

Ich habe eine Versionsressource, das macht .NET automatisch, aber mein Plugin ist unter Info/Versionsinformationen nicht zu sehen. Interessanterweise ist dort aber das X10-Input-Plugin zu sehen, das aber aus dem Optionen-Dialog verschwunden ist. Also scheint es zumindest noch ansatzweise geladen worden zu sein... Weiß nicht. Vielleicht mag mal jemand, der dazu in der Lage ist, mein Plugin debuggen, um festzustellen, warum es nicht so behandelt wird, wie es dokumentiert ist.

Share this post


Link to post
LonelyPixel

Ich habe jetzt ein MFC-Programm zum Testen meiner DLL geschrieben. Es kann die string(void)-Funktionen und die void(int, TRebuildFunc)-Funktion SetAppHandle aufrufen. Die Funktionen in der DLL melden ordnungsgemäß ihren Aufruf per MessageBox, danach macht das MFC-Programm normal weiter. Ich hatte zuerst den Funktionspointer für SetAppHandle in C nicht als __stdcall definiert (Vorgabe ist __cdecl), dann war nach der Rückkehr aus der DLL-Funktion der Stack kaputt. Aber auch erst danach. Und Funktionen ohne Parameter waren davon nicht betroffen. Jetzt mit stdcall geht es aber problemlos. Es kommt auch tatsächlich der nullterminierte String (PChar) "Plugin" aus der LibTyp-Funktion raus. Kein Unicode, kein BSTR.

 

Da meine DLL die gewünschten Funktionen exportiert und sie sich gemäß der SDK-Dokumentation auch aus unverwaltetem Code aufrufen lassen, kann jetzt nur noch eine bislang unbekannte Inkompatibilität zwischen Pascal und nicht-Pascal-DLLs vorliegen (die mein C++-Plugin-DLL-Kollege aber auch sehen müsste), oder im DVBViewer läuft irgendwas daneben.

 

Mir gehen jetzt langsam die Möglichkeiten aus. SetAppHandle wird vom DVBViewer definitiv nicht (oder völlig falsch) aufgerufen und außerdem wird immer noch der Plugin-Ladeprozess beschädigt. Ich glaube, jetzt kann mir erstmal nur ein Pascal-Entwickler weiterhelfen, der mal versuchen kann, meine DLL in eine Delphi/Pascal-Anwendung einzubinden, um zu sehen, ob das überhaupt funktioniert. Die .NET-Seite der Interop-Schnittstelle habe ich jetzt jedenfalls gut erforscht und getestet und mit C++-Anwendungen funktioniert die DLL.

Share this post


Link to post
erwin
Man kann auch mit dem InterfaceTypeAttribute angeben, ob die Schnittstelle von IUnknown, IDispatch oder beiden ("dual", Vorgabe) ableitet. Ich habe meine Schnittstellendefinitionen jetzt mal auf IUnknown eingeschränkt:

 

[interfaceType(ComInterfaceType.InterfaceIsIUnknown)]

[Guid("...")]

public interface ...

Interessant. In Sachen C# bin ich noch mächtig am Lernen.

 

Soweit ich das jetzt gelesen habe, werden Schnittstellen in COM nicht durch ihren Namen, stattdessen aber u. a. durch ihre GUID (UUID) identifiziert, weshalb diese Angabe wichtig ist.

Korrekt. COM arbeitet mit binären Interfaces, d.h. sie sind völlig unabhängig von irgendeiner konkreten Programmierspache - und Namen haben nur in der Domäne von Programmiersprachen einen Sinn.

 

Da stand übrigens mit keinem Wort erwähnt, dass man Schnittstellen für COM nicht ableiten sollte. Die machen das in ihren Beispielen ja selbst so.

Wenn man hier den Begriff "Ableitung" nicht synonym zu "Vererbung" sieht ist alles OK. "Vererbung" ist hier nämlich wieder Programmiersprachen spezifisch. Man muss bei den Beispielen aufpassen, bei C++ (wahrscheinlich auch bei C#, Delphi...) kann der Compiler aus einer objektorientierten "Vererbungshierarchie" genau das Speicherlayout erzeugen, welches als COM-Interface agieren kann.

 

Ich habe jetzt nochmal an anderer Stelle gelesen, dass die Standard-Aufrufkonvention von .NET stdcall ist.

OK. Dein Fehlerbild deutet dennoch darauf hin, das die Aufrufkonvention nicht stimmt. Vielleicht bei der Manipulation in der IL was schief gelaufen? Nur ne Vermutung.

 

@erwin: Welche Methoden müssen virtual sein?

Versteht ein wenig C++? Hier setze ich ein COM-Interface für gewöhnlich so um:

public class IBeispiel : IUnknown
{
 public:

virtual int  Methode1( int arg ) = 0;
virtual void Methode2( ) = 0;
....
}

Dadurch dass die Methoden virtual sind legt der Compiler im Haupspeicher eine sogenannte VTABLE an, also eine Tabelle die aus Pointern zu den eigentlichen Methoden besteht. Der eigentliche Pointer auf das Objekt zeigt dann auf einen Pointer der auf diese VTABLE zeigt. Und genau so muss das COM-Interface im Hauptspeicher aussehen. Zufall oder nicht, durch diese Programmierweise mit dem "virtual" erzeuge ich genau das gewünschte Speicherlayout für COM. Ich denke mir das bei C# (wenn es dafür keine anderen Methoden gibt???) auch eine VTABLE generiert werdenn muss.

 

erwin

Share this post


Link to post
LonelyPixel
COM arbeitet mit binären Interfaces, d.h. sie sind völlig unabhängig von irgendeiner konkreten Programmierspache - und Namen haben nur in der Domäne von Programmiersprachen einen Sinn.

Auch interessant. :-) Diese Erklärung macht die COM-Sache verständlicher. Irgendwo hatte ich mal was davon gelesen, dass COM ein binäres Abbild definiert, aber das ist lange her. Eine separate (oder irgendwo eingebettete) Typbibliothek ist dann wahrscheinlich erforderlich, damit andere Anwendungen an die Namen der Schnittstellen und Member gelangen, oder? Und wenn man keine TLB hat, muss man dieselbe Datenstruktur lokal in der Client-Anwendung definieren, was ja mit den endlosen Schnittstellendefinitionen im Pascal-Code gemacht wird. Wenn ich also etwas binärkompatibles dazu im C#-Plugin (seinerseits ein Client der Hauptanwendung) definiere, oder den Interop-Marshaller etwas binärkompatibles erzeugen lasse, dann sind wir alle glücklich... Außer dem exakten Speicherabbild der Klassen sollte COM dann eigentlich keine weiteren Laufzeitkomponenten enthalten, nehme ich an, so dass es in jeder Programmiersprache, die diese Definition erlaubt, möglich ist, COM-Objekte auszutauschen. COM ist also (v. a.) ein Übergabeformat für statisch definierte Objekte. Richtig?

 

Wenn man hier den Begriff "Ableitung" nicht synonym zu "Vererbung" sieht ist alles OK. "Vererbung" ist hier nämlich wieder Programmiersprachen spezifisch. Man muss bei den Beispielen aufpassen, bei C++ (wahrscheinlich auch bei C#, Delphi...) kann der Compiler aus einer objektorientierten "Vererbungshierarchie" genau das Speicherlayout erzeugen, welches als COM-Interface agieren kann.

Siehe unten...

 

OK. Dein Fehlerbild deutet dennoch darauf hin, das die Aufrufkonvention nicht stimmt. Vielleicht bei der Manipulation in der IL was schief gelaufen? Nur ne Vermutung.

Die Prüfung der Aufrufkonvention mit dem MFC-Programm war erfolgreich. Hier der Anfang des CIL-Codes der LibTyp-Methode:

 

  .method public hidebysig static string 
	  marshal( lpstr) 
	  LibTyp() cil managed
 {
.vtentry 3 : 1
.export [3] as LibTyp

Da steht gar nichts von einer Aufrufkonvention.

 

Versteht ein wenig C++? Hier setze ich ein COM-Interface für gewöhnlich so um:

public class IBeispiel : IUnknown
{
 public:

virtual int  Methode1( int arg ) = 0;
virtual void Methode2( ) = 0;
....
}

Ah, da liegt der tote Hund: C++ kennt gar keine "Schnittstellen". Da bin ich letztens erst drüber gestolpert. Stattdessen werden pure virtual Klassen verwendet. Dank Mehrfachvererbung kann eine "echte" Klasse dann auch mehrere Schnittstellen implementieren. Andere Sprachen wie C# oder Object Pascal (ich les mich da grade ein) unterscheiden aber zwischen Schnittstellen (interface) und Klassen (class). Damit erübrigt sich der "virtual-Hack". Das Schlüsselwort virtual wird für Schnittstellenmethoden in C# nicht unterstützt, weil es keine Bedeutung hätte.

Share this post


Link to post
LonelyPixel

Habe jetzt Lazarus, die freie Delphi-Alternative gefunden und installiert. Hei, da kommen Erinnerungen an alte Delphi-Versionen von vor 15 Jahren auf... Hat sich ja überhaupt nichts verändert! :rotfl:

 

Naja, jedenfalls habe ich nun einen Button und ein Textfeld gemacht, und wenn ich auf den Button klicke, will ich den Rückgabewert der string(void)-Funktion im Textfeld haben. Aber sobald ich eine aus einer DLL (meine oder user32) eingebundene Funktion aufrufe, kann ich mir aussuchen, ob nur mein Programm beim Initialisieren, oder gleich der ganze gdb-Debugger abstürzen soll. Einzig wenn ich einen konstanten String ins Textfeld stecke, funktioniert es. Das macht ja gleich richtig Spaß mit Pascal! :blush:

 

Kommt jemand weiter als ich?

Share this post


Link to post
LonelyPixel

:blush: Ähm, mal was anderes... wir strampeln uns hier einen ab, um die COM-Schnittstellen von Hand zu übersetzen und die ganze Arbeit der DVBViewer-Entwickler in anderen Programmiersprachen und -umgebungen zu wiederholen... Wie wäre es denn, wenn diese ganzen COM-Schnittstellen einfach in die DVBViewer-Typbibliothek aufgenommen würden? Dann könnte man die einfach importieren und es würde funktionieren. So wie man jetzt schon auf den DVBViewer-COM-Server zugreifen kann.

 

Ich hab grade versucht, meine Plugin-DLL in einem einfachen VB6-Programm zu verwenden. Wenn meine DLL keinen LPStr (char*, PChar) sondern einen AnsiBStr (AnsiString) zurückgibt, kann VB6 damit umgehen und es kommt "Plugin" zurück. Der Aufruf der SetAppHandle- oder InitPlugin2-Funktion führt allerdings zum sofortigen Tod des Programms. Und beim Debuggen auch der IDE. Ich habe allerdings keine Anstalten gemacht, irgendwelche Schnittstellen in VB zu definieren, Funktionspointer gehen da eh nicht ohne viel Assembler (oder doch?). Egal, war auch nur ein Versuch, ob das in der sonst so dynamischen Sprache vielleicht irgendwie automatisch geht...

 

Die COM-Schnittstellen einfach zur veröffentlichen wäre aber viel einfacher, und gäbe noch dazu immer die aktuelle Beschreibung, ohne die Gefahr, dass sie irgendwann veraltet und es wieder kracht, weil die Schnittstelle nicht stimmt!

 

Edit: Und vielleicht wäre es sogar möglich, Plugins dann als normale COM-Objekte mit in-process-Aktivierung zu implementieren, so dass auch dieser Teil der Entwicklung etwas "standardkonformer" wäre und man keine untypisierten Zeiger auf COM-Objekte rumreichen muss? *träum*

Edited by LonelyPixel

Share this post


Link to post
LonelyPixel

Ich habe jetzt mal kurz erwins C++-Plugin ausprobiert (ohne den C#-Teil) und mit ein paar MessageBox-Aufrufen versehen. Hier wird beim Starten von DVBViewer 3x LibTyp aufgerufen, dann 8x PluginName, dann ist eine kurze Pause und dann werden SetAppHandle und weitere Funktionen aufgerufen.

 

Bei meiner reinen C#-DLL ist ja nach dem 3. Mal PluginName Schluss und DVBViewer hat sowas von der Schnauze voll, dass er gar keine Plugins lädt. Wenn ich beide DLLs kopiere (DvbTestPlugin.dll in C#, DVBViewerPlugin.dll in C++), wird die zweite auch nicht mehr geladen. Wenn ich die C#-DLL in "zDVBTestPlugin.dll" umbenenne, dann kommt sie anscheinend zum Schluss dran. Dann sehe ich die 3+3 Aufrufe der C++-DLL, dann die 3+3 Aufrufe der C#-DLL und dann nichts mehr, also auch nicht die restlichen Aufrufe der C++-DLL. Schlauer werde ich davon aber auch nicht.

 

Die Versionsinformationen im Hilfemenü sind dafür nutzlos, die zeigen manches an, was im Plugins-Verzeichnis liegt, ob geladen oder nicht, aber nicht alles.

Share this post


Link to post
erwin
Hier wird beim Starten von DVBViewer 3x LibTyp aufgerufen, dann 8x PluginName, dann ist eine kurze Pause und dann werden SetAppHandle und weitere Funktionen aufgerufen.

Wow, tatsächlich! Ist mir bisher noch nicht aufgefallen. Hatte bísher auch keine Veranlassung da nachzuforschen. Gesund sieht das nicht aus.

 

erwin

Share this post


Link to post
LonelyPixel

So, Lazarus (das freie Pascal) kann zwar WinAPI-DLLs und auch die C++-DLL mit .NET-Nutzung (MessageBox) aufrufen, aber sobald ich auch nur versuche, die C#-DLL einzubinden (sie muss gar nicht aufgerufen werden), gibt's nen SegFault an einer beliebigen Stelle. Muss wohl an Lazarus liegen. Interessiert mich erstmal nicht weiter. DVBViewer stürzt ja auch nicht ab und kann die C#-DLL sogar aufrufen.

 

InnoSetup enthält ebenfalls die Möglichkeit, Pascal-Code zu verwenden, um z. B. das Setup zu beeinflussen. Hier kann ich die C#-DLL problemlos einbinden und int(void) sowie char*(void)-Funktionen mehrfach (mind. 3+4 Mal) korrekt aufrufen. Auch in VB6 funktionieren die DLL-Funktionen, sowie in C++/MFC. (Testfälle kann ich zur Verfügung stellen.) An der DLL kann es also kaum noch liegen.

 

Falls sich hier kein DVBViewer-Entwickler melden sollte, werd ich den Fall ins Bug Reports-Forum rüberschieben, denn es liegt ja anscheinend ein Fehler in DVBViewer vor.

 

Wenn das dann geklärt ist, können wir uns wieder dem COM-Kram widmen...

Share this post


Link to post
Tjod
Falls sich hier kein DVBViewer-Entwickler melden sollte, werd ich den Fall ins Bug Reports-Forum rüberschieben, denn es liegt ja anscheinend ein Fehler in DVBViewer vor.

Ich würde Tippen das macht keinen unterschied. Ich würde sogar vermuten das hier eher mit gelesen wird als bei Fehlermeldungen DVBViewer Pro/GE den da sind 90% der Beiträge Konfigurierens Probleme.

 

Aber es kann immer auch etwas dauern bis jemand Zeit hat sich damit intensiver zu beschäftigen. Lars ist glaube ich immer noch mit den Custom Renderern Zugang (hier) und was Christian z.Z. macht weiß ich nicht (wie fast immer).

 

Wenn es allgemein um die Plugin Schnittstelle geht könntest du es auch noch mit dem DVBViewer GE Testen. Die ganz grundlegenden Sachen sollten da auch gehen.

Share this post


Link to post
LonelyPixel

Ich habe jetzt meine aktuelle Version auch nach diesem Verfahren mit dem mitgelieferten Tool geändert und mal das CIL-Ergebnis verglichen. Abgesehen davon, dass die Reihenfolge der Typdefinitionen komplett neu ausgewürfelt wurde, kann ich an den entscheidenden Stellen keinen Unterschied finden. Wundert mich, dass das auf einmal gehen soll. Ich kann's jetzt grad nicht testen, aber das werd ich heut abend sicher tun.

Share this post


Link to post
erwin
Wundert mich, dass das auf einmal gehen soll. Ich kann's jetzt grad nicht testen, aber das werd ich heut abend sicher tun.

 

Ich hab mal deine Quellen etwas modifiziert (nicht durchgängig).

Nicht durchgängig heisst:

		[return: MarshalAs(UnmanagedType.IUnknown)]
	public static IDVBViewerPlugin InitPlugin2()
			...

 

war dabei nicht mit dem [DllExport] Attribut versehen. Da ich bei InitPlugin2() so eine dunkle Ahnung hatte, habe ich es doch noch mal mit diesem Attribut versucht ... und erhalte das alte Fehlerbild. Womit wir wieder beim COM Kram sind.

 

erwin

Share this post


Link to post
LonelyPixel

Okay, das ist anscheinend erstmal der Unterschied. Wenn ich die Funktion InitPlugin2 in meiner C#-DLL nicht exportiere, dann läuft der Rest auch normal durch. DVBViewer scheint nach den 3+3 Aufrufen also InitPlugin2 aufzurufen und ist bislang daran gestolpert; das Problem war nicht bei den anderen DLL-Funktionen (die ich ja ausführlich getestet habe) zu suchen.

 

So, dann bliebe nur die Frage, was beim Aufruf von InitPlugin2 passiert. Anscheinend schlägt ja bereits der Aufruf fehl, denn meine MessageBox gleich zu Beginn der Funktion wird ja schon gar nicht angezeigt.

 

Wenn ich die Signatur der Funktion so ändere, dass sie einfach nur einen Pointer zurückgibt und den auch noch auf Null setze, läuft das Plugin zwar durch. Die geänderte Funktion InitPlugin2 macht also einen Unterschied. Aber so richtig aufgerufen wird sie trotzdem nicht. Weder meine MessageBox noch der Piepton des PC-Lautsprechers werden ausgegeben. Auch die Rückgabe eines garantiert ungültigen Pointers ändert daran nichts.

 

Ah, jetzt wird's wieder interessant: Wenn ich die Rückgabe von InitPlugin2 (eine Instanz der Pluginklasse) als UnmanagedType.Interface ("COM-Interface, die GUID wird aus den Metadaten ermittelt; für object wird ein IUnknown zurückgegeben") statt UnmanagedType.IUnknown zurückgebe, wird die Funktion zwar aufgerufen und die MessageBox erscheint, aber danach verschwindet DVBViewer sofort ohne jede Spur! Jedes Mal.

Share this post


Link to post
Lars_MQ

Wahrscheinlich, weil er versucht die Funktionen des Interface aufzurufen und dabei was schief läuft. Der erste Funktionsaufruf dürfte _Addref sein, damit die referenzzählung des dem Interface zugrunde liegenden Objekts aktualisiert wird.

Share this post


Link to post
JMS

Wie genau ist eigentlich IDVBViewerPlugin definiert - gibt es ein funktionierendes (!) C++ PlugIn, in dem diese Stelle erfolgreich verwendet wird und kann man die Deklaration mal hier posten? Was in den Schnittstellendefinitionen vom letzten Post von erwin auffällt ist, dass die Methoden der Schnittstellen alphabetisch sortiert sind. Die Reihenfolge ist aber von elementarer Bedeutung. Ferner fehlt ein InterfaceTypeAttribute und eine falsche Zuordnung wäre tödlich (IUnknown ==> erste aufgeführte Methode ist in der binären Repräsentation an Position 4; IDispatch ==> an 8). Und ich bin immer noch ziemlich sicher, dass die .NET / C# Ableitung von Schnittstellen für COM InterOp nicht richtig funktioniert.

 

Ich würde gerne was Konkretes dazu sagen, dazu bräuchte ich aber (am besten) die Type Library oder halt eine laufende C++ Variante.

 

Jochen

Share this post


Link to post
Lars_MQ
Und vielleicht wäre es sogar möglich, Plugins dann als normale COM-Objekte mit in-process-Aktivierung zu implementieren, so dass auch dieser Teil der Entwicklung etwas "standardkonformer" wäre und man keine untypisierten Zeiger auf COM-Objekte rumreichen muss? *träum*

Theoretisch ja. Die TLB muss nur unterscheiden zwischen IDispatch (Klassisch COM) für die verwaltung von plugins und IUnknown (lightweight COM) mit der eigentlichen funktionalität.

 

Das ganze bedarf nur eines Plugins im DVBViewer, das die Verwaltung der COM Plugins und die Weiterverteilung der DVBViewer Befehle (init, terminate etc) vornimmt.

Im Prinzip ist ja jedes Unterplugin im endeffekt unabhängig, sobald es das notwendigen Interface zum IOSDSytem (DVBViewer) erhalten hat. Ab dann kommuniziert es nur noch über interfaces. Die anderen IDVBViewerPlugin funktionen sind ja nur zur kontrolle des plugins...

 

Das ganze dürfte nicht wirklich schwierig sein (zumindest in einer (nunja wie sag ichs nett?) systemnäheren sprache), bis auf die interface übersetzungen in eine tlb. Die sind sehr sehr arbeitsintensiv.

Share this post


Link to post
Lars_MQ

Ich habe mir die Datei von erwin mal angesehen.

 

Wenn ich mir die Interfaces.cs anschaue:

 

	public interface IDVBViewerPlugin
{
	void Init(); // Function : HRESULT ??
	[PreserveSig]
	string Name();  // String = 2byte string? in COM -> BSTR
	void SetApplication(ISystem OSD);  // For tests IUnknown OSD should work, why void? it's a function with a HRESULT
	void Terminate();  // The same: why void? it's a function with a HRESULT
}
// ALL Calling conventions must be stdcall!

Share this post


Link to post
LonelyPixel

Ich hab jetzt noch nicht so ganz den Überblick darüber, wer bei so einem Plugin eigentlich wen wann warum womit und wofür aufruft... Also DVBViewer ruft LibTyp, PluginName usw. auf, um Daten zum Plugin zu erfahren. Er ruft InitPlugin2 auf, um ein COM-Objekt des Plugins zu erhalten. IOSDPlugin hat Methoden, die aufgerufen werden, wenn ein Ereignis eintritt. Dann kann das Plugin was tun oder nicht. Und irgendwann wird das Plugin ja bestimmt auch irgendwelche (nicht veröffentlichten) COM-Objekte von DVBViewer erhalten, mit denen es dessen Verhalten beeinflussen kann, nehme ich an. Hab nur noch nicht gefunden wo.

 

Du meinst nun also, man könnte in C++ (oder meinetwegen Delphi) ein Plugin schreiben, das seinerseits reguläre COM-DLLs als Plugins lädt, also einen eigenen Plugin-Mechanismus als Plugin darstellt. Richtig? Und damit wäre es einfacher, COM-Plugins zu erstellen. Z. B. auch in .NET. Bleibt nur noch der Wunsch, sich die Neudefinition der Schnittstellen in jeder Sprache sparen zu können, wenn Delphi die einfach in eine TLB exportieren würde, die andere Sprachen/Compiler automatisch einlesen können, wie .NET, C++/MFC, VB usw. Die Schnittstellen müssen ja nur definiert werden, weil DVBViewer sie nicht als TLB mitteilt. Und wenn ein COM-Übersetzer (z. B. VS) die TLB importiert, ist sie garantiert richtig und aktuell.

 

Irgendwie schon ziemlich verwirrend das alles. Wahrscheinlich bin ich mittlerweile total .NET-verwöhnt. Da muss man sich um so'n Kleinkram überhaupt nicht mehr kümmern, das geht einfach ("It Just Works (IJW)").

 

Gibt's denn irgendwelche Tools, mit denen man solche DLLs testen kann und sich das zurückgegebene COM-Objekt anschauen kann, ob es denn richtig formatiert ist oder nicht? Mal ne bestimmte Methode aufrufen, um zu sehen, ob's die richtige ist usw.

Share this post


Link to post
LonelyPixel
void Init(); // Function : HRESULT ??

Die HRESULT/[out,retval]-Übersetzung übernimmt .NET COM Interop automatisch, wenn nicht das PreserveSigAttribute gesetzt ist. COM verwendet HRESULTs, .NET Exceptions. Die unterschiedlichen Gepflogenheiten beider Welten werden gleich mit angepasst. PreserveSig braucht man aber oft, da viele der DVBViewer-Funktionen eben kein HRESULT zurückgeben.

 

string Name(); // String = 2byte string? in COM -> BSTR

strings werden per Vorgabe als UnmanagedType.BStr "gemarshalt". Das ist ein Unicode-String, dessen erste 2 Bytes die Länge enthalten. Passt schon so. In der Datei DLLExport.cs werden die Rückgabewerte z.B. als LPStr (in C: char *; ANSI, nullterminiert) konvertiert. Sowas muss man dann angeben.

 

void SetApplication(ISystem OSD); // For tests IUnknown OSD should work, why void? it's a function with a HRESULT

Siehe oben: Signaturübersetzung.

 

void Terminate(); // The same: why void? it's a function with a HRESULT

Siehe oben: Signaturübersetzung.

 

// ALL Calling conventions must be stdcall!

Das ist ebenfalls die Vorgabe für COM Interop.

 

http://msdn.microsoft.com/de-de/library/xk1120c3.aspx

-> Konvertieren exportierter Typen (Schnittstellen, Klassen)

-> Konvertieren exportierter Member (Methoden, Eigenschaften)

Interop-Marshalling (Parameter, Rückgabetypen)

 

.NET kümmert sich um die dreckigen Details, ich (als C#-Entwickler) kümmer mich um ein gutes Design meiner Anwendung, egal wie COM das sonst so hält. (Oder gehalten hat, als es mal aktuell war. .NET wird von Microsoft ja an verschiedenen Stellen als COM-Nachfolger bezeichnet.)

Edited by LonelyPixel

Share this post


Link to post
Lars_MQ

Das mag sein sein bei internen Interfaces, aber das sind interfaces, wo DU was zurückgibst.

 

Das ist wie bei der DirectX übersetzung der ganzen interfaces. Das sind interfaces die Du implementierst und zurücklieferst. Wir reden hier von in Process COM, dort gibt es kein COM marshalling, da es nur bei Process übergreifendem COM notwendig ist.

 

Ich kann c# zwar lesen, aber habe viel zu wenig ahnung, um wirklich zu sagen wie es zu machen ist. Aber man könnte sich ja die DirectX oder DirectShow umsetzung der MS Interfaces für c# ansehen, um eine entsprechende idee zu bekommen, wie das ganze für den viewer aussehen sollte...

 

COM verwendet HRESULTs, .NET Exceptions.

Ohja das habe ich gemerkt. Anstatt eine sinnvolle aktion zu vorzunehmen, weil irgendwas nicht geht, schmeissen Net apps ständig irgendwelche exceptions... ;):)

Daran sollte sich jeder NET entwickler schnell gewöhnen (zumindest wenn er mit dem Viewer zusammenarbeiten will) es gibt keine exceptions. Man kann sowas entweder von vornerein vermeiden durch sinnvolle prüfung der parameter, oder entsprechend abfangen und geeignete maßnahmen einleiten. :)

 

Nimm das nicht persönlich, das ist eine erfahrung die hier wahrscheinlich schon jeder gemacht hat. Fehlerbehandlung scheint bei NET etwas verpöhnt zu sein ;)

Share this post


Link to post
Lars_MQ

Gut ich gebe zu, das war nicht nett und hat sicherlich nichts mit Dir oder Deinen wirklich lobenswerten Bemühungen zu tun, ich entschuldige mich für die verallgemeinerung. :)

 

Nur, wenn ich exceptions höre, stellen sich bei mir sofort die Haare im Nacken auf. Eine Exception, die aus irgendeinem Plugin irgendwie hochkommt, kann im Viewer nur beschränkt abgefangen werden und im zweifel eine Aufnahme kosten. Sowas ist in meinen Augen absolut inakzeptabel und vor allem: der DVBViewer kriegt dann die schuld und wir erstmal die schläge... :)

Share this post


Link to post
LonelyPixel

Ich rede auch nicht vom COM Marshaling sondern vom .NET Marshaling. Hier sind z. B. hübsche Bildchen drin, die das erklären: http://msdn.microsoft.com/de-de/library/eaw10et3.aspx .NET Marshaling ist immer aktiv, wenn die managed/unmanaged-Grenze überschritten wird, das geht gar nicht anders. COM Marshaling wird nur dann benötigt, wenn es auch für zwei native Programme benötigt würde, und hängt dann ggf. auch noch in der Aufrufkette drin. Da wir hier in einem Prozess sind, wird das aber nicht verwendet. COM Marshaling macht auch keine .NET-spezifischen Anpassungen wie System.String <-> BSTR oder ComException <-> HRESULT.

 

Ich hab in meinem letzten Beitrag übrigens noch ein paar Links eingefügt, als du schon angefangen hattest, zu antworten. Das und die nähere Umgebung sowie ein Delphi-Buch in den DLL- und COM-Kapiteln war die letzten Tage meine Lektüre. Jetzt bin ich zwar schon viel schlauer, aber noch nicht schlau genug für das hier...

 

Fehlerbehandlung scheint bei NET etwas verpöhnt zu sein

 

Das seh ich ganz anders. Ich kann C# mit Java, C++ und VB vergleichen. In C++ (und C) werden üblicherweise einfach Rückgabewerte zurückgegeben, die Auskunft darüber geben, ob etwas funktioniert hat oder ob die weiteren Funktionsaufrufe eigentlich nur noch groben Müll veranstalten, weil die Datei nicht geöffnet werden konnte. Das halbe WinAPI und viele C- und stdlib-Funktionen arbeiten so. Wer sich nicht für Fehlerbehandlung interessiert, kann in C Programme schreiben, die immer noch irgendwie teilweise funktionieren, wenn man Glück hat. (Vielleicht kriegt ja keiner mit, dass die Datei gar nicht geschrieben wurde *duckundweg*...) In VB ist das schon anders. Wenn man keinen Fehlerhandler (On Error Goto ...) setzt, fliegt man schneller raus, als man schauen kann. Genauso in C# und Java. (C# ist ja eigentlich nur das bessere Java... - Vorsicht: Diese Aussage ist leicht entflammbar!) Hier werden Exceptions geworfen, wenn was nicht geht, und wer sich nicht um Fehler kümmert, ist auch nicht mehr auf Glück angewiesen. Das sorgt im Endeffekt für zuverlässigere Programme, weil Fehler nicht so leicht ignoriert werden können. Ich habe mich gut daran gewöhnt, Exceptions aufzufangen, wo ich Fehler behandeln will. Das ist ganz normal. So gesehen ist Fehlerbehandlung eher in C++ verpöhnt. Über Pascal kann ich keine Aussage treffen, ist zu lange her (Turbo Pascal 7 und ganz kurz noch Delphi ~3) und vermutlich eh nicht mehr gültig. In Delphi hab ich aber auch try/except-Blöcke gesehen. Und spätestens in Delphi.NET läuft das bestimmt ganz genauso wie in C#.

Share this post


Link to post
LonelyPixel
Eine Exception, die aus irgendeinem Plugin irgendwie hochkommt, kann im Viewer nur beschränkt abgefangen werden und im zweifel eine Aufnahme kosten.

Über die COM-Grenze gehen sowieso keine Exceptions, weil das gar nicht vorgesehen ist. COM ist halt eine Microsoft-Erfindung aus einer Zeit, als C++ das Maß aller Dinge war. Deshalb (vermutlich) werden ja auch die HRESULTs zurückgegeben. Eine Exception ist immer ein Sprachfeature. Und Delphi könnte bestimmt keine CLR-Exceptions verarbeiten. Ich kann mir nichtmal vorstellen, wie sowas aussehen sollte. (Das wäre wie Hund mit Vogel... äh... naja.) Also von daher: Keine Angst, die Funktionen halten ihre Signatur ein (wenn ich es jetzt noch schaffe, dass die Objekte ihre Schnittstellen einhalten...) und auf DVBViewer-Seite wirst du von so neumodischem Kram wie .NET vollständig verschont! :)

Share this post


Link to post
Lars_MQ

Bitte klammere Dich Dich bei InProcess COM nicht an dem in einem anderen process laufenden COM fest im im sinne von COM server. :)

 

Du arbeitest IM DVBViewer Process. Wie ein Plugin und Du kannst das ganze Programm zum crash bringen bei einer exception an einer ungünstigen stelle... MS mag das in seiner NET Runtime noch so gut verkleiden, aber gewisse grundlegende sachen bleiben immer noch beim alten... ;)

 

und auf DVBViewer-Seite wirst du von so neumodischem Kram wie .NET vollständig verschont

Da kann ich nur sagen: Bring it on. ;) Ich sehe weder neumodisch noch altbacken, ich sehe nur eine herausforderung das zu integrieren. Christian und ich haben schon ganz andere c# libs auf korrekte funktion getrimmt. :D

 

Gut das war zwar kein scherz, aber trotzdem gibt es in meinen Augen keinen richtigen wettbewerb, da wir alle nur eins als ziel haben: Den DVBViewer voran zu bringen. Wenn man das eine oder andere voneinander lernt hat jeder gewonnen :)

Share this post


Link to post
erwin

Erstmal Gratulation. "Klassiche"-Plugins gehen nun schon mal.

Wenn man mal das InitPlugin2 weglässt hast Du hier erstmalig ein (reines) C# Framework präsentiert welches zumindest "klassische" Plugins in C# erstellbar macht. Das war vor einiger Zeit mal sehnlicher Wunsch einiger C#-Anhänger.

 

Machen wir mal weiter mit dem OSD Zeugs. Ich in C++, du in C# - beide haben wir das Doku-Problem, so dass ein Austausch auch über die Sprachgrenzen hinweg nicht schaden kann.

 

So, dann bliebe nur die Frage, was beim Aufruf von InitPlugin2 passiert. Anscheinend schlägt ja bereits der Aufruf fehl, denn meine MessageBox gleich zu Beginn der Funktion wird ja schon gar nicht angezeigt.

...

Aber so richtig aufgerufen wird sie trotzdem nicht. Weder meine MessageBox noch der Piepton des PC-Lautsprechers werden ausgegeben. Auch die Rückgabe eines garantiert ungültigen Pointers ändert daran nichts.

Über so was bin ich auch schon mal in einem anderem C#-Projekt gestolpert. Obwohl die Fehler nachweislicher erst nach so einer MessageBox/Beep produziert wurden sah es so aus als ob diese MessageBox/Beep nie passiert wurden.

Man konnte den fälschlichen Eindruck haben dass die Funktion erst gar nicht aufgerufen wurde. Was hier .Net macht? Keine Ahnung - vielleicht der Just-in-Time -Compiler.

 

 

...wird die Funktion zwar aufgerufen und die MessageBox erscheint, aber danach verschwindet DVBViewer sofort ohne jede Spur! Jedes Mal.
Wahrscheinlich, weil er versucht die Funktionen des Interface aufzurufen und dabei was schief läuft. Der erste Funktionsaufruf dürfte _Addref sein, damit die referenzzählung des dem Interface zugrunde liegenden Objekts aktualisiert wird.

Ich denke auch dass dies der Grund ist. Ich sehe bei dir noch keine richtige Verbindung zu einer IUnknown-Implementation (Interface ist ja nun inzwischen über die Attribute da) und die wird der DVBV auch ausgiebig nutzen. Erst AddRef dann QueryInterface. Dies kann dir C# mit keinem Automatismus abnehmen. QueryInterface liefert die speziellen Pfade zu den Interfaces. Ich komme evt. später noch mal darauf zurück.

 

gibt es ein funktionierendes (!) C++ PlugIn, in dem diese Stelle erfolgreich verwendet wird und kann man die Deklaration mal hier posten?

Ich arbeite daran.

 

Was in den Schnittstellendefinitionen vom letzten Post von erwin auffällt ist, dass die Methoden der Schnittstellen alphabetisch sortiert sind.

@JMS meinst du wirklich meine (welche eigentlich). Denke eine Verwechselung mit LonelyPixel.

 

Die Reihenfolge ist aber von elementarer Bedeutung. ... und eine falsche Zuordnung wäre tödlich

Da bin ich voll deiner Meinung

 

Ich habe mir die Datei von erwin mal angesehen.

 

Wenn ich mir die Interfaces.cs anschaue:

@Lars_MQ meinst du wirklich meine. Denke eine Verwechselung mit LonelyPixel.

 

Zum Disput wo wohl die *bessere* Fehlerbehandlung angesiedelt ist, sage ich jetzt mal nichts (war wohl doch schon etwas spät gestern). Über das Alter bin ich hinaus :)

Fakt ist wenn der Schnittstellenvertrag vorsieht - um Himmels willen keine Exceptions - dann dürfen keine geliefert werden. Und Verträge müssen gehalten werden. Und jede Sprache die Exceptions würft, hat auch Mittels diese zu fangen.

 

erwin

Share this post


Link to post
JMS

Noch ein Versuch :) Du hast hier doch schon beschrieben, dass Du das IntefaceTypeAttribute konsequent benutzt. Probiere das doch einmal ohne Ableitung der Interfaces aus, d.h. kopiere die 4 Methoden von IDVBViewerPlugin in die anderen Schnittstellen hinein (natürlich in der gegebenen Reihenfolge ganz am Anfang) und nimm' die beiden Ableitungen von der Basisschnittstelle weg. Kostet vermutlich nur ein paar Minuten und dann wissen wir, ob es vielleicht doch an so was Trivialem liegt.

 

Ansonsten wäre der nächste Schritt ein kurzes Native (Unmanged) Debugging (ich habe leider keine DVBViewer, sonst würde ich es mal machen). Debugger im Native Mode dranhängen, sobald die MessageBox aus dem noch erfolgreichen Aufruf erscheint und alle C++ und Win32 Exceptions aktivieren. Weiterlaufen lassen, dann kommt vermutlich eine AccessViolation. Wenn im CallStack sowas wie Call [edx + 4 * yyy] steht, dann ist da fast sicher ein COM Schnittstellenaufruf der yyyten Methode (0 bis 2 sind für IUnknown, 4 wäre Init etc.). So habe ich die Übeltäter bisher meistens gefunden.

 

Jochen

Share this post


Link to post
JMS
@JMS meinst du wirklich meine (welche eigentlich). Denke eine Verwechselung mit LonelyPixel.

Nein, nur die in dem ZIP, dass Du hochgeladen hast - sind sicher seine! Er hat inzwischen das InterfaceTypeAttribute eingesetzt (siehe den Post, der sich gerade mit Deinem überschnitten hat).

 

Jochen

Share this post


Link to post
JMS

Vielleicht mal etwas OffTopic am Rande. Ich habe mal ein Framework für ein anderes Produkt gebastelt, dass es auch erlaubten sollte, PlugIns in C# in ein C++ PlugIn-Framework zu integrieren (letzte Sourcen hier - keine Ahnung, welcher Stand). Damals (das würde ich heute strukturell etwas anders mit einer C++ mixed managed / native DLL machen, spielt aber keine Rolle) war das folgender Ansatz:

 

- es gibt eine .NET (C#) Bibiothek

- gegen diese wird ein Plugin in .NET entwicklet

- es gibt eine fertige C++ DLL mit den Einsprungpunkten

- am Ende des Build Vorgangs werden durch ein Tool die .NET Bibliothek und das Plugin (die Assemblies) als Ressourcen in die C++ DLL eingepflegt (auch Wunsch mit PDB zum Debuggen)

- dabei entsteht eine neue DLL, die ist das PlugIn

- bei Starten der C++ PlugIn Methoden werden die .NET Laufzeitumgebung hochgefahren, eine AppDomain erzeugt und die beiden Assemblies aus den Ressourcen rekonstrukiert und in die AppDomain geladen

- das PlugIn gibt alle statisch exportierten C++ Methoden (hier: dumm, da nicht mixed, aber egal) an das .NET PlugIn weiter

 

Wenn man sich das gesamte mal anschauen möchte, hier meine letzte Version, die ist aber glaube ich Visual Studio 2005. Irgendwo hier sollte es eine VS2008 Version geben, die finde ich aber auf die Schnelle nicht (nur einige PlugIns, die damit gemacht sind). Ich habe eine solche Version hier, weiß aber nicht, ob die Integration als Template in VS noch geht.

 

Jochen

Share this post


Link to post
erwin

Ich wolllte wie angekündigt noch mal auf QueryInterface zurückkommen.

 

lies mal

 

http://www.DVBViewer.info/forum/index.php?...st&p=289998

 

und Folgendes, insbesondere die Antworten von Lars_MQ.

 

Von dieser Klasse sind verschiedene Nachfahren abgeleitet, die automatisch das Interface der Grundklasse durch die Vererbung bereitstellen. Der DVBViewer erhält das "IDVBViewerPlugin" Interface vom Plugin und kann über QueryInterface zum Beispiel das "iOSDPlugin" Interface des Plugins holen.

In deinem Plugin muss also irgendwo der Code stehen wo ein QueryInterface() auf IOSDPlugin mit der konkreten Interfaceadresse von IOSDPlugin beantwortet wird. Wo? Sehe nichts. Wenn man die Delphi-Samples verfolgt kommt man auf eine QueryInterface-Implementation die intern dann GetInterface() aufruft. Leider ist GetInterface() eine Methode aus der Delphi-Infrastruktur die es dem Delphi-Progger leicht macht, in C++ aber keine Entsprechung hat und nachgebaut werden muss. Hier ist wo ich momentan stehe.

 

Und.

Wie JMS sagt, die Reihenfolge der Methoden muss genau mit der Reihenfolge in den Delphi-Samples übereinstimmen.

Im DVBV werden zwar die Methoden über ihren Namen aufgerufen aber spätestens beim Compiler ist dann Schluss. Die Namen sind weg und der Compiler ruft die Methode so ala "call die 5. Adresse aus dieser und jener Funktionspointertabelle".

 

 

erwin

Edited by erwin

Share this post


Link to post
LonelyPixel
Bitte klammere Dich Dich bei InProcess COM nicht an dem in einem anderen process laufenden COM fest im im sinne von COM server. :)

Tu ich das? Mir ist bewusst, dass in diesem Fall hier kein COM Marshaling aktiv ist. Aber .NET ist nunmal eine verwaltete Umgebung, die ihren Speicher nach gutdünken belegt, freigibt oder durch die Gegend schieben kann. Das an sich wäre zunächst mal völlig inkompatibel zu nativem Code. Damit es dennoch überhaupt eine Verbindung gibt, wird .NET (Interop) Marshaling eingesetzt, das verwaltete Objekte für unverwaltete Programme verfügbar macht. Und (wenn ich das richtig verstanden habe) genau an dieser Schnittstelle werden die .NET-Objekte im COM-Format bereitgestellt, also AddRef, QueryInterface usw. implementiert. Das reference-count-basierte Garbage Collecting, wie es in COM üblich ist, wird durch den .NET CCW (COM Callable Wrapper) implementiert und entsprechend in das .NET Garbage Collecting übersetzt.

 

Du arbeitest IM DVBViewer Process. Wie ein Plugin und Du kannst das ganze Programm zum crash bringen bei einer exception an einer ungünstigen stelle... MS mag das in seiner NET Runtime noch so gut verkleiden, aber gewisse grundlegende sachen bleiben immer noch beim alten... :)

Der CCW fängt alle Exceptions auf und liefert stattdessen HRESULT != 0 zurück. Wenn keine Exception aufgetreten ist, ist der Rückgabewert 0. Den Wert, den meine Funktion stattdessen zurückgibt, der wird in den out,retval-Parameter geschrieben, der in der .NET-Version der Methodensignatur gar nicht vorkommt. Damit das funktioniert, muss der out-Parameter gemäß der COM-Richtlinien an letzter Stelle kommen.

 

Ich denke auch dass dies der Grund ist. Ich sehe bei dir noch keine richtige Verbindung zu einer IUnknown-Implementation (Interface ist ja nun inzwischen über die Attribute da) und die wird der DVBV auch ausgiebig nutzen. Erst AddRef dann QueryInterface. Dies kann dir C# mit keinem Automatismus abnehmen. QueryInterface liefert die speziellen Pfade zu den Interfaces. Ich komme evt. später noch mal darauf zurück.

Soweit ich das aus der MSDN verstanden habe, implementiert der CCW die IUnknown-Methoden. Über IDispatch habe ich mich nur am Rande informiert, wie es da laufen würde, weiß ich nicht.

 

@JMS meinst du wirklich meine (welche eigentlich). Denke eine Verwechselung mit LonelyPixel.

Ich habe nichts umsortiert. Ich hab die Pascal-Quellen genommen und Zeile für Zeile übersetzt.

 

@Lars_MQ meinst du wirklich meine. Denke eine Verwechselung mit LonelyPixel.

Er meint wahrscheinlich deine bearbeitete Version meines ersten (und mittlerweile überarbeiteten) Entwurfs.

 

Ansonsten wäre der nächste Schritt ein kurzes Native (Unmanged) Debugging (ich habe leider keine DVBViewer, sonst würde ich es mal machen). Debugger im Native Mode dranhängen, sobald die MessageBox aus dem noch erfolgreichen Aufruf erscheint und alle C++ und Win32 Exceptions aktivieren. Weiterlaufen lassen, dann kommt vermutlich eine AccessViolation. Wenn im CallStack sowas wie Call [edx + 4 * yyy] steht, dann ist da fast sicher ein COM Schnittstellenaufruf der yyyten Methode (0 bis 2 sind für IUnknown, 4 wäre Init etc.). So habe ich die Übeltäter bisher meistens gefunden.

Öhm, dann bist du schon schlauer als ich. Ich hatte das mit dem Debugger auch mal versucht und bei der MessageBox den Prozess angehalten. Ich habe damals allerdings nichts gefunden. Mittlerweile würde ich auch beachten, dass zwischen DVBViewer-Aufruf und MessageBox ja noch ein bisschen MessageBox-.NET-Code sowie CCW- bzw. Interop-Code steckt, den man erstmal abgrenzen und überspringen müsste.

 

DVBViewer stürzt im letzten Versuch ja auch nicht mit einer Zugriffsverletzung ab, da würde sich ja der VS-Debugger zu Wort melden und das Debuggen anbieten. DVBViewer ist einfach *plop* weg, ohne einen Laut.

 

lies mal

Den hatte ich auch schonmal durch...

 

In deinem Plugin muss also irgendwo der Code stehen wo ein QueryInterface() auf IOSDPlugin mit der konkreten Interfaceadresse von IOSDPlugin beantwortet wird. Wo? Sehe nichts.

Im .NET CCW. Der wird zur Laufzeit implementiert, da hab ich nichts mit zu tun. Ich gehe davon aus, dass ich .NET nur sagen muss, dass er das tun soll, und dann geht's los. 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.

 

Kann ich mir so ein Testprogramm selbst schreiben, indem ich die vollständige Struktur einer Schnittstelle in C++ nachprogrammiere (ohne irgendwelche Automatismen zu verwenden) und dann mal versuchen, einzelne Funktionen aufzurufen? Ich glaub das mach ich jetzt mal. Das Problem ist nur, dass ich diese Erkenntnisse dann mit nichts vergleichen kann und sie nur aufgrund meines theoretischen und unvollständigen Wissens prüfen kann.

Edited by LonelyPixel

Share this post


Link to post
erwin
Im .NET CCW.

Das sieht mir doch nach Voodoo aus.

Mach doch mal folgendes:

public class BaseOSDSetupPlugin : IDVBViewerPlugin, IOSDPlugin, IPlugin
{
	protected ISystem fosd;

	#region IDVBViewerPlugin Member
	public virtual void Init()
	{

Setze mal vor (Thema Reihenfolge) "#region IDVBViewerPlugin Member" die (sprich Deine im Unterschied zum CCW-Default) Implementation der drei IUnknown Methoden. Wieder mit MessageBox und schaue was passiert.

 

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...