c++ MFC SDI DLL call method of OLE Server using IDispatch - c++

I have written a c++ MFC DLL that brings up an SDI Application which is a very legacy OLE Server. (I have no choice about using this OLE Server so I have to make it work.)
I am accessing this c++ DLL from C#.
I have everything "working". I can call methods of the DLL, I have implemented C# delegates correctly, etc.
I can call methods of the OLE Server directly in C++ and export these so that my C# application call call them too. This is my "Hello World" for accessing the OLE Server functionality in its entirety from C#.
So far so good.
The next step is to make this C++ DLL as much of a "pass-through" as possible so that C# developers can write business logic around this OLE server. If changes or updates happen from the maker of the OLE Server, we do not want to have to update C++ code, we want to respond to the changes in C#.
So even though I can implement methods in C++ using the imported class from the OLEServer.tlb file and pass these through to C#, we do not want to ultimately do this. I am trying to call methods through the IDispatch interface instead and I am running into difficulties that I can't quite understand.
I am using Visual Studio 2010 for both unmanaged C++ and C#.
VARIANT LaserCat::LaserCatCommand(LPCTSTR* p_pMethodNameAndParamsInReverseOrder, UINT p_Count)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
VARIANT result;
VARIANT* pResult = NULL;
VariantInit(&result);
HRESULT hr = NULL;
DISPID dispid;
const IID IID_ITriad = {0x60EE772D,0xE076,0x4F58,{0xA8,0xB4,0x2F,0x7A,0x29,0xBB,0x02,0x50}};
COleException* pError = NULL;
BOOL HasDispatch = theApp.pTriadView->pTriadItem->Catalog.CreateDispatch(IID_ITriad, pError);
LPDISPATCH iDisp = theApp.pTriadView->pTriadItem->Catalog.m_lpDispatch;
LPOLESTR Names[1] = {(LPOLESTR)L"GetInterfaceVersion"};
hr = iDisp->GetIDsOfNames(IID_NULL, Names, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (hr != S_OK) return result;
DISPPARAMS* pParams = new DISPPARAMS();
short maj = 0;
short min = 0;
short* nMajor = &maj;
short* nMinor = &min;
VARIANTARG Args[2];
VariantInit(&Args[0]);
VariantInit(&Args[1]);
Args[0].piVal = nMinor;
Args[0].vt = VT_BYREF;
Args[1].piVal = nMajor;
Args[1].vt = VT_BYREF;
pParams->rgvarg = Args;
pParams->cNamedArgs = 0;
pParams->cArgs = 2;
pParams->rgdispidNamedArgs = NULL;
EXCEPINFO* pExcept = NULL;
UINT* pArgErrorIndex = NULL;
LPCTSTR Error = NULL;
hr = iDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, pParams, pResult, pExcept, pArgErrorIndex);
if (pExcept != NULL || pArgErrorIndex != NULL || hr != S_OK)
{
Error = _T("Error");
return result;
}
result = *pResult;
return result;
}
The following line from above gives me a "Bad variable type" error:
hr = iDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, pParams, pResult, pExcept, pArgErrorIndex);
BTW, I am sure that the code can be vastly improved, it has been years since I have written in C++ and this is just a "first kick" at getting this working so no real error handling etc.
I have toyed around with "Args[]" type and value with variations of "Type Mismatch" errors and "Null Reference Pointers"
The function that is imported by the .tlb file looks like this:
short GetInterfaceVersion(short * nMajor, short * nMinor)
{
short result;
static BYTE parms[] = VTS_PI2 VTS_PI2 ;
InvokeHelper(0x178, DISPATCH_METHOD, VT_I2, (void*)&result, parms, nMajor, nMinor);
return result;
}
Oh, although I am passing in the "pMethodNameAndParamsInReverseOrder" as a parameter, I am just hard-coding it to get this one simple method working. Once I have it working with this and a few other methods, I am planning on making this generic to handle any methods implemented by the COM interface via IDispatch.
Any help would be appreciated mostly in getting this working but I would also appreciate any pointers on improving the code, I have mouch to learn in C++
Thank-You!
BTW, If this helps clarify things, theApp.pTriadView->pTriadItem->Catalog is the COM class I am implementing
EDIT:
Thanks to #HansPassant (see first comment) I see what I was missing. Unfortunately I have hit a downstream result of fixing that. The VARIANT "pResult" is coming back empty. I will continue to hunt that down now but any thoughts would be welcome :)

Related

How can I give an old ActiveX control new GUIDs?

I am trying to modify an ActiveX control developed in Visual Studio 2008 to use it for a purpose for which it was not originally designed. I will be reusing at least 90% of its code. Therefore, I would like to begin by creating an identical control that uses different GUIDs. I tried to follow instructions I found here (a very old link, written in 2004), but when I tried to build my project, I got an assertion failure in ctlreg.cpp line 113. Then, I restored all my changed files back to their original states, and for each GUID is the .odl file, I searched for the GUID in my .cpp and .h files and changed it wherever I found it. I also made sure to change my major version number. I still get the assertion failure. What else should I be doing?
Here's the code from ctlreg.cpp from the start of the method containing the assertion to the assertion itself:
BOOL AFXAPI AfxOleRegisterTypeLib(HINSTANCE hInstance, REFGUID tlid,
LPCTSTR pszFileName, LPCTSTR pszHelpDir)
{
BOOL bSuccess = FALSE;
CStringW strPathNameW;
wchar_t *szPathNameW = strPathNameW.GetBuffer(_MAX_PATH);
::GetModuleFileNameW(hInstance, szPathNameW, _MAX_PATH);
strPathNameW.ReleaseBuffer();
LPTYPELIB ptlib = NULL;
// If a filename was specified, replace final component of path with it.
if (pszFileName != NULL)
{
int iBackslash = strPathNameW.ReverseFind('\\');
if (iBackslash != -1)
strPathNameW = strPathNameW.Left(iBackslash+1);
strPathNameW += pszFileName;
}
if (SUCCEEDED(LoadTypeLib(strPathNameW.GetString(), &ptlib)))
{
ASSERT_POINTER(ptlib, ITypeLib);
LPTLIBATTR pAttr;
GUID tlidActual = GUID_NULL;
if (SUCCEEDED(ptlib->GetLibAttr(&pAttr)))
{
ASSERT_POINTER(pAttr, TLIBATTR);
tlidActual = pAttr->guid;
ptlib->ReleaseTLibAttr(pAttr);
}
// Check that the guid of the loaded type library matches
// the tlid parameter.
ASSERT(IsEqualGUID(tlid, tlidActual));

Change div innerHTML through IHTMLDocument2 and C++

I'm trying to change the content of a div using IHTMLDocument2 interface this way:
IHTMLElementCollection* collection = NULL;
IDispatch* mydiv;
doc2->get_all(&collection);
long count;
collection->get_length(&count); //just to check I get something
CComVariant varstr = L"mydivname";
CComVariant varint = 0;
collection->item(varstr, varint, &mydiv); //this works I get the div
IHTMLElement* htmldiv;
mydiv->QueryInterface(IID_IHTMLElement, (void**)&htmldiv);
CComBSTR html;
htmldiv->get_innerHTML(&html); //works too, I get the current content
HRESULT hr=htmldiv->put_innerText(L"hello"); //this does not work but returns S_OK
collection->Release();
So the content of my div is just cleared and not replaced with "hello", I don't understand why, can it be a security issue ?
Thanks
According to the MSDN documentation, the string passed to put_innerText is of type BSTR.
So, I would suggest trying some code like this:
CComBSTR text(OLESTR("hello"));
hr = htmldiv->put_innerText(text);

CopyFiles using IFileOperation (C++)

I want to copy multiple files using IFileOperation.
Copy a single file is not a problem like this example:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb775761%28v=vs.85%29.aspx
My Problem is that I don't find a way to copy multiple files like *.txt.
I have tried using SHCreateShellItemArrayFromIDLists, like this snippet:
IShellItem *psiTo = NULL;
HRESULT hr = SHCreateItemFromParsingName( csTarget, NULL, IID_PPV_ARGS(&psiTo) );
LPCITEMIDLIST pidlFiles = ILCreateFromPath(csSource);
UINT count = sizeof(pidlFiles);
IShellItemArray* psiaFiles = NULL;
hr = SHCreateShellItemArrayFromIDLists(count, &pidlFiles, &psiaFiles);
hr = pfo->CopyItems(psiaFiles, psiTo);
hr = pfo->PerformOperations();
A other way is to use SHCreateShellItemArray, like this:
LPCITEMIDLIST pidlParent = ILCreateFromPath(_T("C:\\"));
LPCITEMIDLIST pidlChild = ILCreateFromPath(_T("C:\\Temp\\*.txt"));
HRESULT hr = SHCreateShellItemArray(pidlParent, NULL, 1, &pidlChild, &psiaFiles);
hr = pfo->CopyItems(psiaFiles, psiTo);
hr = pfo->PerformOperations();
I tried different way but almost I get E_INVALIDIDARG or ERROR_PATH_NOT_FOUND.
Whats wrong?
ILCreateFromPath doesn't take wildcards. Couldn't, since it returns a single PIDLIST_ABSOLUTE.
The easiest solution is to enumerate all files manually FindFirstFile(*.txt), call CopyItem on each result, and then call PerformOperations once at the end.
As usual, the older Windows functions are better. SHFileOperation is ten times simpler and more capable to boot: it does support wildcards directly.

Access sub object in COM interface

I want to get access to a sub object of a COM object. In my example I use the CANoe COM Server.
In my program I create a CAN interface to the CANoe Application. Here is an extract of my code so far and it does exactly what I want:
HRESULT result;
//prepare for COM handling...
result = CoInitialize(NULL);
//get CLSID of CANoe...
result = CLSIDFromProgID(L"CANoe.Application", &clsid);
if(SUCCEEDED(result))
{
//connect to COM interface of CANoe...
result = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, IID_IApplication, (void**) &pIApp);
if(SUCCEEDED(result))
{
qDebug() << "COM connection established";
}
else
{
qDebug() << "COM connection error";
}
}
else
{
qDebug() << "Error: CLSID";
}
Now I want to get access to a sub object of the COM Server. For example the Measurement object. I tried it with the method pIApp->get_UI()
IDispatch* pIDis;
IMeasurement* pIMeasurement;
result = pIApp->get_UI(&pIDis);
pIMeasurement = (IMeasurement*) pIDis;
The pointer to the COM object needs to be a pointer of the type IMeasurement, so I can use all the methods defined in the header file. But the method get_UI only supports pointer of the type IDispatch. I tried to cast the pointer from type IDispatch to IMeasurement. But the program crashs at runtime.
I also tried to create a new interface directly to the sub object:
result = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, IID_IMeasurement, (void**) &pIMeasurement);
But in this try in the variable result there is saved an error and I can't access the methods of the sub object Measurement.
Where is my mistake and how can I get access to the sub object?
Thank you for all answers and hints!
Thanks to #WhozCraig, #Hans Passant and #Eric Brown for the hints in the comments.
I was able to solve my problem with the following code:
IDispatch* pIDispatch;
//get pointer pIDispatch to Measurement object of CANoe...
result = pIApp->get_Measurement(&pIDispatch);
if(SUCCEEDED(result))
{
//pointer pIDispatch to pIMeasurement...
result = pIDispatch->QueryInterface(IID_IMeasurement, (void**) &pIMeasurement);
if(SUCCEEDED(result))
{
pIDispatch->Release();
//work with connection here...
pIMeasurement->Release();
}
}

Word automation - SaveAs

I try to write a simple MFC - Word Automation to save for every 1 minute.
I follow this article : http://www.codeproject.com/KB/office/MSOfficeAuto.aspx
And this is what Im trying to implement , I'm new to COM so I think there's problem here:
my VBA is generated by Word 2010:
ActiveDocument.SaveAs2 FileName:="1.docx", FileFormat:=wdFormatXMLDocument _
, LockComments:=False, Password:="", AddToRecentFiles:=True, _
WritePassword:="", ReadOnlyRecommended:=False, EmbedTrueTypeFonts:=False, _
SaveNativePictureFormat:=False, SaveFormsData:=False, SaveAsAOCELetter:= _
False, CompatibilityMode:=14
And my code to implement VBA code above :
{
COleVariant varName(L"b.docx");
COleVariant varFormat(L"wdFormatXMLDocument");
COleVariant varLockCmt((BYTE)0);
COleVariant varPass(L"");
COleVariant varReadOnly((BYTE)0);
COleVariant varEmbedFont((BYTE)0);
COleVariant varSaveNativePicFormat((BYTE)0);
COleVariant varForms((BYTE)0);
COleVariant varAOCE((BYTE)0);
VARIANT x;
x.vt = VT_I4;
x.lVal = 14;
COleVariant varCompability(&x);;
VARIANT result;
VariantInit(&result);
_hr=OLEMethod( DISPATCH_METHOD, &result, pDocApp, L"SaveAs2",10,
varName.Detach(),varFormat.Detach(),varLockCmt.Detach(),varPass.Detach(),varReadOnly.Detach(),
varEmbedFont.Detach(),varSaveNativePicFormat.Detach(),varForms.Detach(),varAOCE.Detach(),varCompability.Detach()
);
}
I get no error from this one, but it doesn't work.
The VBA syntax uses named parameters where the order and count of the parameters do not matter. However, when calling from C++ you need to pass the required number of parameters in the right order.
SaveAs2 is defined as:
void SaveAs2(
ref Object FileName,
ref Object FileFormat,
ref Object LockComments,
ref Object Password,
ref Object AddToRecentFiles,
ref Object WritePassword,
ref Object ReadOnlyRecommended,
ref Object EmbedTrueTypeFonts,
ref Object SaveNativePictureFormat,
ref Object SaveFormsData,
ref Object SaveAsAOCELetter,
ref Object Encoding,
ref Object InsertLineBreaks,
ref Object AllowSubstitutions,
ref Object LineEnding,
ref Object AddBiDiMarks,
ref Object CompatibilityMode
)
So, it has 17 parameters which means you should specify them all if you are going to pass CompatibilityMode, which was specified as 10th parameter in your example (that corressponds to SaveFormsData).
If you do not need all the parameters, or just for testing, you can try a simpler code:
_hr = OLEMethod(DISPATCH_METHOD, &result, pDocApp, L"SaveAs2", 2, varName.Detach(), varFormat.Detach());
If you need the rest of the parameters, you need to pass all the parameters up to the parameter you need set. In that case, you can pass
COleVariant vtOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
for the parameters you do not like to set.
Edit - Test Code
This works for me:
CoInitialize(NULL);
CLSID clsid;
IDispatch *pWApp;
HRESULT hr = CLSIDFromProgID(L"Word.Application", &clsid);
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&pWApp);
hr = OLEMethod(DISPATCH_PROPERTYPUT, NULL, pWApp, L"Visible", 1, COleVariant((long)1));
VARIANT result;
VariantInit(&result);
hr = OLEMethod(DISPATCH_PROPERTYGET, &result, pWApp, L"Documents", 0);
IDispatch *pDocs = result.pdispVal;
VARIANT result2;
VariantInit(&result2);
hr = OLEMethod(DISPATCH_METHOD, &result2, pDocs, L"Open", 1, COleVariant(L"D:\\Archive\\t1.docx"));
IDispatch *pDoc = result2.pdispVal;
VARIANT result3;
VariantInit(&result3);
hr = OLEMethod(DISPATCH_METHOD, &result3, pDoc, L"SaveAs2", 1, COleVariant(L"D:\\Archive\\t2.docx"));
CoUninitialize();
Change your variable varFormat from wdFormatXMLDocument to an integer of 12 (probably like you've done the varCompability variable). Also, what's the 10 for after "SaveAs2"?
Guess I'll start over since you opened the bounty.
Change your variable varFormat from wdFormatXMLDocument to an integer of 12 (probably like you've done the varCompability variable). wdFormatXMLDocument is an enum of WdSaveFormat and was introduced in Word 2003. There is no need to send in an L"name" - just send in the integer of 12.
If these are previously saved documents (i.e. not new ones), perform a conversion first to get it to the right format (like ActiveDocument.Convert)