I have an application that I've been using to parse data from an HTML document. The application has been working for a few years until this week when the QueryInterface method for the IID_IPersistStreamInit started failing. The call to QueryInterface is returning -2147467262 which fails the SUCCEEDED(hr) test. Any ideas why this quit working?
Thanks,
Wade
if (!myIE->IsValid())
return;
HRESULT hr;
LPDISPATCH lpDispatch = NULL;
LPOLECOMMANDTARGET lpOleCommandTarget = NULL;
LPPERSISTSTREAM lpPersistStream = NULL;
lpDispatch = myIE->GetHtmlDocument();
ASSERT(lpDispatch);
if (lpDispatch == NULL)
AfxMessageBox("Couldn't get IHTMLDocument2 interface!");
else
{
hr = lpDispatch->QueryInterface(IID_IPersistStreamInit, (void**) &lpPersistStream);
if (SUCCEEDED(hr) && lpPersistStream != NULL)
At what point are you executing the code above? In case it's not done, you should execute it only after the followings:
Navigating to about:blank to have mshtml loaded properly
Make sure the DocumentComplete event is called, meaning the navigation has completed, before you move on.
Only then is it safe to ask for the stream interface. For more, see Loading HTML content from a Stream.
Now, if all this is known and taken care of, you might pursue the solution from the other direction. The error code means "No such interface supported". I'd try finding out what is the component that contains that interface, and then re-register it. But given this is IE stuff you're dealing with, I kind of doubt it's installation got screwed.
Related
I'm creating a c++ Win32 application which should show some Toast notifications. One of them contains a progress bar. I need to bind its value with my application in order to update it while the status changes in the currently running operation.
The Toast interface containing the progress bar is defined as follow:
<toast>
<visual>
<binding template='ToastGeneric'>
<text>Backup in progress</text>
<progress title='Working folder' value='0' status='Starting backup...' valueStringOverride='0/1 files' />
</binding>
</visual>
</toast>
To show the Toast notification I call the following function, by passing the above interface in the toastContent string:
bool ShowToastNotification(const std::wstring toastContent)
{
if (!toastContent.length())
return false;
// build XML
::CComPtr<ABI::Windows::Data::Xml::Dom::IXmlDocument> pDoc;
HRESULT hr = DesktopNotificationManagerCompat::CreateXmlDocumentFromString(toastContent.c_str(), &pDoc);
if (FAILED(hr))
return false;
// create the notifier. Classic Win32 apps MUST use the Compat method to create the notifier
::CComPtr<ABI::Windows::UI::Notifications::IToastNotifier> pNotifier;
hr = DesktopNotificationManagerCompat::CreateToastNotifier(&pNotifier);
if (FAILED(hr))
return false;
// create the Toast notification (using helper method from Compat library)
::CComPtr<ABI::Windows::UI::Notifications::IToastNotification> pToast;
hr = DesktopNotificationManagerCompat::CreateToastNotification(pDoc, &pToast);
if (FAILED(hr))
return false;
// get access to IToastNotification4, which should contain the binding methods
::CComPtr<ABI::Windows::UI::Notifications::IToastNotification4> pToastData;
pToast->QueryInterface(ABI::Windows::UI::Notifications::IID_IToastNotification4, (void**)&pToastData);
ABI::Windows::UI::Notifications::INotificationData* pData = nullptr;
// FIXME how may I create a valid INotificationData here?
// bind the data with the interface
hr = pToastData->put_Data(pData);
if (FAILED(hr))
return false;
// show it
hr = pNotifier->Show(pToast);
if (FAILED(hr))
return false;
return true;
}
As you can see in the FIXME part above, there is a missing part to achieve the binding before showing the Toast notification, and unfortunately there is absolutely NO documentation from Microsoft which explain how to perform that in a simple C++ Win32 application. The below one explain clearly how to perform a such binding, but only in C#:
https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-progress-bar?tabs=builder-syntax
Even without documentation I could find that the IToastNotification4 seems to be a valid interface to attach a INotificationData container to my Toast, similar to what is done in the above mentioned documentation. However I don't know how to create a valid INotificationData instance. I saw that a CreateNotificationDataWithValuesAndSequenceNumber() function exists in the INotificationDataFactory interface, but same thing, I face a cruel miss of documentation about how to obtain a such interface.
Can someone explain me how I may bind my C++ Win32 application with my progress bar on my Toast notification, in order to send it messages to update its interface? Or can someone explain me how to get the above mentioned interfaces, in order to perform something similar to the above mentioned C# documentation? Or at least can someone point me a documentation which may give information about the INotificationData and INotificationDataFactory interfaces, and how to use them to perform the binding with my Toast progress bar?
If a WinRT class can be created from C#, it means it's activatable (or it's associated with another "statics" class which is), which you can check if you go to NotificationData documentation:
[Windows.Foundation.Metadata.Activatable(262144, "Windows.Foundation.UniversalApiContract")]
[Windows.Foundation.Metadata.Activatable(typeof(Windows.UI.Notifications.INotificationDataFactory), 262144, "Windows.Foundation.UniversalApiContract")]
[Windows.Foundation.Metadata.ContractVersion(typeof(Windows.Foundation.UniversalApiContract), 262144)]
[Windows.Foundation.Metadata.MarshalingBehavior(Windows.Foundation.Metadata.MarshalingType.Agile)]
[Windows.Foundation.Metadata.Threading(Windows.Foundation.Metadata.ThreadingModel.Both)]
public sealed class NotificationData
{...}
If it's activatable, then you just have to find it's activation class id, and call RoActivateInstance . The class id is visible in the corresponding .h file (here windows.ui.notifications.h) and it's usually very straightforward: <namespace> + '.' + <class name>:
...
extern const __declspec(selectany) _Null_terminated_ WCHAR RuntimeClass_Windows_UI_Notifications_NotificationData[] = L"Windows.UI.Notifications.NotificationData";
...
So here is how you can create it:
HRESULT CreateNotificationData(INotificationData** data)
{
if (!data)
return E_INVALIDARG;
*data = nullptr;
IInspectable* instance;
auto hr = RoActivateInstance(HStringReference(RuntimeClass_Windows_UI_Notifications_NotificationData).Get(), &instance);
if (FAILED(hr))
return hr;
hr = instance->QueryInterface(data);
instance->Release();
return hr;
}
PS: using C++/WinRT is usually easier to use than ATL, WRL or WIL for WinRT types.
I wrote the following C++ code, trying to perform some OLE automation tasks
for currently-running Photoshop:
IUnknown* unk = NULL;
IDispatch* disp = NULL;
CLSID clsId;
if (FAILED(CLSIDFromProgID(L"Photoshop.Application", &clsId)))
goto err;
puts("CLSIDFromProgID succeeded");
if (FAILED(GetActiveObject(&clsId, NULL, &unk)))
goto err;
puts("GetActiveObject succeeded");
if (FAILED(IUnknown_QueryInterface(unk, &IID_IDispatch, (void**)&disp)))
goto err;
puts("QueryInterface succeeded");
DISPID dispId;
HRESULT resultOfGettingId = IDispatch_GetIDsOfNames(disp, &IID_NULL,
(LPOLESTR[1]){L"ActiveDocument"}, 1,
LOCALE_USER_DEFAULT, &dispId);
if (FAILED(resultOfGettingId))
{
//The code always goes here.
//The result code is printed as 0X8001010A,
//which is RPC_E_SERVERCALL_RETRYLATER.
printf("GetIDsOfNames for <ActiveDocument> failed. Result code: %#lX.\n",
resultOfGettingId);
goto err;
}
puts("GetIDsOfNames for Selection succeeded");
The above C++ code always prints the following text (whenever I run it):
GetIDsOfNames for <ActiveDocument> failed. Result code: 0X8001010A.
After searching for RPC_E_SERVERCALL_RETRYLATER, I found that the error code
seems to mean that the application is busy. But Photoshop is just opened, and
not being manipulated at all, and just contains only a single empty document.
How can it always be busy?
And why does the equivalent C# code such as
try
{
activeDocument.activeLayer.textItem.font = "FZCYJW--GB1-0";
}
catch(e)
{
...
}
works fine?
It doesn't make sense that Photoshop just happens to be busy everytime
when the C++ program is trying to connect.
Something problematic other than the so-called busyness must be hiden
in the details but I just have no clue.
Edit:
Issued resloved!
I've found the culprit of "busyness". If Photoshop is in the state of selecting some text, then it fails, otherwise it succeeds. So when some text is selected, Photoshop is in a state where it thinks handleing COM call-ins out of process isn't a good idea.
I've found the culprit of "busyness". If Photoshop is in the state of selecting some text, then it fails, otherwise it succeeds. So when some text is selected, Photoshop is in a state where it thinks handling COM call-ins out of process isn't a good idea.
Currently I am working on embedding a HTML editor into a C++ WinAPI application using the MSHTML component.
I got everything set up (activating editing mode, changing font face, etc.), but now I have to support inserting images. MSHTML already has support for it built in, but this support is - to my knowledge - not enough.
Somehow I need a way to intercept the insertion of images into the HTML-editor, since I have to create a list of images in the UI of our application. So, whenever the user uses the default-dialog of the MSHTML-component to insert an image or updates its source (e.g. from file://xyz.jpg to file://abc.jpg), I want my code to be notified.
I already looked at the conecpt of "Edit Designers", the implementation of IHTMLEditHost, or the DWebBrowserEvents2 interface. But nothing seems to do the trick.
Perhaps someone can give me a hint?
Okay,
it looks like you cannot explicitly subscribe for specific changes of the document. What you can do is to create a so-called "Change Sink".
Everytime you change the document, either by user input or programmatically, you can get a notification that "something" changed in your document. This can be done by implementing the IHTMLChangeSink interface and attaching it to the IMarkupContainer2 of the IHTMLDocument2.
Example code (not complete):
class CHTMLChangeSink : public IHTMLChangeSink
{
public:
// Implement IUnknown: QueryInterface, AddRef, Release
// Implement IHTMLChangeSink:
HRESULT STDMETHODCALLTYPE Notify()
{
// Notify() is called everytime the document changes
return S_OK;
}
};
CHTMLChangeSink * pChangeSink = new CHTMLChangeSink();
IHTMLDocument2 * pDoc; // lets suppose we already have it
IMarkupContainer2 * pMarkupContainer = nullptr;
if (pDoc->QueryInterface(IID_IMarkupContainer2, reinterpret_cast<void **>(&pMarkupContainer)) == S_OK) {
DWORD dwCookie = 1;
// registration is done here
pMarkupContainer->RegisterForDirtyRange(pChangeSink, &dwCookie);
}
Please be aware, that the document has to be loaded completely (register for DIID_DWebBrowserEvents2::DocumentComplete).
From now on, whenever a change in the document occurs, your Notify-method will be called and you can do further processing to find out what has changed (e.g. process the list of images in the document).
Have fun!
I am implementing an Internet Explorer Browser Helper Object that should catch the DISPID_FILEDOWNLOAD event.
I first implemented this in C# which worked great except that I also need the cookies that go with the URL so need to call InternetGetCookiesEx. As .NET runs in it's own process it does not return me the session cookies so is no good.
I then wrote a quick test DLL in C++ so that it is loaded into the same process as IE which works great for the cookies but I now have a new problem:
I am getting calls to DISPID_FILEDOWNLOAD in my Invoke function for each page load when I only want them for an actual download.
In the C# version I only got a call to WebBrowser.FileDownload for an actual download but it seems that the C++ version is sending a DISPID_FILEDOWNLOAD even for each page load.
STDMETHODIMP CIEHlprObj::Invoke(
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pDispParams,
VARIANT* pvarResult,
EXCEPINFO* pExcepInfo,
UINT* puArgErr
)
{
USES_CONVERSION;
if (!pDispParams)
return E_INVALIDARG;
LPOLESTR lpURL = NULL;
m_spWebBrowser2->get_LocationURL(&lpURL);
int i = 0;
switch (dispidMember)
{
case DISPID_BEFORENAVIGATE2:
case DISPID_BEFORENAVIGATE:
sCurrentFile=NULL;
if (pDispParams->cArgs >= 5 && pDispParams->rgvarg[5].vt == (VT_BYREF | VT_VARIANT))
{
CComVariant varURL(*pDispParams->rgvarg[5].pvarVal);
varURL.ChangeType(VT_BSTR);
char* myStr = OLE2T(varURL.bstrVal);
if (myStr)
{
sCurrentFile = AllocateString(myStr);
sCurrentFileW = varURL.bstrVal;
}
}
break;
case DISPID_FILEDOWNLOAD:
// CALLED FOR EACH PAGE LOAD!
if(sCurrentFile)
{
TCHAR cookies[8192];
DWORD size = 8192;
BOOL ret = InternetGetCookieEx(sCurrentFile,
0,
cookies,
&size,
INTERNET_COOKIE_HTTPONLY,
0);
::MessageBox(0, sCurrentFile, "Downloading called multiple times!!", MB_OK);
}
break;
default:
break;
}
return S_OK;
}
Is there some filter that needs checking somewhere to know if a DISPID_FILEDOWNLOAD event relates to a file load or an actual file download?
Many thanks
UPDATE:
On closer inspection it seems that the C# managed code version is actually doing the same, I just didn't notice it the firs time around.
It seems that the FileDownload event is being called in the following circumstances:
New window / tab opened
New domain connected to (maybe a new keep-alive connection?)
An actual download
Obviously I only want the even on the actual download event.
As a possible solution to this I notice MS provide a ActiveDocument (BOOL) argument along with the event.. according to the Microsoft documentation the ActiveDocument argument means:
A Boolean that specifies whether the file is an Active Document
http://msdn.microsoft.com/en-us/library/bb268220(v=vs.85).aspx
Not very helpful but if I log the DISPID_FILEDOWNLOAD events to a text file and look at them later it appears that I should ignore all events where ActiveDocument = true
Getting the params is quite easy:
BOOL cancel = *pDispParams->rgvarg[0].pboolVal;
BOOL active = pDispParams->rgvarg[1].boolVal;
So the code would just need updating to something like:
if (active)
return S_OK;
I can only assume that this is some event the IE is sending telling the BHO that a page is downloading.. not a file, although that should mean that I get a DISPID_FILEDOWNLOAD event for each page load, which I don't.. only new tabs/browser instances and some new connections.
Hopefully someone else will be able to contribute to this and clarify what is the best way of handling this as it feels like a bit of a hack.
The MSDN documentation on WebBrowser Customization explains how to prevent new windows from being opened and how to cancel navigation. In my case, my application is hosting an IWebBrowser2 but I don't want the user to navigate to new pages within my app. Instead, I'd like to open all links in a new IE window. The desired behavior is: user clicks a link, and a new window opens with that URL.
A similar question was asked and answered here and rather than pollute that answered post, it was suggested I open a new discussion.
The members on the related post suggested I should be able to do this by trapping DISPID_BEFORENAVIGATE2, setting the cancel flag, and writing code to open a new window, but I've found out that the browser control gets lots of BeforeNavigate2 events that seem to be initiated by scripts on the main page. For example, amazon.com fires BeforeNavigate2 events like crazy, and they are not a result of link invocation.
Replies appreciated!
What I ended up doing was using IHTMLDocument directly rather than IWebBrowser. IWebBrowser is a superset of IHTMLDocument, and the navigation model implemented by IWebBrowser isn't customizable to the degree I wanted.
I actually got MS Developer Support involved and this approach was their recommendation. They say this is what Outlook uses for HTML-based email, which is the user experience I wanted to emulate. They also confirmed that there's no reliable way to filter the OnBeforeNavigate events that result from user action from those that result from script activity.
Hope this helps anybody facing the same issues. It wasn't too hard to port the code to use IHTMLDocument. If you end up doing this, you may also find yourself looking for a way to figure out when the document is done loading. To do that, hook HTMLDocumentEvents instead of DWebBrowserEvents, and look for the DISPID_HTMLDOCUMENTEVENTS_ONREADYSTATECHANGE event. It doesn't tell you what the ready state is; you need to call IHTMLDocument::get_readyState and parse the resulting string. Goofy, but there you go.
You can bind to onclick event before document is complete while creating browser in OnCreate() using IHTMLDocument2::put_onclick():
#include <comutil.h>
ClickEvents<RootFrame> clickEvents;
_variant_t clickDispatch;
clickDispatch.vt = VT_DISPATCH;
clickDispatch.pdispVal = &clickEvents;
CComQIPtr<IDispatch> dispatch;
hr = webBrowser2->get_Document(&dispatch);
ASSERT_EXIT(SUCCEEDED(hr), "webBrowser->get_Document(&dispatch)");
CComQIPtr<IHTMLDocument2> htmlDocument2;
hr = dispatch->QueryInterface(IID_IHTMLDocument2, (void**) &htmlDocument2);
ASSERT_EXIT(SUCCEEDED(hr), "dispatch->QueryInterface(&htmlDocument2)");
htmlDocument2->put_onclick(clickDispatch);
ClickEvents class implements IDispatch, you only need to implement Invoke method, in rest return E_NOTIMPL:
HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
HRESULT hr;
CComQIPtr<IWebBrowser2> webBrowser2;
hr = rootFrame->GetDlgControl(rootFrame->rootview.GetDlgCtrlID(), IID_IWebBrowser2, (void**) &webBrowser2);
ASSERT_EXIT(SUCCEEDED(hr), "rootframe->GetDlgControl(IID_IWebBrowser2) failed");
CComQIPtr<IDispatch> dispatch;
hr = webBrowser2->get_Document(&dispatch);
ASSERT_EXIT(SUCCEEDED(hr), "webBrowser2->get_Document(&dispatch)");
CComQIPtr<IHTMLDocument2> htmlDocument2;
hr = dispatch->QueryInterface(IID_IHTMLDocument2, (void**) &htmlDocument2);
ASSERT_EXIT(SUCCEEDED(hr), "dispatch->QueryInterface(&htmlDocument2)");
CComQIPtr<IHTMLWindow2> htmlWindow2;
hr = htmlDocument2->get_parentWindow((IHTMLWindow2**) &htmlWindow2);
ASSERT_EXIT(SUCCEEDED(hr), "htmlDocument2->get_parentWindow(&htmlWindow2)");
CComQIPtr<IHTMLEventObj> htmlEvent;
hr = htmlWindow2->get_event(&htmlEvent);
ASSERT_EXIT(SUCCEEDED(hr), "htmlWindow2->get_event(&htmlEvent)");
CComQIPtr<IHTMLElement> htmlElement;
hr = htmlEvent->get_srcElement(&htmlElement);
ASSERT_EXIT(SUCCEEDED(hr), "htmlEvent->get_srcElement(&htmlElement)");
CComBSTR hrefAttr(L"href");
VARIANT attrValue;
VariantInit(&attrValue);
hr = htmlElement->getAttribute(hrefAttr, 0 | 2, &attrValue); // 0 = case insensitive, 2 = return BSTR
ASSERT_EXIT(SUCCEEDED(hr), "htmlElement->getAttribute()");
wchar_t href[2084]; // maximum url length in IE, http://support.microsoft.com/kb/208427
wcsncpy_s(href, _countof(href), attrValue.bstrVal, _TRUNCATE);
if (!rootFrame->IsURLAllowed(href)) {
VARIANT variant;
variant.vt = VT_BOOL;
variant.boolVal = VARIANT_FALSE;
htmlEvent->put_returnValue(variant);
ShellExecute(0, L"open", href, 0, 0, SW_SHOWNORMAL);
}
return S_OK;
}
As you can see after querying some interfaces I finally have the element that got clicked, then I call IsURLAllowed() defined in my root frame to check whether to allow opening url in current webbrowser window or whether to open it using default browser on user's computer.
This handles all links even if they were appended to document using javascript.
The same should be done with "onsubmit" events for forms.
I also think I have a solution for "window.location" redirects in javascript, I haven't tested it yet, but I will soon test it and I will update this answer then. You could use a combination of "onunload" and "onbeforeunload" events along with DWebBrowserEvents2::BeforeNavigate2(), after onunload/onbeforeunload are called you will know that user is leaving current page so now in BeforeNavigate2() you can cancel it. You can attach unload events using IHTMLWindow2::put_onunload() and IHTMLWindow2::put_onbeforeunload().
See sources of a complete solution for the "onclick" below.
AttachClickEvents in BrowserFrame:
http://code.google.com/p/phpdesktop/source/browse/phpdesktop-msie/msie/browser_frame.h?r=709d00b991b5#125
Invoke in ClickEvents(IDispatch):
http://code.google.com/p/phpdesktop/source/browse/phpdesktop-msie/msie/click_events.h?r=a5b0b350c933#132
I'm hypothesising here but yet another approach could be to maintain a count of navigation events, incrementing the counter on DISPID_BEFORENAVIGATE2 and decrementing it on occurrences of DISPID_NAVIGATECOMPLETE2 and DISPID_NAVIGATEERROR. With that in place, you could speculate that whenever you get DISPID_BEFORENAVIGATE2 and your counter is at zero, it is actual user navigation / link invocation.
I have no idea whether this approach would work, or whether those are the right events you'd need to make it work, but it could be worth investigating.
You could try a different approach instead and physically add the attribute target="_blank"
to all <a> tags in the rendered document.
This approach would involve waiting for DISPID_DOCUMENTCOMPLETE and then using IHTMLDocument3::getElementsByTagName() to fetch all of the anchor elements. You would then use IHTMLElement::setAttribute() to set target="_blank" on each of them.
It seems to me, that it you want "to open all links in a new IE window", it means that you want that the opening of new windows must be done in another process. The easiest way to do so: using CreateObject("InternetExplorer.Application") way (see another question which solve a problem, which is opposite to your question: InternetExplorer.Application object and cookie container). With this way you will receive the best isolation from your application and the user who clicks on the link receive all possibilities which exist in IE. You should of cause continue usage of BeforeNavigate2 events to find out the moment when "a new IE window" should be opened.