Problems accessing a COM interface in C++

2.6k views Asked by At

What I want to do is access a COM interface and then call the "Open" method of that interface. I have a sample code in Visual Basic which works fine, but I need to write it in C++ and I can't seem to get it to work.

First of all, this is the working VB code:

Dim CANapeApplication As CANAPELib.Application
CANapeApplication = CreateObject("CANape.Application")
Call CANapeApplication.Open("C:\Users\Public\Documents\Vector\CANape\12\Project", 0)

CANape.Application is the ProgID which selects the interface I need.

After reading some docs at msdn.microsoft.com and this question, I wrote this code:

void ErrorDescription(HRESULT hr); //Function to output a readable hr error
int InitCOM();
int OpenCANape();

// Declarations of variables used.
HRESULT hresult;
void **canApeAppPtr;
IDispatch *pdisp;
CLSID ClassID;
DISPID FAR dispid;
UINT nArgErr;
OLECHAR FAR* canApeWorkingDirectory = L"C:\\Users\\Public\\Documents\\Vector\\CANape\\12\\Project";

int main(){

    // Instantiate CANape COM interface
    if (InitCOM() != 0) {
        std::cout << "init error";
        return 1;
    }

    // Open CANape
    if (OpenCANape() != 0) {
        std::cout << "Failed to open CANape Project" << std::endl;
        return 1;
    }
    CoUninitialize();
    return 0;
}


void ErrorDescription(HRESULT hr) { 
    if(FACILITY_WINDOWS == HRESULT_FACILITY(hr)) 
        hr = HRESULT_CODE(hr); 
    TCHAR* szErrMsg; 

    if(FormatMessage( 
        FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, 
        NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
        (LPTSTR)&szErrMsg, 0, NULL) != 0) 
    { 
        _tprintf(TEXT("%s"), szErrMsg); 
        LocalFree(szErrMsg); 
    } else 
        _tprintf( TEXT("[Could not find a description for error # %#x.]\n"), hr); 
}

int InitCOM() {
    // Initialize OLE DLLs.
    hresult = OleInitialize(NULL);
    if (!SUCCEEDED(hresult)) {
        ErrorDescription(hresult);
        return 1;
    }
    // Get CLSID from ProgID
    //hresult = CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID);
    hresult = CLSIDFromProgID(OLESTR("CanapeCom.CanapeCom"), &ClassID);
    if (!SUCCEEDED(hresult)) {
        ErrorDescription(hresult);
        return 1;
    }
    // OLE function CoCreateInstance starts application using GUID/CLSID
    hresult = CoCreateInstance(ClassID, NULL, CLSCTX_LOCAL_SERVER,
        IID_IDispatch, (void **)&pdisp);
    if (!SUCCEEDED(hresult)) {
        ErrorDescription(hresult);
        return 1;
    }
    // Call QueryInterface to see if object supports IDispatch
    hresult = pdisp->QueryInterface(IID_IDispatch, (void **)&pdisp);
    if (!SUCCEEDED(hresult)) {
        ErrorDescription(hresult);
        return 1;
    }

    std::cout << "success" << std::endl;
    return 0;
}

int OpenCANape() {
    //Method name
    OLECHAR *szMember = L"Open";
    // Retrieve the dispatch identifier for the Open method
    // Use defaults where possible
    DISPID idFileExists;
    hresult = pdisp->GetIDsOfNames(
        IID_NULL,
        &szMember,
        1,
        LOCALE_SYSTEM_DEFAULT,
        &idFileExists);
    if (!SUCCEEDED(hresult)) {
        std::cout << "GetIDsOfNames: ";
        ErrorDescription(hresult);
        return 1;
    }

    unsigned int puArgErr = 0;

    VARIANT VarResult;

    VariantInit(&VarResult); 

    DISPPARAMS pParams;
    memset(&pParams, 0, sizeof(DISPPARAMS)); 
    pParams.cArgs = 2; 

    VARIANT Arguments[2];
    VariantInit(&Arguments[0]); 

    pParams.rgvarg = Arguments; 
    pParams.cNamedArgs = 0;
    pParams.rgvarg[0].vt = VT_BSTR;
    pParams.rgvarg[0].bstrVal = SysAllocString(canApeWorkingDirectory);
    pParams.rgvarg[1].vt = VT_INT;
    pParams.rgvarg[1].intVal = 0; // debug mode

    // Invoke the method. Use defaults where possible.
    hresult = pdisp->Invoke(  
        dispid,
        IID_NULL,
        LOCALE_SYSTEM_DEFAULT,
        DISPATCH_METHOD,
        &pParams,
        &VarResult,
        NULL,
        &puArgErr
        );

    SysFreeString(pParams.rgvarg[0].bstrVal);

    if (!SUCCEEDED(hresult)) {
        ErrorDescription(hresult);
        return 1;
    }
    return 0;
}

There are several problems with this.

  • Using the ClassID received from CLSIDFromProgID as the first parameter of CoCreateInstance does not work, it returns the error: class not registered
  • If i use the ProgID CanapeCom.CanapeCom (I found it by looking in the Registry), CoCreateInstance works. However, when I use pdisp->GetIDsOfNames I get the error message: Unkown name. Which I think means that the method was not found. That seems logical because I've used a different ProgID, but I just can't figure out how to get to the interface I'm looking for.
  • I have also tried to use the resulting CLSID from CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID); as the 4th argument of CoCreateInstance but that resulted in a "No such interface supported" error.

Do I need the dll file of the software? In the VB example the dll file is used to get the interface and then create a new object using the ProgID. I'm not sure if I need to do the same in C++ or how this should work.

I'm really stuck here and hope that someone can help me.

1

There are 1 answers

0
Black-Pixel On BEST ANSWER

Thanks for your comments. I've fixed the problem, although the solution is kind of embarrassing... In my defense, I'm still a student and new to this kind of stuff.

I've used the Process Monitor to check what happens when I execute the VB script. I saw that the CLSID used there is the ID returned by CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID);, which meant that this had to be the right one and the problem had to be somewhere else. I've looked again at the CoCreateInstance and then took a look at the other parameters. Turns out that the context CLSCTX_LOCAL_SERVER was wrong, it has to be CLSCTX_INPROC_SERVER. I don't know why I've set it to local_server in the first place or why I've never questioned it. I wrote that part of the code a few days ago and then focused too much on the CLSID and IID rather than on the other parameters. I've also taken the first comment from Alex into account and created a tlb file.

This is a simplified version of the code that works:

#import "CANape.tlb"

int _tmain(int argc, _TCHAR* argv[])
{
    _bstr_t path = "C:\\Users\\Public\\Documents\\Vector\\CANape\\12\\Project";
    CLSID idbpnt; 

    CoInitialize(NULL); 

    HRESULT hr = CLSIDFromProgID (L"CANape.Application", &idbpnt); 
    CANAPELib::IApplication *app; 
    hr = CoCreateInstance(idbpnt,NULL,CLSCTX_INPROC_SERVER,__uuidof(CANAPELib::IApplication),(LPVOID*)&app ); 
    app->Open(path,0);
    CoUninitialize();
    return 0; 
}