Excel OpenText method - c++

I keep getting the ambiguous error code of 0x800A03EC.
I've been searching quite a bit to see if I could find a specific reason for the error but unfortunately that code seems to cover a multitude of possible errors. I will copy and paste the code that seems to be giving me problems and hopefully someone will be able to provide me with some feedback on how I might solve the problem. I am using a method called AutoWrap that I came across in this kb21686 article.I'll add that method here:
HRESULT AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp, LPOLESTR ptName, int cArgs...) {
// Begin variable-argument list...
va_list marker;
va_start(marker, cArgs);
if(!pDisp) {
//MessageBox(NULL, "NULL IDispatch passed to AutoWrap()", "Error", 0x10010);
MessageBox(NULL,_T("IDispatch error"),_T("LError"),MB_OK | MB_ICONEXCLAMATION);
_exit(0);
}
// Variables used...
DISPPARAMS dp = { NULL, NULL, 0, 0 };
DISPID dispidNamed = DISPID_PROPERTYPUT;
DISPID dispID;
HRESULT hr;
char buf[200];
char szName[200];
// Convert down to ANSI
WideCharToMultiByte(CP_ACP, 0, ptName, -1, szName, 256, NULL, NULL);
// Get DISPID for name passed...
hr = pDisp->GetIDsOfNames(IID_NULL, &ptName, 1, LOCALE_USER_DEFAULT, &dispID);
if(FAILED(hr)) {
sprintf_s(buf, "IDispatch::GetIDsOfNames(\"%s\") failed w/err 0x%08lx", szName, hr);
MessageBox(NULL, CString(buf), _T("AutoWrap()"), MB_OK | MB_ICONEXCLAMATION);
_exit(0);
return hr;
}
// Allocate memory for arguments...
VARIANT *pArgs = new VARIANT[cArgs+1];
// Extract arguments...
for(int i=0; i<cArgs; i++) {
pArgs[i] = va_arg(marker, VARIANT);
}
// Build DISPPARAMS
dp.cArgs = cArgs;
dp.rgvarg = pArgs;
// Handle special-case for property-puts!
if(autoType & DISPATCH_PROPERTYPUT) {
dp.cNamedArgs = 1;
dp.rgdispidNamedArgs = &dispidNamed;
}
// Make the call!
hr = pDisp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT, autoType, &dp, pvResult, NULL, NULL);
if(FAILED(hr)) {
sprintf_s(buf, "IDispatch::Invoke(\"%s\"=%08lx) failed w/err 0x%08lx", szName, dispID, hr);
MessageBox(NULL, CString(buf), _T("AutoWrap()"), MB_OK | MB_ICONEXCLAMATION);
_exit(0);
return hr;
}
// End variable-argument section...
va_end(marker);
delete [] pArgs;
return hr;
}
Everything works fine up until I make this call:
AutoWrap(DISPATCH_PROPERTYGET, &result, pXlBooks, L"OpenText",18,param1,vtMissing,vtMissing,paramOpt,paramOpt,
vtMissing,vtMissing,vtMissing,paramTrue,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing
,vtMissing,vtMissing);
The parameters passed to the function are initialized as:
VARIANT param1,paramOpt,paramFalse,paramTrue;
param1.vt = VT_BSTR;
paramOpt.vt = VT_I2;
paramOpt.iVal = 1;
paramFalse.vt = VT_BOOL;
paramFalse.boolVal = 0;
paramTrue.vt = VT_BOOL;
paramTrue.boolVal = 1;
//param1.bstrVal = ::SysAllocString(L"C:\\Documents and Settings\\donaldc\\My Documents\\DepositSlip.xls");
param1.bstrVal = ::SysAllocString(L"C:\\logs\\TestOut.txt");
If I uncomment the commented out param1 and make a call to Open and pass it that version of param1 everything works wonderfully. Unfortunately when Invoke is called on the OpenText method I get the 0x800A03EC error code. 90% of what I find when searching is performing automation using interop in C# and the other 10% is doing the same thing in VB and while the C# examples are helpful they don't help to explain the parameters being passed when using C++ very well. I feel like it's all a problem with parameters but I'm having difficulty in figuring out exactly what the problem with them is.
Thanks in advance for any help you can offer and pelase let me know if I need to post more code.

From the KB article you linked to:
One caveat is that if you pass
multiple parameters, they need to be
passed in reverse-order.
From MSDN, the parameters to OpenText are:
expression.OpenText(Filename, Origin, StartRow, DataType,
TextQualifier, ConsecutiveDelimiter, Tab, Semicolon, Comma,
Space, Other, OtherChar, FieldInfo, TextVisualLayout, DecimalSeparator,
ThousandsSeparator, TrailingMinusNumbers, Local)
So if param1 holds your filename then you are currently trying to pass that as the Local parameter and you aren't passing anything to the Filename parameter which is required

Even though this question was put ages ago, I want to answer it.
I struggled much through the day with this, but got it done eventually.
Code 0x800a03ec is ambiguous, it means essentially, (NO OFFENSE MEANT!), you dumbhead! you did something pretty stupid, try finding it out for yourself. Close to the old "syntax error" without further elucidation.
So, there is no single meaning for code 0x800a03ec and, googling around, you can see it occurs when using zero based addressing in ranges, and many other gotcha situations.
The gotcha in this case is that you have to pass the parameters to a DISPATCH_METHOD call IN REVERSE. Obviously, with a single parameter this will not lead to trouble, and many dispatch calls can do with a single parameter.
So, when I want to open a workbook with just the filename, everything is OK.
AutoWrap(DISPATCH_METHOD, &Result, pExcelWorkbooks, "Open", 1, fn);
But e.g. the following code does not work:
_variant_t fn("MyExcelBook.xlsx"), updatelinks(0), readonly(true);
VARIANT Result;
hr = Autowrap(DISPATCH_METHOD, &Result, pExcelWorkbooks, "Open", 3, fn, updatelinks, readonly);
It produces 0x800a03ec and won't load the workbook.
To amend this without rewriting all your calls, the AutoWrap function should be extended as follows:
// Allocate memory for arguments..
VARIANT * pArgs = new VARIANT[cArgs + 1];
// Extract arguments..
if (autoType & DISPATCH_METHOD)
{ // reverse (variable) DISPATCH parameters after cArgs
for (int i = 1; i <= cArgs; i++)
pArgs[cArgs-i] = va_arg(marker, VARIANT);
}
else
{
for (int i = 0; i < cArgs; i++)
pArgs[i] = va_arg(marker, VARIANT);
}
(only relevant part shown, see earlier post for the whole method).
So now I call the C++ version of workbooks.open()
_variant_t fn("MyExcelBook.xlsx"), updatelinks(0), readonly(true), optional(DISP_E_PARAMNOTFOUND, VT_ERROR);
VARIANT Result; readonly,
hr = Autowrap(DISPATCH_METHOD, &Result, pExcelWorkbooks, "Open", 7, fn, updatelinks,
optional, optional, optional, readonly);
// copy the dispatch pointer to the workbook pointer
if (Result.vt == VT_DISPATCH)
{
pExcelWorkbook = Result.pdispVal;
// save the workbook pointer to close it later if needed
if (pExcelWorkbook)
IDworkBooks.push_back(pExcelWorkbook);
}
As you can see, you do not have to fill in the tail options you are not going to use anyway, as in some VB code and C#.
Happy coding,
Jan
PS, I see the reverse thing was noted before (see above).. I couldn't figure out where I had seen it until now...

Related

How to Identify an windows process with a certain executable and command line argument is running or not using C++? [duplicate]

I am trying to get another process' command-line parameters (on WinXP 32bit).
I do the following:
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, ProcList.proc_id_as_numbers[i]);
BytesNeeded = sizeof(PROCESS_BASIC_INFORMATION);
ZwQueryInformationProcess(hProcess, ProcessBasicInformation, UserPool, sizeof(PROCESS_BASIC_INFORMATION), &BytesNeeded);
pbi = (PPROCESS_BASIC_INFORMATION)UserPool;
BytesNeeded = sizeof(PEB);
res = ZwReadVirtualMemory(hProcess, pbi->PebBaseAddress, UserPool, sizeof(PEB), &BytesNeeded);
/* zero value returned */
peb = (PPEB)UserPool;
BytesNeeded = sizeof(RTL_USER_PROCESS_PARAMETERS);
res = ZwReadVirtualMemory(hProcess, peb->ProcessParameters, UserPool, sizeof(RTL_USER_PROCESS_PARAMETERS), &BytesNeeded);
ProcParam = (PRTL_USER_PROCESS_PARAMETERS)UserPool;
After the first call, pbi.UniqueProcessID is correct.
But, after calling ZwReadVirtualMemory(), I get the command-line for my process, not the requested one.
I also used ReadProcessMemory() & NtQueryInformationProcess(), but get the same result.
Can anybody help?
On this forum thread, it is said that this code works. Unfortunately, I do not have access to post on that forum to ask them.
It looks like ZwReadVirtualMemory is called only once. That is not enough. It has to be called for each level of pointer indirection. In other words when you retrieve a pointer it points to other process' address space. You cannot read it directly. You have to call ZwReadVirtualMemory again. For the case of those data structures ZwReadVirtualMemory has to be called 3 times: once to read PEB (that is what the code above does), once to read RTL_USER_PROCESS_PARAMETERS and once to read UNICODE_STRING's buffer.
The following code fragment worked for me (error handling omitted for clarity and I used documented ReadProcessMemory API instead of ZwReadVirtualMemory):
LONG status = NtQueryInformationProcess(hProcess,
0,
pinfo,
sizeof(PVOID)*6,
NULL);
PPEB ppeb = (PPEB)((PVOID*)pinfo)[1];
PPEB ppebCopy = (PPEB)malloc(sizeof(PEB));
BOOL result = ReadProcessMemory(hProcess,
ppeb,
ppebCopy,
sizeof(PEB),
NULL);
PRTL_USER_PROCESS_PARAMETERS pRtlProcParam = ppebCopy->ProcessParameters;
PRTL_USER_PROCESS_PARAMETERS pRtlProcParamCopy =
(PRTL_USER_PROCESS_PARAMETERS)malloc(sizeof(RTL_USER_PROCESS_PARAMETERS));
result = ReadProcessMemory(hProcess,
pRtlProcParam,
pRtlProcParamCopy,
sizeof(RTL_USER_PROCESS_PARAMETERS),
NULL);
PWSTR wBuffer = pRtlProcParamCopy->CommandLine.Buffer;
USHORT len = pRtlProcParamCopy->CommandLine.Length;
PWSTR wBufferCopy = (PWSTR)malloc(len);
result = ReadProcessMemory(hProcess,
wBuffer,
wBufferCopy, // command line goes here
len,
NULL);
Why we see see the command line of our own process? That is because processes are laid out in a similar way. Command line and PEB-related structures are likely to have the same addresses. So if you missed ReadProcessMemory you end up exactly with local process' command line.
I was trying to do this same thing using mingw & Qt. I ran into a problem with "undefined reference to CLSID_WbemLocator". After some research, it seems that the version of libwbemuuid.a which was included with my version of mingw only defined IID_IWbemLocator but not CLSID_WbemLocator.
I found that manually defining CLSID_WbemLocator works (although its probably not the "correct" way of doing things).
The final working code:
#include <QDebug>
#include <QString>
#include <QDir>
#include <QProcess>
#define _WIN32_DCOM
#include <windows.h>
#include "TlHelp32.h"
#include <stdio.h>
#include <tchar.h>
#include <wbemidl.h>
#include <comutil.h>
const GUID CLSID_WbemLocator = { 0x4590F811,0x1D3A,0x11D0,{ 0x89,0x1F,0x00,0xAA,0x00,0x4B,0x2E,0x24 } }; //for some reason CLSID_WbemLocator isn't declared in libwbemuuid.a (although it probably should be).
int getProcessInfo(DWORD pid, QString *commandLine, QString *executable)
{
HRESULT hr = 0;
IWbemLocator *WbemLocator = NULL;
IWbemServices *WbemServices = NULL;
IEnumWbemClassObject *EnumWbem = NULL;
//initializate the Windows security
hr = CoInitializeEx(0, COINIT_MULTITHREADED);
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &WbemLocator);
//connect to the WMI
hr = WbemLocator->ConnectServer(L"ROOT\\CIMV2", NULL, NULL, NULL, 0, NULL, NULL, &WbemServices);
//Run the WQL Query
hr = WbemServices->ExecQuery(L"WQL", L"SELECT ProcessId,CommandLine,ExecutablePath FROM Win32_Process", WBEM_FLAG_FORWARD_ONLY, NULL, &EnumWbem);
qDebug() << "Got here." << (void*)hr;
// Iterate over the enumerator
if (EnumWbem != NULL) {
IWbemClassObject *result = NULL;
ULONG returnedCount = 0;
while((hr = EnumWbem->Next(WBEM_INFINITE, 1, &result, &returnedCount)) == S_OK) {
VARIANT ProcessId;
VARIANT CommandLine;
VARIANT ExecutablePath;
// access the properties
hr = result->Get(L"ProcessId", 0, &ProcessId, 0, 0);
hr = result->Get(L"CommandLine", 0, &CommandLine, 0, 0);
hr = result->Get(L"ExecutablePath", 0, &ExecutablePath, 0, 0);
if (ProcessId.uintVal == pid)
{
*commandLine = QString::fromUtf16((ushort*)(long)CommandLine.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
*executable = QString::fromUtf16((ushort*)(long)ExecutablePath.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
qDebug() << *commandLine << *executable;
}
result->Release();
}
}
// Release the resources
EnumWbem->Release();
WbemServices->Release();
WbemLocator->Release();
CoUninitialize();
//getchar();
return(0);
}
and in my Qt project file (.pro) I link to the following libraries:
LIBS += -lole32 -lwbemuuid
Duplicate of How to query a running process for it's parameters list? (windows, C++) , so I'll just copy my answer from there over here:
You can't reliably get that information. There are various tricks to try and retrieve it, but there's no guarantee that the target process hasn't already mangled that section of memory. Raymond Chen discussed this awhile back on The Old New Thing.
You need to be more disciplined with checking return codes. It may be that any of your ZwReadVirtualMemory calls yield an error code which points you into the right direction.
In particular, the ProcList.proc_id_as_numbers[i] part suggests that you're executing this code in a loop. Chances are that the procPeb.ProcessParameters structure is still filled with the values of an earlier loop iteration - and since the ZwReadVirtualMemory call fails on your target process, you get to see the command line of whatever process was previously queried.
You don't have to read the VM of the target process to do this. Just make sure you have the correct Process ID for the target process.
Once you have the process handle via OpenProcess, you can then use NtQueryInformationProcess to get detailed process info. Use the ProcessBasicInformation option to get the PEB of the process - this contains another structure pointer RTL_USER_PROCESS_PARAMETERS, through which you can get the command line.

IShellDispatch NameSpace-call fails, don't know why

First time working with COM from C++, been using C# and Delphi before.
So here's the code
HRESULT Result;
Result = CoInitialize(nullptr);
if (!SUCCEEDED(Result))
return Result;
IShellDispatch* API;
Result = CoCreateInstance(CLSID_Shell, nullptr, CLSCTX_INPROC_SERVER, IID_IShellDispatch, (void **)&API);
if ((!SUCCEEDED(Result)) || (nullptr == API))
goto Cleanup;
VARIANT Source1, Source2, Destination, Options;
VariantInit(&Destination);
Destination.vt = VT_BSTR;
Destination.bstrVal = SysAllocString(OutFile.c_str());
Folder* PDestination;
Result = API->NameSpace(Destination, &PDestination);
if (!SUCCEEDED(Result))
goto Cleanup;
VariantInit(&Source1);
VariantInit(&Source2);
Source1.vt = VT_BSTR;
Source2.vt = VT_BSTR;
Source1.bstrVal = SysAllocString(InFile1.c_str());
Source2.bstrVal = SysAllocString(InFile2.c_str());
VariantInit(&Options);
Options.vt = VT_I4;
Options.lVal = FOF_NO_UI;
PDestination->CopyHere(Source1, Options);
Sleep(1000);
PDestination->CopyHere(Source2, Options);
Sleep(1000);
PDestination->Release();
API->Release();
Cleanup:
CoUninitialize();
return Result;
OutFile, InFile1 and InFile2 are of type wstring as the program is (mostly) C++. I've managed to create an empty ZIP file, that's not the issue. The issue is that I get an exception claiming that PDestination is nullptr.
If it helps, the variants themselves seems to work. Inspecting them in the debugger yields the paths I provided. I've noticed though how the NameSpace-call returns an S_FALSE. Does anyone know what I can do about that?
Using a library doesn't exactly work, it's corporate code and there's some truly draconian rules when it comes to linking external code so using the Windows API is the easiest way.

Data being lost when sent via a different thread over COM server - previously worked in VC6, now broken in VS2010

I have been bit by a lot of little problems in conversion from VC6 to VS2010, but having problems solving this one. Below is the delegate used when sending data from my class to the COM server.
EDIT 2: Added code to check varResult.
HRESULT Fire_Msg(BSTR Msg)
{
CComVariant varResult;
T* pT = static_cast<T*>(this);
int nConnectionIndex;
CComVariant* pvars = new CComVariant[1];
int nConnections = m_vec.GetSize();
for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
{
pT->Lock();
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
pT->Unlock();
IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
pvars[0] = Msg;
DISPPARAMS disp = { pvars, NULL, 1, 0 };
pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
}
}
delete [] pvars;
// Changed to display what happens to varResult.
if (varResult.scode != S_OK)
{
// Do some error handling... (later)
return varResult.scode;
}
else
{
return varResult.scode;
}
}
EDIT 2: From either working or non-working, varResult returns S_OK. No difference.
When I break on this line, when sent from the main process or a thread, the data looks correct:
pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, NULL, NULL, NULL);
Edit:
I have pictures of working and non-working. It really just shows that when called from a thread started from threadex, the data is properly set in "disp" and there is no problem doing the "pDispatch->Invoke"..
Working:
Non-working:
:End Edit
In the other application, I have the following code to answer the dispatched data:
BEGIN_DISPATCH_MAP(CGenRTEvent, CCmdTarget)
//{{AFX_DISPATCH_MAP(CGenRTEvent)
DISP_FUNCTION(CGenRTEvent, "OnMsgStr", OnMsgStr, VT_EMPTY, VTS_BSTR)
DISP_FUNCTION(CGenRTEvent, "OnMsg", OnMsg, VT_EMPTY, VTS_BSTR)
DISP_FUNCTION(CGenRTEvent, "OnEndRun", OnEndRun, VT_EMPTY, "")
DISP_FUNCTION(CGenRTEvent, "OnProgress", OnProgress, VT_EMPTY, VTS_I4 VTS_BSTR)
DISP_FUNCTION(CGenRTEvent, "OnISocket", OnISocket, VT_EMPTY, VTS_BSTR)
// DISP_FUNCTION(CGenRTEvent, "OnMsg", OnMsg, VT_EMPTY, VTS_PVARIANT)
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
BEGIN_INTERFACE_MAP(CGenRTEvent, CCmdTarget)
INTERFACE_PART(CGenRTEvent, DIID__IGenRTEvents, Dispatch)
END_INTERFACE_MAP()
void CGenRTEvent::OnMsgStr(LPCTSTR strMsg)
{
m_pFrame->OnMsgStr(strMsg);
}
When used from the main thread (inside the first application), OnMsgStr is properly raised. When done from a different thread, OnMsgStr is never raised. This had been working solidly in VC6, but VS2010 doesn't like it.
Edit: My question is, how can I prevent the data from being lost? I am fairly sure the reason is tied to calling this from a different thread, but don't know how to fix it.
Any suggestions or input is appreciated?
Thanks in advance.
With Paulo Madeira's generous help, I was able to solve this by searching KB280512 which led me to a project called GPSCom written by PJ Naughter. Link is here http://www.naughter.com/gpscom2.html. I downloaded his code and he offers a freeware file named "ATLCPImplMT.h" for use in any form as long as it is unmodified. Based on his example, I modified the code as follows:
HRESULT Fire_Msg(BSTR Msg)
{
CComCritSecLock<CComAutoCriticalSection> lock(m_CPMTCritSec);
ATL::CComVariant varResult;
int nConnections = m_Clients.GetSize();
int nConnectionIndex;
ATL::CComVariant* pvars = new ATL::CComVariant[1];
for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
{
ATL::CComPtr<IDispatch> sp;
if (SUCCEEDED(GetInterfaceAt(nConnectionIndex, sp)))
{
VariantClear(&varResult);
pvars[0] = Msg;
DISPPARAMS disp = { pvars, NULL, 1, 0 };
sp->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
if (varResult.scode != S_OK)
{
// Do some error handling... (later)
return varResult.scode;
}
}
}
delete [] pvars;
return varResult.scode;
}
This question is a possible duplicate of the one asked here: ATL COM: Access Event Methods From Other Thread. But to be fair, when I started my searching - I wasn't aware of the right words to search.
Thanks.

Proper way of using IXMLDOMDocument

I am trying to use IXMLDOMDocument for XML reading/writing. I am not good COM, and I am not aware that I am doing things right or wrong. I am very unsure that there could be some problems with COM initialization and relase. Here is my code below and let me know If there are any possibles bugs / memory leaks here due to COM.
void MyClass::ReadXML(BSTR *pVal)
{
IXMLDOMDocument * pXMLDoc;
IXMLDOMNode * pXDN;
HRESULT hr = CoInitialize(NULL);
hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER,
IID_IXMLDOMDocument, (void**)&pXMLDoc);
if (SUCCEEDED(hr))
{
IXMLDOMNode* pEntityNode = CDOMHelpers::InsertDOMElement(pDoc, NULL, L"Person", NULL);
if (SUCCEEDED(hr))
{
SomeClassObject->SerializeXML(pXMLDoc, pXDN);
pXMLDoc->get_xml(pVal);
pXDN->Release(); // Is this proper way to release COM?
pXDN = NULL;
pXMLDoc->Release();
pXMLDoc = NULL;
}
}
}
void SomeOtherClass::SerializeXML(IXMLDOMDocument* pDoc, IXMLDOMNode* pXDN)
{
CStringW text;
IXMLDOMNode* pNewNode;
text.Format(L"%u", Name);
pNewNode = CDOMHelpers::InsertDOMElement(pDoc, pEntityNode, L"Name", text);
text.Format(L"%u", Address);
pNewNode = CDOMHelpers::InsertDOMElement(pDoc, pEntityNode, L"Address", text);
}
In MyClass::ReadXML it is potentially dangerous to call CoInitialize. The Caller could have already called it on this thread, which leads to problems, when you do not check the return value.
You need to call CoUnInitialize for each successful CoInitialize:
S_OK meaning "OK",
S_FALSE meaning "OK, was already initialized" or
RPC_E_CHANGED_MODE meaning "ERROR, Can not change to new mode" and the reference-count is not incremented. You can ignore this, when you do not actively require appartment mode or multithreaded mode, but you must not call CoUnInitialize when you do.
So, the best way would be a RAII-Object whose c'tor calls CoInitialize and monitors the return value, and whose d'tor calls CoUnInitialize only when needed.
Reference:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms695279%28v=vs.85%29.aspx

Problems accessing a COM interface in C++

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