Search code examples
c++comole

C++ creating an instance of a COM class


I use this code:

EDIT:

interface __declspec(uuid("{325ADA34-FF89-4AAB-BB14-A81A07F8C791}")) IRegister : public IDispatch
{
public:
    virtual bool IsRegister(char* address) = 0;
    virtual void Register(char* address) = 0;
};
IRegister* pBox;

  void Test1()
{
    pBox->Register("test");
    auto a = pBox->IsRegister("test");
}

extern "C"  int _tmain(int argc, _TCHAR * argv[])
{

    CoInitialize(NULL);

    CLSID clsid;
    HRESULT hr1 = CLSIDFromProgID(OLESTR("GameCheater.COM.RegisterManager"), &clsid);

    HRESULT hr = CoCreateInstance(
        clsid,
        NULL,
        CLSCTX_LOCAL_SERVER,
        IID_PPV_ARGS(&pBox)
    );

    
    if (FAILED(hr))
    {
        printf("Couldn't create the instance!...");
    }
    else
    {
        Test1();
    }
    
    return 0;
}

The handler is created, but when I try to use the class, I received this error:

image

This working perfect in C#:

image

I use this code to create the com class:

    [ComVisible(true)]
    [Guid("325ADA34-FF89-4AAB-BB14-A81A07F8C791")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IRegister
    {
        [DispId(0)]
        bool IsRegister(string address);
        [DispId(1)]
        void Register(string address);
    }
    [ComVisible(true)]
    [Guid("DA27DEC3-6B10-452E-B9B0-0AAC8C86D892")]
    [ProgId("GameCheater.COM.RegisterManager")]
    [ClassInterface(ClassInterfaceType.None)]
    public class RegisterManager : IRegister
    {
        public bool IsRegister(string address)
        {
            return RegisterPull.dictionary.TryGetValue(address, out _);
        }

        public void Register(string address)
        {

            var clientDetails = new ClientDetails()
            {
                Address = address,
                LastRegister = DateTime.Now
            };

            if (RegisterPull.dictionary.ContainsKey(address))
                RegisterPull.dictionary[address] = clientDetails;
            else
                RegisterPull.dictionary.TryAdd(address, clientDetails);
        }

        public RegisterManager()
        {

        }

        ~RegisterManager()
        {

        }
    }

How I can solve the error? How can I cast the class with C++ class?

I searched for many tutorials about this, but I don't found anything.


Solution

  • All COM interfaces derive from IUnknown, and sometimes from IDispatch. Your interface needs to do the same, according to the requirements of the COM object you are trying to use, eg:

    interface RegisterManager : public IUnknown { // or IDispatch
        ...
    };
    

    Also, interface methods need to be declared as pure virtual (ie abstract), eg:

    public:
        virtual bool IsRegister(char* address) = 0;
        virtual void Register(char* address) = 0;
    

    Also, you don't need the InterfaceDispatcher class just to use __declspec(uuid). You can (and should) apply the uuid directly to the interface, eg:

    interface __declspec(uuid("{325ADA34-FF89-4AAB-BB14-A81A07F8CE71}")) RegisterManager : public IUnknown {
        ...
    };
    

    In which case, your call to CoCreateInstance() can then make use of the IID_PPV_ARGS macro, eg:

    HRESULT hr = CoCreateInstance(
            clsid,
            NULL,
            CLSCTX_LOCAL_SERVER,
            IID_PPV_ARGS(&pBox)
        );
    

    Also, you are calling Test1() even if CoCreateInstance() fails, which will cause your code to crash. Don't do that, eg:

    HRESULT hr = CoCreateInstance(...);
    
    if (SUCCEEDED(hr))
        Test1();
    else
        printf("Couldn't create the instance, error: 0x%08x", hr);
    

    UPDATE:

    Now that you have posted your C# code, I see that your IRegister interface is marked as InterfaceIsIDispatch, which means it derives from IDispatch rather than IUnknown (also, its methods are marked with DispId, so they are callable via the IDispatch.Invoke() method).

    Unfortunately, being marked as InterfaceIsIDispatch also means the methods are accessible ONLY by IDispatch.Invoke() (ie, late binding), so they are much more complicated to call then using the IRegister interface directly (ie, early binding).

    Per the InterfaceTypeAttribute documentation:

    By default, the Tlbexp.exe (Type Library Exporter) exposes a managed interface to COM as a dual interface, giving you the flexibility of late binding or the performance of early binding. The ComInterfaceType enumeration enables you to override the default behavior and specify late binding only or early binding only. For example, you can apply InterfaceType (ComInterfaceType.InterfaceIsIDispatch) to an interface to produce metadata to restrict callers to late binding only. Although interfaces that derive from the IDispatch interface are often dual, the InterfaceIsIDispatch enumeration member allows only late-bound calls to the interface methods. This attribute has no effect on the managed view of the interface. For additional information on how interfaces are exposed to COM, see Exported Type Conversion.

    So, for example (error handling omitted for brevity):

    IDispatch* pBox;
    
    void Test1()
    {
        VARIANTARG arg;
        VariantInit(&arg);
        V_VT(&arg) = VT_BSTR;
        V_BSTR(&arg) = SysAllocString(OLESTR("test"));
    
        DISPPARAMS param = {};
        param.rgvarg = &arg;
        param.cArgs = 1;
    
        DISPID dispid;
    
        //pBox->Register("test");
        LPCWSTR name = OLESTR("Register");
        pBox->GetIDsOfNames(IID_NULL, (LPOLESTR*)&name, 1, 0, &dispid);
        pBox->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &param, NULL, NULL, NULL);
    
        //auto a = pBox->IsRegister("test");
        name = OLESTR("IsRegister");
        pBox->GetIDsOfNames(IID_NULL, (LPOLESTR*)&name, 1, 0, &dispid);
        VARIANT a;
        VariantInit(&a);
        pBox->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &param, &a, NULL, NULL);
        VariantClear(&a);
    
        VariantClear(&arg);   
    }
    
    extern "C" int _tmain(int argc, _TCHAR * argv[])
    {
        CoInitialize(NULL);
    
        CLSID clsid;
        HRESULT hr1 = CLSIDFromProgID(OLESTR("GameCheater.COM.RegisterManager"), &clsid);
    
        HRESULT hr = CoCreateInstance(
            clsid,
            NULL,
            CLSCTX_LOCAL_SERVER,
            IID_PPV_ARGS(&pBox)
        );
      
        if (FAILED(hr))
        {
            printf("Couldn't create the instance!...");
        }
        else
        {
            Test1();
            pBox->Release();
        }
        
        CoUninitialize();
        return 0;
    }
    

    Refer to Accessing Members Through IDispatch for more details.