Proper way of using IXMLDOMDocument - c++

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

Related

How do I implement `BeginRead` in the IMFByteStream interface

I have created ReadByteContainer to store current data and ReadByteAsyncCallback for the callback. Are there alternatives that would work better?
HRESULT MediaByteStream::BeginRead(
BYTE *pb,
ULONG cb,
IMFAsyncCallback *pCallback,
IUnknown *punkState)
{
HRESULT hr = S_OK;
// Create a new read byte container.
ReadByteContainer* readBytes = new ReadByteContainer(pb, cb);
ReadByteAsyncCallback* readCallback = new ReadByteAsyncCallback(this);
// If not created.
if (readBytes == NULL)
{
return E_OUTOFMEMORY;
}
// If not created.
if (readCallback == NULL)
{
return E_OUTOFMEMORY;
}
IMFAsyncResult *pResult = NULL;
readBytes->_readCallback = readCallback;
// Creates an asynchronous result object. Use this function if you are implementing an asynchronous method.
hr = MFCreateAsyncResult(readBytes, pCallback, punkState, &pResult);
if (SUCCEEDED(hr))
{
// Start a new work item thread.
hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, readCallback, pResult);
pResult->Release();
}
// Return the result.
return hr;
}
If pb remains valid until EndRead occurs, you can avoid the copy (new ReadByteContainer), and just keep pb as is.
Normally your MediaByteStream implements IMFAsyncCallback so you should call MFPutWorkItem like this (avoid new ReadByteAsyncCallback) :
hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, this, pResult);
Also, i don't see some lock mechanism inside BeginRead. If you use it in a multithreading environment you should handle this. Close can be call when BeginRead has been called and did not finish, so you will face problems.

DirectShow CLR having issue with global variables

I am trying to code up GMFBridge and DirectShow in CLR C++. I am trying to compare its performance against the GMFBridgeLib and the DirectShowLib in the same solution to see which is more efficient.
Right now I am following the GMFBridge source code for setting up C++ capture. One issue I am having is in objects that need to be global so that they can be accessed across the GUI buttons. The GMFBridge code does that as follows:
private:
IGMFBridgeControllerPtr m_pBridge;
that is then used in the setup code as follows:
HRESULT hr = m_pBridge.CreateInstance(__uuidof(GMFBridgeController));
if (FAILED(hr))
{
return hr;
}
// init to video-only, in discard mode (ie when source graph
// is running but not connected, buffers are discarded at the bridge)
hr = m_pBridge->AddStream(true, eMuxInputs, true);
My current problem is that CLR states that any global has to be a pointer of some form, * or ^ depending on managed or unmanaged. It will not just let me add in a global variable such as the GMFBridge source code does. If I create a pointer:
IGMFBridgeControllerPtr* pBridge2;
and try to use that in my GUI code:
(*pBridge2).CreateInstance(__uuidof(GMFBridgeController));
(*pBridge2).AddStream(true, eMuxInputs, true);
It does compile, but when i run it, the code crashes with
An unhandled exception of type 'System.NullReferenceException' occurred in Program.exe.
Addidional information: Object reference not set to an instance of an object.
on the block of code
void _Release() throw()
{
if (m_pInterface != NULL) { <--------------
m_pInterface->Release();
}
}
in comip.h line 823 called from:
HRESULT CreateInstance(const CLSID& rclsid, IUnknown* pOuter = NULL, DWORD dwClsContext = CLSCTX_ALL) throw()
{
HRESULT hr;
_Release();
if (dwClsContext & (CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER)) { <----------
IUnknown* pIUnknown;
hr = CoCreateInstance(rclsid, pOuter, dwClsContext, __uuidof(IUnknown), reinterpret_cast<void**>(&pIUnknown));
if (SUCCEEDED(hr)) {
hr = OleRun(pIUnknown);
if (SUCCEEDED(hr)) {
hr = pIUnknown->QueryInterface(GetIID(), reinterpret_cast<void**>(&m_pInterface));
}
pIUnknown->Release();
}
}
else {
hr = CoCreateInstance(rclsid, pOuter, dwClsContext, GetIID(), reinterpret_cast<void**>(&m_pInterface));
}
if (FAILED(hr)) {
// just in case refcount = 0 and dtor gets called
m_pInterface = NULL;
}
return hr;
}
comip.h line 626 called from this line of code
(*pBridge2).CreateInstance(__uuidof(GMFBridgeController));
the only thing that seems to work is creating a local variable that is not a point object, but then i cant set it to a global, or use it across GUI objects.
if I make it local:
IGMFBridgeControllerPtr pBridge;
pBridge.CreateInstance(__uuidof(GMFBridgeController));
that works.
The problem seems to be that you do not assign anything to the pointer you declare:
IGMFBridgeControllerPtr* pBridge2;
You have to do something like:
pBridge2 = &m_pBridge;
Or just skip the use of pBridge2 completely and use &m_pBridge instead.

Why COM doesn't work in a new thread?

My problems started after converting my VS2003 project to VS2008. Solution contains 3 projects. Projects are DLL's. There were A LOT of compilation errors, then some linker errors... Well, I fought them off. Now it just simply doesn't work ;)
So, one of this DLL's is suppoused to communicate with Word by COM.
Word::_ApplicationPtr d_pApp;
Word::_DocumentPtr d_pDoc;
void MSWord2003::init()
{
free();
HRESULT hr;
CLSID clsid;
CLSIDFromProgID(L"Word.Application", &clsid);
// Get an interface to the running instance, if any..
IUnknown *pUnk;
hr = GetActiveObject(clsid, NULL, (IUnknown**)&pUnk);
if(hr!=S_OK)
throw MSWord::MSWordException("Nie znaleziono działającej aplikacji MSWord.");
IDispatch* d_pDispApp;
hr = pUnk->QueryInterface(IID_IDispatch, (void**)&d_pDispApp);
if(hr!=S_OK)
throw MSWord::MSWordException("Nie udało się połączyć z aplikacją MSWord.");
pUnk->Release();
pUnk = 0;
d_pApp = d_pDispApp;
d_pDoc = d_pApp->ActiveDocument;
d_pDispApp->AddRef();
d_currIdx = -1;
paragraphsCount = d_pDoc->GetParagraphs()->Count;
footnotesCount = d_pDoc->GetFootnotes()->Count;
endnotesCount = d_pDoc->GetEndnotes()->Count;
}
void MSWord2003::free()
{
if(d_pApp!=0)
{
d_pApp->Release();
d_pApp=0;
}
}
This code works on VS2003 (and different machine, I don't have VS2003 on my computer) while in VS2008 it works only if it is called by main thread.
When called by a new thread (wich is initialized by CoInitialize) d_pApp is not initialized properly - its ptr shows 0.
While debugging I reached code in comip.h:
template<typename _InterfacePtr> HRESULT _QueryInterface(_InterfacePtr p) throw()
{
HRESULT hr;
// Can't QI NULL
//
if (p != NULL) {
// Query for this interface
//
Interface* pInterface;
hr = p->QueryInterface(GetIID(), reinterpret_cast<void**>(&pInterface));
// Save the interface without AddRef()ing.
//
Attach(SUCCEEDED(hr)? pInterface: NULL);
}
else {
operator=(static_cast<Interface*>(NULL));
hr = E_NOINTERFACE;
}
return hr;
}
In a new thread, QueryInterface returns E_NOINTERFACE, although GetIID() returns the same thing for both threads. And that is where I got stuck - I have no idea, what causes this behaviour...
IMO you should initialize COM not with CoInitialize, but with CoInitializeEx, specifying COINIT_MULTITHREADED. Otherwise you'll have separate single-threaded COM apartment for every thread.

Do I have to use ->Release()?

I am working with a webbrowser host on c++, I managed to sink event and I am running this void on DISPID_DOCUMENTCOMPLETE:
void DocumentComplete(LPDISPATCH pDisp, VARIANT *url)
{
READYSTATE rState;
iBrowser->get_ReadyState(&rState);
if(rState == READYSTATE_COMPLETE)
{
HRESULT hr;
IDispatch *pHtmlDoc = NULL;
IHTMLDocument2 *pDocument = NULL;
IHTMLElement *pBody = NULL;
IHTMLElement *lpParentElm = NULL;
BSTR bstrHTMLText;
hr = iBrowser->get_Document(&pHtmlDoc);
hr = pHtmlDoc->QueryInterface(IID_IHTMLDocument2, (void**)&pDocument);
if( (FAILED(hr)) || !pDocument)
{
MessageBox(NULL, "QueryInterface failed", "WebBrowser", MB_OK);
}
hr = pDocument->get_body( &pBody );
if( (!SUCCEEDED(hr)) || !pBody)
{
MessageBox(NULL, "get_body failed", "WebBrowser", MB_OK);
}
pBody->get_parentElement(&lpParentElm);
lpParentElm->get_outerHTML(&bstrHTMLText);
_bstr_t bstr_t(bstrHTMLText);
std::string sTemp(bstr_t);
MessageBox(NULL, sTemp.c_str(), "WebBrowser", MB_OK);
}
}
I don't much about c++, I built this code from watching other codes in google. Now I know I have to use ->Release, but do I have to use all these?:
pHtmlDoc->Release();
pDocument->Release();
pBody->Release();
lpParentElm->Release();
iBrowser->Release();
Because on the examples I used to build my code it was using it only for the IHTMLElement(s).
Yes, you do have to call Release() on those pointers, otherwise objects will leak. The same goes for BSTRs.
You'll be much better off if you use smart pointers for that - ATL::CComPtr/ATL::CComBSTR or _com_ptr_t/_bstr_t.
You should wrap those objects into a CComPtr or one of its variants. That will handle the release for you. It goes with the concept of RAII.
Yes you do. But not on the iBrowser, you didn't acquire that pointer inside this code.
Beware that your error checking isn't sufficient, your code will bomb when get_Document() fails. Same for get_parentElement(). And after the message box is dismissed.

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