[Revised yet again for clarity]
I have a C++ program which interacts with a website. The site is IE-specific, and so is my program.
I'm connecting to the running instance of IE in an ordinary way (out of process -- see code). Once I get the IWebBrowser2, I have no problem getting the IHTMLDocument2 and interacting with the individual IHTMLElement objects, filling in fields and clicking buttons.
But if the web page has javascript that calls window.showModalDialog, I'm stuck: I need to interact with the HTML elements in the popup, just like the other pages; but I can't seem to get its IWebBrowser2.
The popup is always titled "Web Page Dialog", and is a window of type Internet Explorer_TridentDlgFrame containing an Internet Explorer_Server. But I can't get the IWebBrowser2 from the Internet Explorer_Server window the way I can when it's a normal IE instance.
I can get the IHTMLDocument2Ptr, but when I try to get the IWebBrowser2 I get an HRESULT of E_NOINTERFACE.
The code is pretty standard stuff, and works fine if it's a 'normal' IE window
IHTMLDocument2Ptr pDoc;
LRESULT lRes;
/* hWndChild is an instance of class "Internet Explorer_Server" */
UINT nMsg = ::RegisterWindowMessage( "WM_HTML_GETOBJECT" );
::SendMessageTimeout( hWndChild, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000,
(DWORD*)&lRes );
LPFNOBJECTFROMLRESULT pfObjectFromLresult =
(LPFNOBJECTFROMLRESULT)::GetProcAddress( hInst, "ObjectFromLresult" );
if ( pfObjectFromLresult != NULL )
{
HRESULT hr;
hr = (*pfObjectFromLresult)( lRes, IID_IHTMLDocument, 0, (void**)&pDoc );
if ( SUCCEEDED(hr) ) {
IServiceProvider *pService;
hr = pDoc->QueryInterface(IID_IServiceProvider, (void **) &pService);
if ( SUCCEEDED(hr) )
{
hr = pService->QueryService(SID_SWebBrowserApp,
IID_IWebBrowser2, (void **) &pBrowser);
// This is where the problem occurs:
// hr == E_NOINTERFACE
}
}
}
In case it matters, this is Vista and IE8. (I emphasize this because both of these introduced breaking changes in my codebase, which had worked fine with XP/IE7.)
Once again, my goal is to get each IHTMLElement and interact with it. I don't have access to the source code of the application which I'm automating.
I'm considering sending keystrokes blindly to the Internet Explorer_Server window, but would rather not.
Edited to add:
Someone suggested getting the child windows and sending them messages, but I'm pretty sure that doesn't work with Internet Explorer_Server; according to Spy++, there's aren't any child windows. (This is not IE-specific. Java applets don't seem to have child windows, either.)
Update
In the comments, Simon Maurer said the above code worked for him, and just to make sure there were no typos he very generously posted a complete standalone app on pastebin. When I used his code, it failed in the same way in the same place, and I realized he thought I wanted to connect to the underlying page, not the popup. So I've edited the text above to remove that ambiguity.
I don't know why you want to get the IServiceProvider or IWebBrowser2 if you just want the IHTMLElement's. You can get them by calling the IHTMLDocument's get_all() method.
This code snippet shows you how this works:
#include <Windows.h>
#include <mshtml.h>
#include <Exdisp.h>
#include <atlbase.h>
#include <SHLGUID.h>
#include <oleacc.h>
#include <comdef.h>
#include <tchar.h>
HRESULT EnumElements(HINSTANCE hOleAccInst, HWND child)
{
HRESULT hr;
UINT nMsg = ::RegisterWindowMessage(_T("WM_HTML_GETOBJECT"));
LRESULT lRes = 0;
::SendMessageTimeout(child, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, (PDWORD)&lRes);
LPFNOBJECTFROMLRESULT pfObjectFromLresult = (LPFNOBJECTFROMLRESULT)::GetProcAddress(hOleAccInst, "ObjectFromLresult");
if (pfObjectFromLresult == NULL)
return S_FALSE;
CComPtr<IHTMLDocument2> spDoc;
hr = (*pfObjectFromLresult)(lRes, IID_IHTMLDocument2, 0, (void**)&spDoc);
if (FAILED(hr)) return hr;
CComPtr<IHTMLElementCollection> spElementCollection;
hr = spDoc->get_all(&spElementCollection);
if (FAILED(hr)) return hr;
CComBSTR url;
spDoc->get_URL(&url);
printf("URL: %ws\n", url);
long lElementCount;
hr = spElementCollection->get_length(&lElementCount);
if (FAILED(hr)) return hr;
printf("Number of elements: %d", lElementCount);
VARIANT vIndex; vIndex.vt = VT_I4;
VARIANT vSubIndex; vSubIndex.vt = VT_I4; vSubIndex.lVal = 0;
for (vIndex.lVal = 0; vIndex.lVal < lElementCount; vIndex.lVal++)
{
CComPtr<IDispatch> spDispatchElement;
if (FAILED(spElementCollection->item(vIndex, vSubIndex, &spDispatchElement)))
continue;
CComPtr<IHTMLElement> spElement;
if (FAILED(spDispatchElement->QueryInterface(IID_IHTMLElement, (void**)&spElement)))
continue;
CComBSTR tagName;
if (SUCCEEDED(spElement->get_tagName(&tagName)))
{
printf("%ws\n", tagName);
}
}
return S_OK;
}
int _tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(NULL);
HINSTANCE hInst = ::LoadLibrary(_T("OLEACC.DLL"));
if (hInst != NULL)
{
HRESULT hr = EnumElements(hInst, (HWND)0x000F05E4); // Handle to Internet Explorer_Server determined with Spy++ :)
::FreeLibrary(hInst);
}
::CoUninitialize();
return 0;
}
Above code works on both: a normal window or a modal window, just pass the correct HWND to the SendMessageTimeout function.
WARNING I use a hard-coded HWND value in this example, if you want to test it you should start an IE instance and get the HWND of the Internet Explorer_Server window using Spy++.
I also advise you to use CComPtr to avoid memory leaks.
Related
I have learned C/C++ Basics and practiced , but I have hard time understanding
Microsoft documentation and find it confusing Documention example
for example : I try to create command line program that should let the user open
folder dialog and choose folder , as result the folders path should be stored in variable
did research and found that there is many ways to achieve this goal but the best way is using IFileDialog::GetFolder method (shobjidl_core.h)
what the difference between file dialogs?
The main question :
How to get folders path as string variable based on user choice from file dialog?
There is c++ resources with practical tutorials ?
I try to understand how I use the following dialog :
Folder dialog
it refernces me to:
BROWSEINFOA structure
Would be very helpful if someone could explain how I can use this folder dialog or something better
any great tutorial of windows/linux file system handling
I used to wcout to print out path
TCHAR path[260];
BROWSEINFO bi = { 0 };
LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
SHGetPathFromIDList(pidl, path);
wcout << path << '\n';
We can use com interface as well :
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow);
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
IFileOpenDialog* pFileOpen;
// Create the FileOpenDialog object.
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
// if object created ( that's com object )
if (SUCCEEDED(hr))
{
// Show the Open dialog box.
hr = pFileOpen->Show(NULL);
// Get the file name from the dialog box.
if (SUCCEEDED(hr))
{
IShellItem* pItem;
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr))
{
PWSTR pszFilePath;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
// Display the file name to the user.
if (SUCCEEDED(hr))
{
MessageBoxW(NULL, pszFilePath, L"File Path", MB_OK);
CoTaskMemFree(pszFilePath);
cout << pszFilePath;
std::string stringtoconvert;
std::wstring temp = std::wstring(stringtoconvert.begin(), stringtoconvert.end());
LPCWSTR lpcwstr = temp.c_str();
}
pItem->Release();
}
}
pFileOpen->Release();
}
CoUninitialize();
}
return 0;
}
Important to mention :
Open and Save As common dialog boxes ( commdlg.h ) have been superseded by the Common Item Dialog
For linux and other platforms there is cross platforms libraries like Qt and more , here is a link for UI solutions
and about Microsoft documentation : I think there should be simpler examples and explanations its complicated & frustrating to learn this way.
I am a bit new to C++, please be gentle.
I am trying to automate Internet Explorer. I have a simple Win32 console application where I am trying to create an instance of IE using a local server.
However, my call to CoCreateInstance() doesn't return an object to initialize my IWebBrowser2 variable.
I could use some help to see what I am missing.
Here is my code:
HRESULT InstanciateIEResult;
HRESULT NavigateResult;
HRESULT ShowBrowserResult;
VARIANT * empty = new VARIANT();
BSTR URL = L"bing.com";
IWebBrowser2* pBrowser2;
InstanciateIEResult = CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER,
IID_IWebBrowser2, (void**)&pBrowser2);
if(pBrowser2)
{
//never reach here
NavigateResult = pBrowser2->Navigate(URL, empty, empty, empty, empty);
ShowBrowserResult = pBrowser2->put_Visible(VARIANT_TRUE);
}
I am also not sure how to decode what the HRESULT returns. If you know, that would be helpful as well.
I was looking at documentation on IWebBrowser2 interface and CoCreateInstance.
You need to call CoInitialize() before using COM objects.
Also, you need to use SysAllocString() to allocate the string.
Example:
#include <windows.h>
#include <MsHTML.h>
#include <Exdisp.h>
#include <ExDispid.h>
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
CoInitialize(NULL);
HRESULT InstanciateIEResult;
HRESULT NavigateResult;
HRESULT ShowBrowserResult;
VARIANT empty;
VariantInit(&empty);
IWebBrowser2* browser = NULL;
HRESULT hr = CoCreateInstance(CLSID_InternetExplorer, NULL,
CLSCTX_LOCAL_SERVER, IID_IWebBrowser2, (void**)&browser);
if (browser)
{
BSTR URL = SysAllocString(L"bing.com");
NavigateResult = browser->Navigate(URL, &empty, &empty, &empty, &empty);
SysFreeString(URL);
ShowBrowserResult = browser->put_Visible(VARIANT_TRUE);
browser->Release();
}
CoUninitialize();
return 0;
}
I have an installer that tries to (re)start my application in the current user context after the installation is done.
The installer runs in the SYSTEM context and before launching the application it attempts (and theoretically succeeds) to impersonate the current user. However, when I look in the task manager, I see that my application is running in the SYSTEM context.
This is (a snippet from) my code:
TCHAR szUsername[128] = _T("");
DWORD dwUsernameSize = 128;
GetUserName(szUsername, &dwUsernameSize);
// Lets the calling process impersonate the security context of a logged-on user.
if (!ImpersonateLoggedOnUser(hToken))
{
throw Win32Exception(GetLastError(), _T("Failed to impersonate current user"));
}
TCHAR szUsername2[128] = _T("");
DWORD dwUsernameSize2 = 128;
GetUserName(szUsername2, &dwUsernameSize2);
MLOGD(_T("ProcessUtils::StartProcessInCurrentUserContext: Successfully impersonated %s"), szUsername2);
ProcessUtils::StartProcess(sExeName, lstParams, sWorkingDir, bWaitToFinish, errCode);
ProcessUtils::StartProcess is a wrapper around CreateProcess.
szUsername contains SYSTEM and szUsername2 contains the current user. So ImpersonateLoggedOnUser is successful.
However, as mentioned above, the process is started in the SYSTEM context, not the current user one.
I'm not sure how helpful this might be, but my installer is written in NSIS and it's calling the function that contains the code from above via a plugin written in C/C++.
Does anyone know why my application doesn't start in the current user context?
Win32 CreateProcess creates a process in the same security context as the caller which is SYSTEM (even though you are impersonating).
Think you need to be calling CreateProcessAsUser.
I had a very similar problem a couple of years ago when I was also
working on an installer application. After A LOT of frustration, caused
by failed attempts to start an application in the context of the current
user using CreateProcessAsUser, I've finally given up. After a thorough
search on the web, I've found a briliant implementation that uses
IShellDispatch2 interface. Here is an example:
#include <Windows.h>
#include <exdisp.h>
#include <Shobjidl.h>
#include <Shlwapi.h>
#include <comutil.h>
#include <SHLGUID.h>
#include <cstdlib>
#include <iostream>
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "comsuppw.lib")
bool ShellExecuteAsCurrentUser(const TCHAR *pcOperation, const TCHAR *pcFileName, const TCHAR *pcParameters,
const TCHAR *pcsDirectory, const DWORD dwShow)
{
bool bSuccess = false;
IShellWindows *psw = NULL;
HRESULT hr = CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psw));
if(SUCCEEDED(hr))
{
HWND hwnd = 0;
IDispatch* pdisp = NULL;
_variant_t vEmpty;
if(S_OK == psw->FindWindowSW(&vEmpty, &vEmpty, SWC_DESKTOP, reinterpret_cast<long*>(&hwnd), SWFO_NEEDDISPATCH, &pdisp))
{
if((hwnd != NULL) && (hwnd != INVALID_HANDLE_VALUE))
{
IShellBrowser *psb;
hr = IUnknown_QueryService(pdisp, SID_STopLevelBrowser, IID_PPV_ARGS(&psb));
if(SUCCEEDED(hr))
{
IShellView *psv = NULL;
hr = psb->QueryActiveShellView(&psv);
if(SUCCEEDED(hr))
{
IDispatch *pdispBackground = NULL;
HRESULT hr = psv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&pdispBackground));
if(SUCCEEDED(hr))
{
IShellFolderViewDual *psfvd = NULL;
hr = pdispBackground->QueryInterface(IID_PPV_ARGS(&psfvd));
if(SUCCEEDED(hr))
{
IDispatch *pdisp = NULL;
hr = psfvd->get_Application(&pdisp);
if(SUCCEEDED(hr))
{
IShellDispatch2 *psd;
hr = pdisp->QueryInterface(IID_PPV_ARGS(&psd));
if(SUCCEEDED(hr))
{
_variant_t verb(pcOperation);
_variant_t file(pcFileName);
_variant_t para(pcParameters);
_variant_t dir(pcsDirectory);
_variant_t show(dwShow);
if(SUCCEEDED(psd->ShellExecute(file.bstrVal, para, vEmpty, verb, show)))
bSuccess = true;
psd->Release();
psd = NULL;
}
pdisp->Release();
pdisp = NULL;
}
}
pdispBackground->Release();
pdispBackground = NULL;
}
psv->Release();
psv = NULL;
}
psb->Release();
psb = NULL;
}
}
pdisp->Release();
pdisp = NULL;
}
psw->Release();
psw = NULL;
}
return bSuccess;
}
int main(int argc, char *argv[])
{
CoInitialize(NULL);
if(ShellExecuteAsCurrentUser(L"open", L"notepad", nullptr, nullptr, SW_SHOWNORMAL))
std::cout << "SUCCESS" << std::endl;
CoUninitialize();
return 0;
}
This is just a quick demo, the implementation of ShellExecuteAsCurrentUser can be
improved by using smart pointers for COM interfaces and some refactoring. This method
worked for me on versions WinXP SP3 - Win 8.1, not sure if it works on Windows 10. For
more details, check the authors github page:
https://github.com/lordmulder/stdutils/tree/master/Contrib/StdUtils
If you had read the documentation for CreateProcess, you would have found the answer to your question in the first three sentences:
Creates a new process and its primary thread. The new process runs in the security context of the calling process.
If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token.
There really isn't much else to say; the behaviour you describe is as documented. If you want to create a process as another user, you must use CreateProcessAsUser or one of the related functions.
I’m stuck with an issue and I’m pretty sure that this forum will be able to help me for this.
I’m trying to add few automation objects to the scripts (hosted via a separate process (ATL EXE server)).
Also I am trying to sink the events from these automation objects inside my ATL EXE script engine.
To achive this I’m using CDispExSinkConnector as described in http://www.codeproject.com/Articles/3326/Extending-the-Internet-Explorer-Scripting-Engine
As described I’m using IDispatchEx::GetDispID()to create new members inside the scripting engine. Once the member DISPID has been created, I’m using InvokeEx()to set the property value.
But these method calls return RPC_E_SYS_CALL_FAILED.
Note- Here I get the IDispEx pointer from an OUT-OF-PROC IDispatch pointer.
More detailed descriptions:
I do have a BrowserCtrl hosted by CAxWindow in an ATL EXE server which I’m using to create HTML plug-ins for my MFC client application.
The ATL EXE server provides wrapper methods to IWebBrowser2 methods so that I can call methods like IWebBrowser2Ptr->Navigate2().
Also I can subscribe to IWebBrowser2 events like OnDocumentComplete(….)in this ATL EXE server and ultimately forward those to my MFC Client application to handle those there.
Below are few code snippets for my ATL EXE server (BrowserCtrl):
1st is CreateControl() which creates the CAxWindow, creates the WebBrowser Control and host the control on internal default CAxHostWindow.
STDMETHODIMP BrowserCtrl::CreateControl(ULONG hwndParent, LONG left,LONG top, LONG right, LONG bottom)
{
CRect rc(left, top, right, bottom);
// Create the AX host window. m_wndBrowserCtrl is CAxWindow object
HWND hwndAxHost = m_wndBrowserCtrl.Create(reinterpret_cast<HWND>(hwndParent), &rc, L"Shell.Explorer", WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, ID_BROWSERCTRL );
//Obtain a pointer to the container object
CComQIPtr<IAxWinHostWindow> ptrWinHost;
HRESULT hr = m_wndBrowserCtrl.QueryHost(&ptrWinHost);
LPOLESTR lpszCLSID = NULL;
hr = StringFromCLSID(CLSID_WebBrowser, &lpszCLSID);
// Create the WebBrowser Ctrl
CComPtr<IUnknown> cpIUnknown = NULL;
hr = ptrWinHost->CreateControlEx(lpszCLSID, m_wndBrowserCtrl, NULL, &cpIUnknown, __uuidof(SHDocVw::DWebBrowserEvents2), reinterpret_cast<IUnknown*>(reinterpret_cast<DWebBrowserEvents2Impl*>(this)));
// Get the IWebBrowser2 interface pointer for the control
// CComQIPtr<IWebBrowser2> m_cpIBrowser;
m_cpIBrowser = cpIUnknown;
return S_OK;
}
//Now the wrapper method for IWebBrowser2 methods
STDMETHODIMP BrowserCtrl::Navigate2(VARIANT *pwszUrl, VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData, VARIANT *Headers)
{
HRESULT hr = m_cpIBrowser->Navigate2(pwszUrl, Flags,TargetFrameName,PostData,Headers);
}
//Now the event handlers for the IWebBrowser2 events:
void __stdcall BrowserCtrl::OnDocumentComplete(IDispatch* pDisp, VARIANT* pvaURL)
{
Fire_OnDocumentComplete(pDisp , pvaURL);// forwarding to the MGC client APP
}
Now the issue:
1.On the MFC Client APP and in the event handler function OnDocumentComplete() I do the following:
void MFCClientApp::HandleDocumentComplete(LPDISPATCH pIDisp, VARIANT* pvaURL)
{
CComPtr<IDispatch> pDispDoc;
CComQIPtr<IDispatch> pScriptDisp;
CComQIPtr<IDispatchEx> pDispEx;
CComQIPtr<IHTMLDocument> pHTMLDoc;
CComQIPtr<IWebBrowser2> pWebBrowser;
if (pIDisp != NULL)
{
pWebBrowser = pIDisp;
//CComPtr<IDispatch> ptrIDisp;
//BOOL bResult = GetOPBrowser()->GetDisp(&ptrIDisp);
//pWebBrowser = ptrIDisp;
pWebBrowser->get_Document(&pDispDoc);
if (pDispDoc != NULL)
{
// Get the IDispatchEx interface
pHTMLDoc = pDispDoc;
if (pHTMLDoc != NULL)
{
pHTMLDoc->get_Script(&pScriptDisp);
pDispEx = pScriptDisp;
if (pDispEx != NULL)
{
CDispExSinkConnector pDispExSinkConnector = new CDispExSinkConnector();
Hr = pDispExSinkConnector->SetDispEx(pDispEx); // here the Hr returned is 0x80010100 “System Call Failed” i.e. RPC_E_SYS_CALL_FAILED
……
……..
………
}
Here is the SetDispEx() and internal related functions:
void CDispExSinkConnector::SetDispEx(IDispatchEx *pDispEx)
{
CComBSTR bstrThisGuid("AC0B188C-6B55-408f-9E8C-821B9B5467CB"); // #!I8NIGNORE
HRESULT hr = m_pDispExGIT.Attach(pDispEx);//add it to the GIT Table
ASSERT(pDispEx);
// We need to add ourselves to the script engine
AddNamedObject(bstrThisGuid, this);
}
HRESULT CDispExSinkConnector::AddNamedObject(BSTR bsName, IDispatch *pDisp)
{
HRESULT hr = 0;
CComPtr<IDispatchEx> pDispEx;
GetDispEx(&pDispEx);
if(pDispEx == NULL)
return E_FAIL;
if(!bsName || SysStringLen(bsName) == 0 || !pDisp)
return E_INVALIDARG;
// Also, add our root objects into the script namespace
DISPID dispIdThis = 0;
hr = pDispEx->GetDispID(bsName, fdexNameEnsure | fdexNameCaseSensitive,&dispIdThis); // here the Hr returned is 0x80010100 “System Call Failed” i.e. RPC_E_SYS_CALL_FAILED
if (hr == DISP_E_UNKNOWNNAME)
…
..
}
Please help me to find out the root cause and a solution to resolve this issue.
Your quick help will be much appreciated.
Thanks,
SP
I am working on a browser automation framework, which automates Internet Explorer, as well as other browsers. I am running into an intermittent problem when attempting to launch IE. The framework launches IE using the IELaunchURL API if it is present, and uses CreateProcess if not. The code to obtain the IWebBrowser2 interface is as follows:
// hwndBrowser is obtained by calling ::EnumWindows() with a function that
// compares the process ID of the window handle to the known process ID of
// the IE instance.
CComPtr<IHTMLDocument2> document;
LRESULT result;
::SendMessageTimeout(hwndBrowser,
WM_HTML_GETOBJECT,
0L,
0L,
SMTO_ABORTIFHUNG,
1000,
(PDWORD_PTR)&result);
// oleacc_instance_handle is obtained from ::LoadLibrary("oleacc.dll")
LPFNOBJECTFROMLRESULT object_pointer = reinterpret_cast<LPFNOBJECTFROMLRESULT>(
::GetProcAddress(oleacc_instance_handle, "ObjectFromLresult"));
if (object_pointer != NULL) {
HRESULT hr = (*object_pointer)(result,
IID_IHTMLDocument2,
0,
reinterpret_cast<void **>(&document));
if (SUCCEEDED(hr)) {
CComPtr<IHTMLWindow2> window;
hr = document->get_parentWindow(&window);
if (SUCCEEDED(hr)) {
// http://support.microsoft.com/kb/257717
CComQIPtr<IServiceProvider> provider(window);
if (provider) {
CComPtr<IServiceProvider> child_provider;
hr = provider->QueryService(SID_STopLevelBrowser,
IID_IServiceProvider,
reinterpret_cast<void **>(&child_provider));
if (SUCCEEDED(hr)) {
IWebBrowser2* browser;
hr = child_provider->QueryService(SID_SWebBrowserApp,
IID_IWebBrowser2,
reinterpret_cast<void **>(&browser));
if (SUCCEEDED(hr)) {
// The IWebBrowser2 pointer is passed back to the caller.
// process_window_info->pBrowser = browser;
}
}
}
}
}
}
Now for the problem: It seems that we can always successfully retrieve the IHTMLDocument2 object. However, when we attempt to call get_parentWindow(), we sometimes receive a "class not registered" result (0x80040154 REGDB_E_CLASSNOTREG), most often when launching a new instance of IE after closing a previous one. We can get the IWebBrowser2 interface by omitting the call to get_parentWindow() and simply calling QueryService directly on the document, but we will receive this error further down the line when we try to manipulate parts of the document. What could cause the get_parentWindow() call to fail?
Note that the entirety of the code can be found in context here.
The problem was in the use of threading in the application. We were not properly waiting for a thread to terminate before launching a new instance of IE.