Greets,
When working with DirectX, you get this nice header to #include called DxErr9.h which has really helpful functions like:
DXGetErrorString9
and
DXGetErrorDescription9
They tell you everything you need to know about the error given the HR.
But now working with COM and OLE, I find I'm kind of on my own with the HRESULTS that come back from COM functions. Is it really just me and MSDN at this point, or are there similar helper functions in OLE DB that I just haven't come across yet?
Additionally, you should look at the error info. Part of the COM system is the concept of the error information, which is a per-thread global which can be set and cleared at various times. You query for it in response to an error, and if it is set, it will have more useful information than just looking at the HRESULT.
HRESULT hr=something();
if (FAILED(hr))
{
CComPtr<IErrorInfo> err;
::GetErrorInfo(0, &err);
if (err)
{
CComBSTR description;
err->GetDescription(&description);
// description will be a more descriptive error message than just formatting the
// HRESULT because it is set by the COM server code at the point of the error
}
}
Use _com_error to get a meaningful string:
#include <comdef.h>
HRESULT hr = SomeComFunc();
if ( FAILED(hr) )
{
_com_error err(hr);
LPTCSTR szErrMsg = err.ErrorMessage();
// log szErrMsg or whatever
}
Related
I'm using an external C++ application to control Internet Explorer (version is 11, but that seems unimportant for this example). Part of this control is being able to run arbitrary JavaScript in the browser's context. To enable that control, I'd like to be able to create a JavaScript object (or array, or function, etc.), represented by an appropriate IDispatch variant, that I can use in subsequent script calls. There are some examples online, including some from Microsoft, that would indicate such a thing should be possible, by finding and invoking the Object constructor.
Below is some sample code, cribbed from the aforementioned examples, that should do the trick. However, when I execute the code, I get an E_INVALIDARG ("One or more arguments are invalid") HRESULT returned. What am I doing wrong, and how do I fix the issue?
// Example assumes that you're using an ATL project which give access to
// the "CCom" wrapper classes, and that you have the ability to retrieve
// an IHTMLDocument2 object pointer.
int CreateJavaScriptObject(IHTMLDocument2* script_engine_host, CComVariant* created_object) {
// NOTE: Proper return code checking and error handling
// has been omitted for brevity
int status_code = 0;
CComPtr<IDispatch> script_dispatch;
HRESULT hr = script_engine_host->get_Script(&script_dispatch);
CComPtr<IDispatchEx> script_engine;
hr = script_dispatch->QueryInterface<IDispatchEx>(&script_engine);
// Create the variables we need
DISPPARAMS no_arguments_dispatch_params = { NULL, NULL, 0, 0 };
CComVariant created_javascript_object;
DISPID dispatch_id;
// Find the javascript object using the IDispatchEx of the script engine
std::wstring item_type = L"Object";
CComBSTR name(item_type.c_str());
hr = script_engine->GetDispID(name, 0, &dispatch_id);
// Create the jscript object by calling its constructor
// The below InvokeEx call returns E_INVALIDARG in this case
hr = script_engine->InvokeEx(dispatch_id,
LOCALE_USER_DEFAULT,
DISPATCH_CONSTRUCT,
&no_arguments_dispatch_params,
&created_javascript_object,
NULL,
NULL);
*created_object = created_javascript_object;
return status_code;
}
Potential answerers desiring a fully compilable solution can find a version of the above code in a GitHub repo. The repo includes a Visual Studio 2017 solution that creates a console application which will reproduce the issue, including launching Internet Explorer and obtaining a reference to the required IHTMLDocument2 object. Note that the version in the repo has somewhat more complete error handling than the above sample, in which it was omitted for brevity.
So, we are currently upgrading an ancient program from visual studio 2008 (where everything works) to 2017. We use a dll, whose classes the application connects to through ATL and a project-dependency.
Unfortunately all attempts at calling functions from these classes return exceptions, presumably because it can't find them.
The dll's classes are successfully added to the registry through regedit when built, and the uuids correspond correctly with the registered values. The classes can also be found in the OLE/COM-viewer.
It also only breaks when attempting to call from one of our classes. An attempt to call functions from IDispatch, which the classes in question inherits from, worked correctly.
In the below code the first attempt 'm_pRenderer' throws the exception. The second attempt 'test2' does not enter it's if-statement as CoCreateInstance returns a bad variable type-error
HRESULT res = CoCreateInstance(__uuidof(CBSNullRenderer), NULL, CLSCTX_INPROC_SERVER, __uuidof(IDispatch), (void**)&m_pRenderer);
Log("\nCreateInstance: %ld", res);
ICBSNullRendererPtr test2 = NULL;
HRESULT res2 = CoCreateInstance(__uuidof(CBSNullRenderer), NULL, CLSCTX_INPROC_SERVER, __uuidof(ICBSNullRenderer), (void**)&test2);
Log("\nres2: %ld", res2);
wireHWND mainHwnd = (wireHWND)GetParent(p_hWnd);
if(mainHwnd == NULL)
Log("\nWARNING mainHWND is NULL!");
try {
if (test2)
{
Log("\nDid create NullRenderer!");
test2->SetMainWnd(mainHwnd);
}
if (m_pRenderer)
{
Log("\nDid create NullRenderer!");
m_pRenderer->SetMainWnd(mainHwnd);
}
}
catch (...)
{
}
Which breaks on line 2 for m_pRenderer for:
inline HRESULT ICBSNullRenderer::SetMainWnd ( wireHWND hwnd ) {
HRESULT _hr = raw_SetMainWnd(hwnd);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _hr;
}
In the dll's .tli file.
The error creates an "Exception Thrown" dialog with the following:
Exception thrown at 0x00007FF9153ED7F2 (oleaut32.dll) in
AnimgramPro.exe: 0xC0000005: Access violation executing location
0x00007FF9153ED7F2
We also attempted to use QueryInterface on the m_pRenderer with the nullrenderer's uuid. This ended in another bad variable type-error.
Any advice or information of the errors which could be associated would be greatly appreciated.
So, I solved the problem. Apparently properly calling a dll-function in my program requires that 'Common Language Runtime Support' is turned off and that 'Whole Program Optimization' is set to 'Use Link Time Code Generation'
I have an ATL project where I need to perform various initialization routines inside CComObjectRootEx::FinalConstruct. For demonstration purposes, consider the following implementation:
HRESULT FinalConstruct()
{
return m_bInit ? S_OK : E_FAIL;
}
This should return the appropriate HRESULT to the caller, indicating whether the initialization of the object was successful.
However, the client always receives REGDB_E_CLASSNOTREG instead of E_FAIL in case of failure, when trying to create the server:
#include <Windows.h>
#import <finalconstructtest.dll>
int main()
{
HRESULT hr = CoInitialize(0);
{
finalconstructtestLib::IFCClassPtr p;
// Returns REGDB_E_CLASSNOTREG
hr = p.CreateInstance(__uuidof(finalconstructtestLib::FCClass));
}
CoUninitialize();
return 0;
}
When I change the class context to CLSCTX_INPROC_SERVER however, the intended HRESULT is returned correctly:
// Returns E_FAIL
hr = p.CreateInstance(__uuidof(finalconstructtestLib::FCClass), nullptr, CLSCTX_INPROC_SERVER);
I've seen this post, where a similar behavior could be observed. However, I can't seem to find any reason why the class context affects the return value of FinalConstruct. Is this intended and perhaps documented somewhere?
CoCreateInstance API is not promising to forward the internal failure code to the caller. The implementation prefers to indicate the the source of the problem its own way, by returning REGDB_E_CLASSNOTREG meaning that it was unable to complete instantiation. Which is in turn true: it indeed was unable to create an instance (and the problem is not, for instance, related to missing interface, marshaling, permissions etc.). That is, the API prefers to suppress internal failure code to replace it with a documented one.
If you prefer to indicate a specific instantiate failure, you are better off with succeeding with initial creating an instance, and then you can implement an interface with a property or method exposing status, or giving relevant HRESULT error (with or without IErrorInfo support etc).
I'm trying to port a LabView program to C++, and the OLE calls it contains are giving me some trouble.
The LabView program starts out by doing an "Automation open", i.e. getting a reference to the interface "XLib.XInterface" (LabView calls this expression the "ActiveX class"), then calls the method QA found in the interface and finally closes the reference again. I think LabView gets its info on the interface from the type library, but I'm not entierly sure.
I've tried to adapt some code for Word automation I found: http://www.codeproject.com/KB/office/MSOfficeAuto.aspx
CoInitialize(NULL);
CLSID clsid;
HRESULT hr = CLSIDFromProgID(L"XConfig.XInterface", &clsid);
IDispatch *pWApp;
if(SUCCEEDED(hr))
{
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER,
IID_IDispatch, (void **)&pWApp);
}
// etc.
The program is successful in looking up the CLSID, but CoCreateInstance fails, claiming that the class is not registered. I've also tried entering the CLSID from the type library directly, bypassing CLSIDFromProgID, but yielding the same result. Needless to say that the LabView program works fine, and the C++ code I'm using has no trouble at all to create an instance of Word when using the progID "Word.Application". The interface in question looks like this:
[
odl,
uuid(33AAA2DA-70EB-48EE-ACA7-DD0D1F5CAF2D),
helpstring("XInterface Interface"),
dual,
oleautomation
]
interface XInterface : IDispatch {
[id(0x00000001), helpstring("method QA")]
HRESULT QA();
[id(0x00000002), helpstring("method LoadFromDisk")]
HRESULT LoadFromDisk();
...
As you may have noticed, OLE is kind of new to me (most likely, that's a part of the problem). Any hints would be greatly appreciated. Thanks.
OK, I think I figured it out by myself, even though I don't fully understand my solution. Anyway, when I use
hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IDispatch,
(void **)&pWApp);
it seems to work; at least I don't get the "class not registered" error anymore. The difference is replacing the argument CLSCTX_LOCAL_SERVER with CLSCTX_ALL. I think it's got something to do with the fact that I'm using a dll. Does anyone have a more insightful explaination?
It is not really a question because I have already found a solution. It took me a lot of time, that's why I want to explain it here.
Msxml is based on COM so it is not really easy to use in C++ even when you have helpful classes to deal with memory allocation issues. But writing a new XML parser would be much more difficult so I wanted to use msxml.
The problem:
I was able to find enough examples on the internet to use msxml with the help of CComPtr (smart pointer to avoid having to call Release() for each IXMLDOMNode manually), CComBSTR (to convert C++ strings to the COM format for strings) and CComVariant. This 3 helpful classes are ATL classes and need an #include <atlbase.h>.
Problem: Visual Studio 2008 Express (the free version) doesn't include ATL.
Solution:
Use comutil.h and comdef.h, which include some simple helper classes:
_bstr_t replaces more or less CComBSTR
_variant_t replaces more or less CComVariant
_com_ptr_t replaces indirectly CComPtr through the use of _COM_SMARTPTR_TYPEDEF
Small example:
#include <msxml.h>
#include <comdef.h>
#include <comutil.h>
// Define some smart pointers for MSXML
_COM_SMARTPTR_TYPEDEF(IXMLDOMDocument, __uuidof(IXMLDOMDocument)); // IXMLDOMDocumentPtr
_COM_SMARTPTR_TYPEDEF(IXMLDOMElement, __uuidof(IXMLDOMElement)); // IXMLDOMElementPtr
_COM_SMARTPTR_TYPEDEF(IXMLDOMNodeList, __uuidof(IXMLDOMNodeList)); // IXMLDOMNodeListPtr
_COM_SMARTPTR_TYPEDEF(IXMLDOMNamedNodeMap, __uuidof(IXMLDOMNamedNodeMap)); // IXMLDOMNamedNodeMapPtr
_COM_SMARTPTR_TYPEDEF(IXMLDOMNode, __uuidof(IXMLDOMNode)); // IXMLDOMNodePtr
void test_msxml()
{
// This program will use COM
CoInitializeEx(NULL, COINIT_MULTITHREADED);
{
// Create parser
IXMLDOMDocumentPtr pXMLDoc;
HRESULT hr = CoCreateInstance(__uuidof (DOMDocument), NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument, (void**)&pXMLDoc);
pXMLDoc->put_validateOnParse(VARIANT_FALSE);
pXMLDoc->put_resolveExternals(VARIANT_FALSE);
pXMLDoc->put_preserveWhiteSpace(VARIANT_FALSE);
// Open file
VARIANT_BOOL bLoadOk;
std::wstring sfilename = L"testfile.xml";
hr = pXMLDoc->load(_variant_t(sfilename.c_str()), &bLoadOk);
// Search for node <testtag>
IXMLDOMNodePtr pNode;
hr = pXMLDoc->selectSingleNode(_bstr_t(L"testtag"), &pNode);
// Read something
_bstr_t bstrText;
hr = pNode->get_text(bstrText.GetAddress());
std::string sSomething = bstrText;
}
// I'm finished with COM
// (Don't call before all IXMLDOMNodePtr are out of scope)
CoUninitialize();
}
Maybe try using the #import statement.
I've used it in a VS6 project I have hanging around, you do something like this (for illustrative purposes only; this worked for me but I don't claim to be error proof):
#import "msxml6.dll"
...
MSXML2::IXMLDOMDocument2Ptr pdoc;
HRESULT hr = pdoc.CreateInstance(__uuidof(MSXML2::DOMDocument60));
if (!SUCCEEDED(hr)) return hr;
MSXML2::IXMLDOMDocument2Ptr pschema;
HRESULT hr = pschema.CreateInstance(__uuidof(MSXML2::DOMDocument60));
if (!SUCCEEDED(hr)) return hr;
pschema->async=VARIANT_FALSE;
VARIANT_BOOL b;
b = pschema->loadXML(_bstr_t( /* your schema XML here */ ));
MSXML2::IXMLDOMSchemaCollection2Ptr pSchemaCache;
hr = pSchemaCache.CreateInstance(__uuidof(MSXML2::XMLSchemaCache60));
if (!SUCCEEDED(hr)) return hr;
_variant_t vp=pschema.GetInterfacePtr();
pSchemaCache->add(_bstr_t( /* your namespace here */ ),vp);
pdoc->async=VARIANT_FALSE;
pdoc->schemas = pSchemaCache.GetInterfacePtr();
pdoc->validateOnParse=VARIANT_TRUE;
if (how == e_filename)
b = pdoc->load(v);
else
b = pdoc->loadXML(bxmldoc);
pXMLError = pdoc->parseError;
if (pXMLError->errorCode != 0)
return E_FAIL; // an unhelpful return code, sigh....
Another option would to use another XML parser that is already done, such as eXpat. It avoids having to use ATL and the complexities of COM, and is way easier than implementing your own. I suggest this only becasue you've stated that the reason you're looking at msxml is because you don't want to implement your own parser.
You can use TinyXML. It is open source and more over platform independent.
I'm happy I posted my question although I already had a solution because I got several alternative solutions. Thanks for all your answers.
Using another parser such as eXpat or the maybe smaller (not so powerfull but enough for my needs) TinyXML could actually be a good idea (and make it easier to port the program to another operating system).
Using an #import directive, apparently a Microsoft specific extension to simplify the use of COM, is also interesting and brought me to the following web page MSXML in C++ but as elegant as in C#, which explain how to simplify the use of msxml as much as possible.
Why don't you use some MSXML wrapper that would shield you form COM, such as Arabica?