Programmatically copy a cell from Excel using VC++ - c++

Without:
MFC
ATL
using COM, with pure C++, steps taken thus far:
//steps above omitted
_ApplicationPtr application(__uuidof(Excel::Application));
//omitted
const BSTR wcharFileName = SysAllocString(L"...");
application->Workbooks->Open(wcharFileName);
application->put_Visible(10, true);
Question:
How to then copy a cell, for instance A4, B4 and C4, into an array?

Try this http://support.microsoft.com/kb/216388/en-us

I earlier posted that
"For Excel - COM using C++
http://shaktisaran.tech.officelive.com/ExcelCOM.aspx
It also has Windows Programming tutorials."
I've deleted that post because it wouldn't help easily.
I'm providing more info related to example on web-site,
In ExcelProcessor.cpp
ReadRange function reads a range of Excel cells but you need to read a cell of data.
ShowAddedCells function writes to an Excel cell which you can use like below.
In ShowAddedCells function,
//Comment the following
/*
DISPID dispidPUT = DISPID_PROPERTYPUT;
DISPPARAMS dparams = {vDblVal, &dispidPUT, 1, 1};
EXCEPINFO excepinfo;
hr = pXlCell->Invoke(dispID,
IID_NULL,
LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUT,
&dparams,
NULL,
&excepinfo,
NULL);
*/
//Add the following
DISPPARAMS dparams = {NULL, NULL, 0, 0};
EXCEPINFO excepinfo;
VARIANT vResult;
VariantInit(&vResult);
hr = pXlCell->Invoke(dispID,
IID_NULL,
LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET,
&dparams,
&vResult,
&excepinfo,
NULL);
//You get the cell value in vResult
So you create a ReadCell function like ShowAddedCells function.

Related

c++ ole automation how to clear excel cell contents

I am using the following function call of IDispatch interface to successfully write Hello World! to a cell in excel sheet excel.
LPOLESTR ptName = L"Value"; //What is the key for clearing the cell contents?
DISPID dispID;
xlCell->GetIDsOfNames(IID_NULL, &ptName, 1, LOCALE_USER_DEFAULT, &dispID);
LCID lcid = GetUserDefaultLCID();
std::wstring cellVal = L"Hello World!";
VARIANT vArgArray[1];
vArgArray[0].vt = VT_BSTR,
vArgArray[0].bstrVal = SysAllocString(cellVal.c_str());
DISPID dispidNamed = DISPID_PROPERTYPUT;
DISPPARAMS dp = {NULL, NULL, 0, 0};
dp.cArgs = 1;
dp.rgvarg = vArgArray;
dp.cNamedArgs = 1;
dp.rgdispidNamedArgs = &dispidNamed;
xlCell->Invoke(
dispID,
IID_NULL,
lcid,
DISPATCH_PROPERTYPUT,
&dp,
NULL,
NULL,
NULL);
Now I need to clear a cell using the same IDispatch interface (xlCell)
What value should I use for the LPOLESTR type variable (2nd parameter in GetIDsOfNames function) to initialize the dispID to successfully clear the cell by calling Invoke?
While I could not find the unique DISPID for clearing a cell/cell range, the thing that did work was to modify the VARIANT passed to the Invoke().
All I did was to change the VARIANT from this
vArgArray[0].vt = VT_BSTR,
vArgArray[0].bstrVal = SysAllocString(cellVal.c_str());
to this
vArgArray[0].vt = VT_EMPTY;
Hope it helps someone.

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.

Register all interfaces of COM DLL using C++ API

I know, we can register interfaces of COM DLL using regsvr32.exe
is any windows C++ API available, Which API will register interfaces of COM DLL.
Thanks!
Vijay Kumbhani
All regsvr32 does is load a dll and call the DllRegisterServer function exposed by the DLL.
If you want to write your own code to do this then you need to use LoadLibrary and GetProcAddress to get a pointer to the function, and then just call it.
Do you know the CLSID and COM dll file path?
If yes, you can write them into registry with API RegCreateKeyEx/SetKeyValue/RegCreateKeyEx/..., here is a example:
[HKEY_CLASSES_ROOT\CLSID\{CLSID}\InprocServer32]
#="C:\\Windows\\System32\\oleaut32.dll"
"ThreadingModel"="Both"
LONG lreturn = RegCreateKeyEx(
HKEY_CLASSES_ROOT,
_T("CLSID\${COMCLSID}"), // subkey
0, // reserved
NULL, // class string (can be NULL)
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
NULL, // security attributes
&hKey,
NULL // receives the "disposition" (is it a new or existing key)
);
hr = __HRESULT_FROM_WIN32(lreturn);
// The default key value is a description of the object.
if (SUCCEEDED(hr))
{
hr = SetKeyValue(hKey, NULL, _T("your description"));
}
// Create the "InprocServer32" subkey
if (SUCCEEDED(hr))
{
const TCHAR *sServer = TEXT("InprocServer32");
LONG lreturn = RegCreateKeyEx(hKey, sServer, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hSubkey, NULL);
hr = __HRESULT_FROM_WIN32(lreturn);
}
if (SUCCEEDED(hr))
{
hr = SetKeyValue(hSubkey, NULL, _T("${COM DLL file Path}"));
}
// Add a new value to the subkey, for "ThreadingModel" = <threading model>
if (SUCCEEDED(hr))
{
hr = SetKeyValue(hSubkey, TEXT("ThreadingModel"), sThreadingModel);
}

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.

Excel OpenText method

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