Verwendung von COM in Forms



  • Hallo Forum,

    ich bin neu hier und erhoffe mir etwas Beistand bei einer für mich kniffeligen Fragestellung.

    Ich möchte eine WindowsForm (erzeugt mit Visual Studio C++ 2008 Express) als COM-Client nutzen, um Daten von einem COM-Server zu beziehen. Der COM-Server liegt als local Server (*.EXE) vor. Die von ihm angebotenen Funktionen sind mir aus seiner Dokumentation bekannt.

    Wie kann ich mir diese Funktionen jedoch im Client nutzbar machen??? Ich denke das geht mittels Instanzierung eines Objektes der COM-Komponente des Servers.

    Als einfachstes Beispiel möchte ich gern bei Button-Click in der WindowsForm den Server und Client verbinden, die Verbindung prüfen und eine angebotene Funktion des Servers ausführen...

    Nun meine Fragen:
    ➡ Ist mein Vorhaben grundsätzlich möglich?
    ➡ Wie genau instanziert man in diesem Fall das Objekt bei vorliegender *.Exe ⚠ ? (Codebeispiel bitte)

    Vielen Dank! 😃



  • dermobb schrieb:

    Hallo Forum,
    ➡ Ist mein Vorhaben grundsätzlich möglich?

    Sicher, sogar auf viele Arten, denn C++/CLI ist quasi die Vereinigungsmenge von C++ und .net.

    In C++ fallen die Wege über die MFC- und ATL-Bibliotheken für dich weg, da die bei der Express-Version nicht dabei sind.

    Ein sehr leistungsfähiger Weg ist der über #import
    http://msdn.microsoft.com/de-de/library/8etzzkb6.aspx
    Damit kannst du deine *.exe einfach im Quelltext "includen", der Compiler erzeugt dir dann entsprechende C++-Klassen, mit denen du auf das COM-Interface zugreifen kannst.

    Bei Automatisierungsschnittstellen gibt es auch den Weg über Late-Binding, darüber hat der hiesige Moderator einen schönen Artikel geschrieben. 🙂
    http://blog.kalmbachnet.de/?postid=63

    In .net gibt es auch mehrere Wege:

    Wie in C# kannst du einfach einen Verweis zu deinem Projekt hinzufügen. Das geht mit COM-Komponenten genauso wie mit .net-Komponenten. Es werden dann automatisch Wrapper generiert, über die du die COM-Objekte wie .net-Objekte ansprechen kannst.

    In C++/CLI kann man auch selber COM-Wrapper schreiben, da kommt dann auch wieder #import zum Zuge.

    Auch Late-Binding gibt es in .net, ein C++/CLI-Beispiel findest du hier.
    http://www.sahirshah.com/cpp/CLRLateBinding.html

    dermobb schrieb:

    (Codebeispiel bitte)

    C++/CLI ist was für Fortgeschrittene. Da sollte die Doku reichen.



  • Also erstmal vielen Dank für deine Tips! 👍

    Ein sehr leistungsfähiger Weg ist der über #import. Damit kannst du deine *.exe einfach im Quelltext "includen"

    Hört sich gut an! Habe ich auf folgende Weise probiert. Keine Fehler beim Kompilieren:

    // crosscomm.cpp: Hauptprojektdatei.
    
    #include "stdafx.h"
    #include "Form1.h"
    #import "C:\KRC\UTIL\CrossCommEXE.exe"    //local COM-Server
    
    using namespace crosscomm;
    
    [STAThreadAttribute]
    int main(array<System::String ^> ^args)
    {
    	// Aktivieren visueller Effekte von Windows XP, bevor Steuerelemente erstellt werden
    	Application::EnableVisualStyles();
    	Application::SetCompatibleTextRenderingDefault(false); 
    
    	// Hauptfenster erstellen und ausführen
    	Application::Run(gcnew Form1());
    	return 0;
    }
    

    Es gibt ja angeblich keine doofen Fragen - deshalb:

    ...der Compiler erzeugt dir dann entsprechende C++-Klassen,...

    Wo? Ich kann sie nicht finden... 😕

    ...mit denen du auf das COM-Interface zugreifen kannst.

    Wie, wenn ich sie doch nicht finden kann? 😕

    Sorry, aber diese Fragen sind ernst gemeint. Ich bin leider kein Fortgeschrittener. Mit ein paar wenigen weiteren Tips denke ich aber, dass der Knoten bei dieser Problematik platzen könnte. Deshalb hoffe ich sehr auf eine erneute Antwort!

    Danke.



  • dermobb schrieb:

    Wie, wenn ich sie doch nicht finden kann?

    Auf der Platte sollten sich in deinem Projekt Dateien mit der Endung *.tlh und *.tli befinden.

    Da kannst du hineinschauen, was generiert wurde.
    Aber du sollst damit nichts weiter tun. Die sind implizit durch das #import in deinem Projekt enthalten.

    Minimalbeispiel in normalem C++:

    #import "Server.exe" no_namespace
    
    int main()
    {
      CoInitialize(NULL);
    
      ISchnittstellePtr pSchnittstelle( __uuidof(Schnittstelle) );
    
      pSchnittstelle->hrTuWas();
    
      pSchnittstelle = 0;
    
      CoUninitialize();
    
      return 0;
    }
    

    So geht das im Prinzip. Für weiter Fragen frag mal die Experten im MFC/VC Unterforum zwei Rubriken höher. Ich mach sowas nicht so oft ... 😉



  • Hallo,

    trotz deiner ausführlichen Hilfe - für die ich dir erneut sehr danke - habe ich es leider noch immer nicht geschafft ein COM-Objekt zu erzeugen und dessen Funktionen zu nutzen. Ich weiß nicht, wie ich das von dir gezeigte Beispiel in einer Windows Forms Anwendung mit C++ umsetzen soll.

    Zumindest konnte ich mir in den erzeugten *.tlh und *.tli den generierten Code anschauen.

    hier kurzer Auszug aus der TLI:

    #pragma once
    
    //
    // interface _CrossCommand wrapper method implementations
    //
    
    inline HRESULT _CrossCommand::Init ( IDispatch * * oParent ) {
        HRESULT _hr = raw_Init(oParent);
        if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
        return _hr;
    }
    
    inline _variant_t _CrossCommand::ConnectToCross ( _bstr_t sConnectName, short nC_Mode ) {
        VARIANT _result;
        VariantInit(&_result);
        HRESULT _hr = raw_ConnectToCross(sConnectName, nC_Mode, &_result);
        if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
        return _variant_t(_result, false);
    }
    
    inline VARIANT_BOOL _CrossCommand::GetCrossIsConnected ( ) {
        VARIANT_BOOL _result = 0;
        HRESULT _hr = get_CrossIsConnected(&_result);
        if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
        return _result;
    }
    
    inline VARIANT_BOOL _CrossCommand::ShowVar ( _bstr_t sVariableName, BSTR * sResult, long * vTimeOut ) {
        VARIANT_BOOL _result = 0;
        HRESULT _hr = raw_ShowVar(sVariableName, sResult, vTimeOut, &_result);
        if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
        return _result;
    }
    

    Da sind all die "schönen" Methoden, die ich gern nutzen möchte, in Wrapper verpackt.

    Hier noch ein Auszug der TLH:

    #pragma once
    #pragma pack(push, 8)
    
    #include <comdef.h>
    
    //
    // Forward references and typedefs
    //
    
    struct __declspec(uuid("cccace96-1b8b-4249-ae4f-5382e3e5c34f"))
    /* LIBID */ __CrossCommEXE;
    struct __declspec(uuid("5801c642-72f0-4f40-b37f-426ffe7ef39c"))
    /* dual interface */ _CrossCommand;
    struct /* coclass */ CrossCommand;
    enum Msg_Type;
    enum eOpenMode;
    enum eEditMode;
    enum eFileAttrib;
    enum eItemType;
    enum IO_TYPE;
    
    //
    // Smart pointer typedef declarations
    //
    
    _COM_SMARTPTR_TYPEDEF(_CrossCommand, __uuidof(_CrossCommand));
    
    //
    // Type library items
    //
    
    struct __declspec(uuid("83a8c026-f198-4d71-8b26-6aaad6628455"))
    CrossCommand;
        // [ default ] interface _CrossCommand
    
    enum __declspec(uuid("3124bd31-9eb0-4d49-abd1-ad6be2bcdb77"))
    Msg_Type
    {
        MELD_ADE = -1,
        MELD_H = 1,
        MELD_Q = 2,
        MELD_Z = 3
    };
    
    enum __declspec(uuid("48ebacd6-6089-4b7b-9089-71b8b6f88c03"))
    eOpenMode
    {
        eOpenModeRead = 1,
        eOpenModeWrite = 2,
        eOpenModeReadWrite = 3,
        eOpenModeExclusive = 4,
        eOpenModeData = 8,
        eOpenModeSource = 16,
        eOpenModeRaw = 32,
        eOpenModeCancel0 = 64,
        eOpenModeCancel1 = 128,
        eOpenModeReSelect0 = 256,
        eOpenModeReSelect1 = 512
    };
    
    enum __declspec(uuid("6add1c30-b6ab-44b8-8da7-cb6de008361a"))
    eEditMode
    {
        eEditModeFullEdit = 0,
        eEditModeDatKor = 1,
        eEditModeProKor = 2,
        eEditModeReadOnly = 3
    };
    
    enum __declspec(uuid("2cf2a0ab-eabd-4246-b9bd-a2cf5f54fb0c"))
    eFileAttrib
    {
        eItemAttribReadOnly = 1,
        eItemAttribSystem = 4,
        eItemAttribHidden = 2,
        eItemAttribArchiv = 32,
        eItemAttribDirectory = 16
    };
    
    enum __declspec(uuid("8560ecc3-6cb5-4d09-b096-28efc369ccb7"))
    eItemType
    {
        eItemTypeUnknown = 0,
        eItemTypeDir = 1,
        eItemTypeVirtualDir = 2,
        eItemTypeArchiv = 4,
        eItemTypeBinFile = 8,
        eItemTypeTextFile = 16,
        eItemTypeModule = 32,
        eItemTypeRaw = 64
    };
    
    enum __declspec(uuid("bd3e0663-294c-4c99-8bc8-ad1bed686efe"))
    IO_TYPE
    {
        IORestart = -1,
        IBusReset = 1,
        CanReset = 2,
        BoschReset = 3,
        PerceptronReset = 4
    };
    
    struct __declspec(uuid("5801c642-72f0-4f40-b37f-426ffe7ef39c"))
    _CrossCommand : IDispatch
    {
        //
        // Property data
        //
    
        __declspec(property(get=GetCrossError))
        long CrossError;
        __declspec(property(get=GetCrossIsConnected))
        VARIANT_BOOL CrossIsConnected;
    

    Tja also damit kann ich leider nicht viel anfangen...

    Probleme bleiben die gleichen: Wie COM-Objekt erzeugen und wie Funktionen nutzen??

    Hoffe weiterhin auf eure Hilfe! Vielleicht weiß ja jemand mit dem gezeigten Code etwas anzufangen.
    Danke



  • Tja, COM in C++ ist nicht einfach, C++/CLI auch nicht.

    Und beides zusammen ist nur was für Leute mit Erfahrung. Durch Rumprobieren wirst du bei so komplexen Dingen nichts zustande bringen.

    Warum überhaupt ein Formular in C++/CLI ? C++/CLI ist gut um die .net-Welt mit der C++-Welt zu verbinden, aber wer sich auskennt schreibt damit keine ganzen Anwendungen, sondern nur die Verbindungsstücke z.B. zwischen C++ und C#.

    Nimm C# und normale COM-Interop für dein Formular. Wenn du kein C# kannst, kannst du erst recht kein C++/CLI.

    So genug Dampf abgelassen, nun zu deiner Frage:

    Der Compiler hat genau das gemacht, was ich geschrieben habe. Er hat dir eine Wrapperklasse für deinen COM-Server gebaut.

    Da steht ganz gross:

    _COM_SMARTPTR_TYPEDEF(_CrossCommand, __uuidof(_CrossCommand));
    

    Das Ding heist jetzt entweder I_CrossCommandPtr oder ICrossCommandPtr, da bin ich mir nicht ganz sicher.

    Den kannst du so aber möglicherweise nicht in einer managed Klasse verwenden. Wahrscheinlich brauchst du dafür eine normale Klasse, die du dann wieder im managed Code verwenden kannst.

    Jede Menge Aufwand, dessen Sinn ich nicht sehe ...
    Warum nicht normale COM-Interop, die geht in C++/CLI wie in C#.

    In C++/CLI kann man die mit #import generierten Sachen auch direkt verwenden.

    Das geht irgendwie so

    #include <msclr\com\ptr.h>
    
    #import ....
    
    msclr::com::ptr pSchnittstelle;
    
    pSchnittstelle.CreateInstance(__uuidof(Schnittstelle));
    
    pSchnittstelle->TuWas();
    

    Keine Ahnung, ob das in der Express geht. Kann sein, das diese Header nur in den Kaufversionen drin sind ...



  • Hallo nochmal,
    nach einer längeren Zwangsunterbrechung möchte ich mich dem Thema wieder zuwenden...

    So habe ich das jetzt mal nach den letzten Tips versucht:

    msclr::com::ptr<_CrossCommand> pSchnittstelle;
    pSchnittstelle.CreateInstance(__uuidof(_CrossCommand));
    

    Resultat:

    Eine nicht behandelte Ausnahme des Typs "System.Runtime.InteropServices.COMException" ist in mscorlib.dll aufgetreten.

    Zusätzliche Informationen: Klasse nicht registriert (Ausnahme von HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))

    Weiß jemand etwas damit anzufangen? Klasse nicht registriert 😕
    HELP ⚠



  • dermobb schrieb:

    Klasse nicht registriert 😕

    Das ist kein Fehler in deinem Programm. Der COM-Server wird nicht in der Registry gefunden. Eventuell ist er nicht richtig installiert.



  • Ok, ich habe nach Anleitung in diesem Link versucht, den (EXE)-Server manuell zu registrieren (ich hoffe, dass du das mit installieren meinst). Die Fehlermeldung bleibt bei anschließender Ausführeng des Clients jedoch die gleiche...

    Ich komm schon wieder nicht selbst aus der Situation... 😞

    Kann es nicht doch ein Programmfehler sein?
    Was passiert eigentlich, wenn ich die EXE des Servers einfach ausführe (doppelklicke)?

    Danke


Anmelden zum Antworten