Einstiegspunkt dll (com-dll) erstellen

  • VB.NET

Es gibt 8 Antworten in diesem Thema. Der letzte Beitrag () ist von raist10.

    Einstiegspunkt dll (com-dll) erstellen

    Hallo zusammen.
    Zwar bin ich neu im Forum doch wage ich gleich, eine Frage aus lauter Verzweiflung zu stellen.
    Ich habe mich schon Wochenlang (!) durch deutsche und englische Foren gequält aber nichts gefunden.

    Folgender Hindergrund:
    Ich habe in VB 2010 express eine relativ umfangreiche dll erstellt.
    Diese funktioniert auch gut, solange ich sie in VB anspreche.
    Nun will ich diese auch in c++ ansprechen.

    Problem:
    Ich weiss, dass ich dazu eben die Einsprungspunkte für die dll brauche, auch erkennbar über dependency walker.
    Ärgerlich ist, dass ich für Uralt-VB Lösungen (aus 2003 bzw 2004) finde, aber weder die für VB 2008 geschweige denn 2010.

    Bisherige Maßnahmen:
    Ich habe in den Einstellungen von VB die Assembly-com Klasse sichtbar gemacht und in der AssemblyInfo.vb <Assembly: ComVisible(True)> gesetzt.
    Auch mit <ClassInterface(ClassInterfaceType.AutoDual)> habe ich rumgespielt aber es nutzt nichts :(

    Dependency sagt mir freundlich aber bestimmt njet ;(
    Regasm will auch nicht so richtig auf Windows7 funktionieren.

    Hat jemand da eine Lösung parat?

    Vielen Dank dadafür *ggg*
    Woody
    Bei einer echten COM-Dll musst Du noch einige weitere Sachen beachten die seltsamerweise in diesen ganzen Tutorials nie erwähnt werden. ^^

    Die COM-Dll muss auch COM-Klassen haben, dazu reicht es nicht deren Sichtbarkeit auf True zu setzen.

    Hier mal ein Snipet wie die Deklaration einer COM-Klasse in VB.NET aussieht (bzw. so funzt es bei mir):

    VB.NET-Quellcode

    1. <Microsoft.VisualBasic.ComClass(comBASpecific.ClassId, comBASpecific.InterfaceId, comBASpecific.EventsId)> _
    2. Public Class comBASpecific
    3. Inherits rsmop.netBestAdvisor.netBAFolders ' das inherits ist natürlich nicht nötig für eine COM-Klasse ^^
    4. #Region "COM-Class-Definition"
    5. Public Const ClassId As String = "F12E752F-63CC-45e9-82F6-0A3E03057B6B"
    6. Public Const InterfaceId As String = "7BE79C30-0887-4a75-BECD-C42F9E0B96C2"
    7. Public Const EventsId As String = "D09BBF85-9E33-46e8-918F-FA7326F92C89"
    8. ' Für Initialisierung bei Aufruf
    9. Public Sub New()
    10. MyBase.New()
    11. End Sub
    12. #End Region


    Die GUID's musst Du natürlich für jeden Eintrag neu erzeugen (Extras -> GUID erstellen -> Registry Format wählen und mit Klick auf 'New GUID' erzeugen).

    Auch der Konstruktor MUSS zwingend öffentlich sein und MUSS zwingend mit MyBase.New() beginnen. Was aber bei einer COM-Klasse eh kein Thema sein sollte. ;)

    Und ja ... das musst Du für jede Klasse einzeln machen für die Du den COM-Zugriff aktivieren willst. Wenn die Dll schon größer ist ... have fun. ;)

    Wie Du an meinen Präfixen erkennst habe ich in der Dll sowohl COM-Klassen als auch NET-Klassen drinnen. Der einfache Grund ist weil ich keinen Bock habe die COM-Klassen für den NET-Zugriff neu zu erstellen wenn ich die mal brauche. Also basiert jede COM-Klasse auf einer NET-Klasse und beerbt diese ... aber ACHTUNG ... geerbte Methoden und Eigenschaften werden NICHT nach aussen transportiert. Um als eine geerbte Methode für den COM-Zugriff zur Verfügung zu stellen musst Du die gleiche Methode/Eigenschaft in der COM-Klasse deklarieren und als Return kannst Du dann den Return der NET-Klasse zurück werfen.

    Damit regasm.exe nicht mault solltest Du die Assembly signieren (MyProject - Signierung - Assembly signieren). Dadurch wird die Assembly strong named und regasm.exe hört auf zu maulen.

    Ein weitere Problemstelle ist die Einbindung der COM-Dll auf einem fremden Rechner da man ja die COM-Dll dann meistens mit einer Anwendung weiter geben will die nicht in NET erstellt wurde und daher meistens separate Installer nutzt und nicht den NET eigenen.

    Ich habe das wie folgt gelöst: Dll mit allen zugehörigen Dateien aus dem Release-Order (ausser .xml und .pdb) werden über meinen Installer ins einen Sub-Ordner meiner Anwendung kopiert und dort starte ich dann eine Batch-Datei die regasm.exe auf dem Zielrechner zur Registrierung ausführt.

    Hier der Batch-Code dazu:

    VB.NET-Quellcode

    1. SET fwpath="%windir%\Microsoft.NET\Framework\v2.0.50727"
    2. SET dllPath="C:\BestAdvisor\bin\Dll"
    3. if not exist %dllPath%\rsmop.tlb goto RegisterRsmop
    4. %fwpath%\regasm.exe %dllPath%\rsmop.dll /u /tlb:%dllPath%\rsmop.tlb
    5. del %dllPath%\rsmop.tlb
    6. :RegisterRsmop
    7. %fwpath%\regasm.exe %dllPath%\rsmop.dll /codebase /tlb:%dllPath%\rsmop.tlb


    Habe mich auf das niedrigste verfügbare Framework gestürtzt das die Regasm.exe vorhält. Und vor der Registrierung mit regsam.exe eine De-Registrierung mit regasm.exe falls auf dem Zielrechner bereits eine alte/vorherige Version meiner COM-Dll registriert wurde.

    Und zum Schluß das ALLER WICHTIGSTE:

    Wenn Du Dir die Möglichkeit freihalten willst einfach auch nur mal auf den Zielrechnern die Dll auszutauschen (Update, Verbesserung, Bug-Fixing) ohne gleich jedesmal die komplette Anwendung neu zu liefern, dann verändere NIEMALS die Assemblyinformationen. Eine einzige Änderung und Du musst gleich die komplette App mit dem Verweis auf die neue Assmbly ausliefern. Sonst kracht es da durch die Signierung bei einer Abweichung in der Assemblyinformation die neu gelieferte Dll in der App in der die alte Dll-Version eingebunden war als ungültig zurück gewiesen. Veränderst Du aber keine einzige Assemblyinformation dann wird die neue Dll (die natürlich auch mit obigen Batch-Aufruf auf dem Zielrechner installiert wird, daher auch der Deinstallationscode vor der Registrierung) ohne jegliche Problem akzeptiert.

    So zumindest funktioniert es bei mir mit der COM-Dll, allerdings nicht für C++ sondern für ein Access-Anwendung (war nie geplant das das Teil so einen Umfang annimmt, sonst hätte ich es gleich in VB.NET gemacht ^^).

    Und wenn Dir das alles weitergeholfen hat, gebe ich Dir meine Kontonummer ... das rauszufinden wie eine COM-Dll in/mit VB.NET funzt hat mich fast 4 Arbeitstage gekostet. (natürlich nur ein Spaß :D)

    Gruß

    Rainer

    P.S.: Nutze in der COM-Klasse selber ausschliesslich den Modifikator Public für Methoden und Eigenschaften die nach aussen sichtbar sein sollen. Jegliche weitere Angabe wie Overloads/Shadows/Shared etc. führte bei mir dazu das die Methode nicht mehr sichtbar war. Ach ja ... und achte auf die Variablen Deklarationen das die dann auch mit einem Datentyp in C++ übereinstimmen, sonst kracht es mächtig im Gebälk.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „raist10“ ()

    Danke Raist10

    Woha... Dankeeeeeeeeee...

    Glaube ich, dass Du 4 Tage drangesessen hast, ich suche ja ebenfalls schon sehr lange und Microsoft bekleckert sich da auch nicht unbedingt mit Ruhm...
    Bin jetzt schon ein bisschen gedanklich weitergekommen.
    Also mich gleich an die GUID´s gesetzt, Dll in soweit abgeändert, signiert und...

    Mistvieh :-D
    Das mit der Überweisung muss jetzt warten :love:

    Was sauber funktioniert ist auf jeden Fall die Registrierung mit regasm, obwohl ich die prinzipiell nicht unbedingt brauche.
    Aber für eine saubere funktionierende dll jedenfalls ein "must have" ...

    Wenn ich die neu erstellte dll in dependendcy walker überprüfe bringt er mir immer noch nicht die Einstiegspunkte.
    Alles leer.

    Folgerichtig bricht auch meine c++ Testumgebung ab, die zwar die dll einbinden kann, aber dann den Einstiegspunkt für die Funktionen nicht findet.

    Was mich nur wundert ist die Ausgabe von regasm (bei einer generierten *.reg Datei), in der z.B. die "Public NotInheritable Class InitScreen" angezeigt wird, nicht aber die Klasse der dll selbst bzw. deren Funktionen darin :(

    Nun habe ich auch selbst noch einiges zum Austesten und Probieren.
    Zum Glück habe ich nur eine öffentliche Klasse mit 5 Funktionen, die öffentlich sein müssen.
    Alles in allem also noch relativ überschaubar trotz ca. 3000 Zeilen Quellcode.

    Und um es gleich vorweg zu nehmen, es ist relativ "sauberer" code, aber leider kenne ich mich dann in c++ nicht so gut aus,
    um die ganze dll dorthin zu portieren (primär geht es da um eine serielle Schnittstellenansteuerung mit viel (igitt) Binärcode) :evil:


    Danke erst einmal auch an pc-werkstatt, wobe es da ja Prinzipiell nur um den Aufbau einer dll geht, so wie ich das gelesen habe.

    Gruss und DANKE
    Matthias aka. Woody

    Woody schrieb:


    Das mit der Überweisung muss jetzt warten :love:


    Ach verdammt ... und ich hatte schon eine Karibik-Kreuzfahrt ins Auge gefasst. 8-)

    Woody schrieb:

    Was sauber funktioniert ist auf jeden Fall die Registrierung mit regasm, obwohl ich die prinzipiell nicht unbedingt brauche.
    Aber für eine saubere funktionierende dll jedenfalls ein "must have" ...


    Gut, keine Ahnung wie Du die Dll in Dein C++ Projekt einbinden willst, aber unter VBA hast Du keine Chance einen Verweis auf die Datei zu setzen wenn Du nicht mit regasm.exe und dem Paramter /codebase registriert hast.

    Woody schrieb:

    Wenn ich die neu erstellte dll in dependendcy walker überprüfe bringt er mir immer noch nicht die Einstiegspunkte.
    Alles leer.


    Es gibt auch auf diese Weise keinen Einstiegspunkt in der Dll soweit ich bei meinen Recherchen rausgefunden habe ist der eh nur über eine C++ COM-Dll herzustellen und nicht über eine COM-Dll die mit VB.NET erstellt wurde.

    Woody schrieb:

    Folgerichtig bricht auch meine c++ Testumgebung ab, die zwar die dll einbinden kann, aber dann den Einstiegspunkt für die Funktionen nicht findet.


    Musst Du mal die verschiedenen Möglichkeiten der Einbindung einer Dll prüfen. Ich kenne mich mit C++ nicht aus, aber nachdem was ich beim kurzen googeln gelesen habe müsstest Du vermutlich den Weg über den Linker gehen der eine Lib erzeugt und die dem Compiler die Funktionen der Dll bekannt macht ... zumindest habe ich das so verstanden. ^^

    Woody schrieb:

    Was mich nur wundert ist die Ausgabe von regasm (bei einer generierten *.reg Datei),


    Öhem ... *.reg? Ka, ich erzeuge eine *.tlb und das funzt.


    in der z.B. die "Public NotInheritable Class InitScreen" angezeigt wird, nicht aber die Klasse der dll selbst bzw. deren Funktionen darin :(


    Ich würde auf alle Fälle alle NET spezifischen Modifikatoren entfernen, wie eben NotInheritable/MustInheritable, Shared, Shadow, Overloads, Overrides/Overrideable etc., etc. ... auch mit Delegaten oder generischen Typen würde ich auf gar keinen Fall in einer COM-Klasse die für den Zugriff unter anderen Entwicklungsumgebungen dienen soll agieren. Ich hatte damit in meinen Test mit Access/VBA nur Probleme ... Funktionen wurden nicht angezeigt sobald ein NET spezifischer Modifikator zur Anwendung kam oder auch mal kompletter Absturz.

    Eine andere Klasse beerben geht, Enumerationen der beerbten Klasse werden dann auch sauber angezeigt in Access ... aber alles andre hat nur Probleme gemacht. Daher ja auch mein Ansatz den kompletten Code in klassischen NET-Klassen zu erstellen und die COM-Klassen nur als Wrapper um die NET-klassen zu gestalten. Auch wenn man die Deklarationen für die Zugriffspunkte nochmals schreiben muss in dem Fall, fand ich es aber sinniger den Code unter voller Verwendung aller Möglichkeiten von NET zu schreiben.

    Woody schrieb:

    Nun habe ich auch selbst noch einiges zum Austesten und Probieren.
    Zum Glück habe ich nur eine öffentliche Klasse mit 5 Funktionen, die öffentlich sein müssen.
    Alles in allem also noch relativ überschaubar trotz ca. 3000 Zeilen Quellcode.


    Okay, das geht wirklich ... das ist ja gut überschaubar, 3000 Zeilen sind ja gar nichts.

    Woody schrieb:

    Und um es gleich vorweg zu nehmen, es ist relativ "sauberer" code, aber leider kenne ich mich dann in c++ nicht so gut aus,
    um die ganze dll dorthin zu portieren (primär geht es da um eine serielle Schnittstellenansteuerung mit viel (igitt) Binärcode) :evil:


    Wie gesagt, in C++ kann ich Dir überhaupt nicht weiter helfen. Aber der ein oder andere hier im Forum kennt sich da vermutlich aus und kann zum Thema Dll einbinden vllt noch den ein oder anderen Tipp für Dich haben. ^^

    Gruß

    Rainer
    Nochmals Danke für die Tipps :)

    Das mit c++ wird noch sicherlich lustig, aber machbar.

    Nur noch eine Frage zum Verständnis.
    Habe ja die #Region und die GUID eingefügt in der Klasse.
    Muss ich nun für jede öffentliche Funktion etwas ähliches tun oder reicht das Public in der nun definierten Klasse.
    Beispiel:

    VB.NET-Quellcode

    1. Class Irgendetwas
    2. #Region "COM-Class-Definition"
    3. Public Const ClassId As String = "Ein GUID String"
    4. Public Const InterfaceId As String = "Noch ein GUID String"
    5. Public Const EventsId As String = "Letzter GUID String"
    6. ' Für Initialisierung bei Aufruf
    7. Public Sub New()
    8. MyBase.New()
    9. End Sub
    10. #End Region
    11. Public Function TueDies() as String
    12. ...
    13. End Function
    14. Public Function TueDas() as String
    15. ...
    16. End Function
    17. End Class


    oder wieder explizit mit #Region

    VB.NET-Quellcode

    1. Class Irgendetwas
    2. #Region "COM-Class-Definition"
    3. ...GUID´s
    4. Public Sub New()
    5. MyBase.New()
    6. End Sub
    7. #End Region
    8. #Region "TueDies-Definition"
    9. Public Const ClassId As String = "Wieder ein GUID String"
    10. Public Const InterfaceId As String = "Noch ein neuer GUID String"
    11. Public Const EventsId As String = "Letzter neuer GUID String"
    12. Public Function TueDies() as String
    13. ...
    14. End Function
    15. #End Region
    16. End Class


    Danke für Deine unerschöpfliche Geduld,
    Im Moment ist aus dem Tagesausflug schon ein WE-Tripp geworden *ggg*

    LG
    Matthias

    Muss ich nun für jede öffentliche Funktion etwas ähliches tun oder reicht das Public in der nun definierten Klasse.


    Sorry, Du musst natürlich jede einzelne Public Function auf ComVisible True stellen. Habe ich vergessen, weil es in NET im Eigenschaften-Fenser bei mir immer schon automatisch voreingestellt ist. Siehe angehängte Grafik.
    Per Code machst Du das so:

    VB.NET-Quellcode

    1. <ComVisible(True)> _
    2. Public Function ...


    Den einzelnen Funktionen GUID's zu weisen ist nicht nötig, nur die Klasse braucht diese Identifier.

    Das Du bei der Klassendeklaration den COM-Header nicht mit genannt hast war Absicht und nicht vergessen, oder? ;) Die GUID's in der Klasse sind nämlich nur sinnvoll wenn die Klasse auch den COM-Header hat.

    Gruß

    Rainer
    Bilder
    • Eigenschaften_COM_Method.png

      23,25 kB, 276×217, 170 mal angesehen
    Grins..

    Ich bin eine faule Tippse und sitze hier an einem Rechner ohne VB und dem Programm im Moment.

    Der Com-Header ist natürlich mit drinnen, dahingegen nicht die (...) Punkte :D und ein richtiger GUID String ist auch dabei.

    Die <ComVisible(True)> hatte ich in meiner Hauptfunktion mit drinnen, aber die wurde (wie ja geklärt), dann nicht angezeigt.

    Die Com-Sichtbareinstellungen hatte ich ja, wie zuerst beschrieben, schon alle vorgenommen (Dein letzter Punkt), sowohl im Assembly und in den Einstellungen...

    Jetzt muss ich wohl nur noch ein bisschen c++ üben, nutze dev c++ zum Testen.

    Hatte mir vorher schon aus Verzweiflung überlegt, ob ich mir nicht via Microsoft C++ eine "Workaround-DLL" schreiben kann,
    die dann die VB Dll aufruft (Manchmal gehe ich noch von dem Glauben aus, MS-Produkte können zusammen funktionieren :whistling: )


    Thanks
    Matthias

    Woody schrieb:

    Die <ComVisible(True)> hatte ich in meiner Hauptfunktion mit drinnen, aber die wurde (wie ja geklärt), dann nicht angezeigt.

    Die Com-Sichtbareinstellungen hatte ich ja, wie zuerst beschrieben, schon alle vorgenommen (Dein letzter Punkt), sowohl im Assembly und in den Einstellungen...


    Nur um alle Klarheiten zu beseitigen ^^ ... ComVisible True (ob jetzt über Code oder Eigenschaften ist latte) muss natürlich auch bei den von mir beschriebenen Änderungen gegeben sein. Ist das auf False werden die Methoden/Eigenschaften auf gar keinen Fall angezeigt.

    Dann noch viel Spaß mit C++. ;)

    Irgendwann demnächst muss ich auch an C ran, aber dürfte vermutlich das Glück haben es mit C# machen zu können ... da habe ich dann wenigsten schon ein bisserl eine Ahnung. ^^

    Gruß

    Rainer