Link to home
Start Free TrialLog in
Avatar of hach-que
hach-queFlag for Australia

asked on

How do I use CoMarshalInterThreadInterfaceInStream to wrap an IHTMLElement?

I need to access an IHTMLElement from another thread, in my Browser Helper Object.  Unfortunately, I've been unable to get it working without causing a segmentation fault when put_innerHTML is called.

Does anyone know how to (correctly) use CoMarshalInterThreadInterfaceInStream in a BHO?
// *********** This is the calls that are made in the main thread ************
       LPSTREAM st;
       CoMarshalInterThreadInterfaceInStream(IID_IHTMLElement, Status, &st);
       m_downloadParam.statusElement = reinterpret_cast<IHTMLElement*>(st);
 
       // Create a thread to download it
       m_pDownloadThread = CreateThread(NULL,
                                                                               0,
                                                                               StartYAIPPDownload,
                                                                               &m_downloadParam,
                                                                               CREATE_SUSPENDED,
                                                                               NULL);
 
// *********** This is the calls that are made in the second thread for downloading ************
DWORD WINAPI StartYAIPPDownload(PVOID pParam)
{
       CBhoApp::DOWNLOADPARAM* parameters =
reinterpret_cast<CBhoApp::DOWNLOADPARAM*>(pParam);
       IHTMLElement* Status = parameters->statusElement;
       BSTR URLToDownload = parameters->strURL;
       YAIPPDownloader* yaippdownloader = new YAIPPDownloader(Status);
       yaippdownloader->Status = Status; // pass the HTML element
       yaippdownloader->AddRef();
       HRESULT hr = URLDownloadToFile(NULL,"http://www.roket-games.com/games/19/gdownload"/*URLToDownload*/,"C:\\test.htm",0,yaippdownloader);
 
       if (hr)
       {
               YAIPPInProgress=false;
               YAIPPStatusText=L"Could Not Download File";
       }
       else
       {
               YAIPPInProgress=false;
               YAIPPStatusText=L"Downloaded File";
       }
       return S_OK;
}
 
// *********** This is the calls that are made in the downloader class's OnProgress function (note that the two Status variables ARE different) ************
if (Status != NULL)
	{
		char ulProgressBuffer [33];
		char ulProgressPercBuffer [33];
		counter++;
		if (ulProgressMax!=0)
			itoa((100/ulProgressMax*ulProgress),ulProgressPercBuffer,10);
		else
			itoa(counter,ulProgressPercBuffer,10);
		itoa(ulProgress,ulProgressBuffer,10);
		std::wstring text;
		text = L"Downloading... ";
		if (ulProgressMax!=0)
			text += ConvertToWideChar(ulProgressBuffer);
		else
			text += ConvertToWideChar(ulProgressPercBuffer);
		BSTR StatText;
		StatText = W2BSTR((text).c_str());
 
		YAIPPStatusText=StatText;
	
		// ************* SEGMENTATION FAULT ON NEXT LINE ****************
		Status->put_innerHTML(YAIPPStatusText);
	}

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of HalfAsleep
HalfAsleep

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of jkr
Take a look at http://svn.openqa.org/fisheye/browse/~raw,r=57/floyd/trunk/src/cpp/ie/IEBrowserObject.cpp - they do that like
void IEBrowserObject::click(IEHTMLElement *element, int button)
{
    COMAutoPtr<IHTMLDOMNode> comObj = element->getCOMObject();
 
	if (comObj != NULL)
	{
        COMAutoPtr<IHTMLElement2> elementObj = comObj.castTo<IHTMLElement2>(IID_IHTMLElement2);
 
	    if (elementObj != NULL)
	    {
            IStream * stream     = NULL;
            IUnknown *unknownPtr = NULL;
 
            elementObj->QueryInterface(IID_IUnknown, (void **)&unknownPtr);
            if (unknownPtr != NULL)
            {
                CoMarshalInterThreadInterfaceInStream(__uuidof(IHTMLElement2), unknownPtr, &stream);
                unknownPtr->Release();
 
                DWORD result = 0;
 
                sendMessageToThread(MSG_CLICK, (WPARAM)button, (LPARAM)stream, &result);
            }
            else
            {
                LOG4CXX_WARN(_logger, L"Cannot retrieve IDispatch pointer for the element object");
            }
        }
        else
        {
            throw IEBrowserException(L"Could not access the IHTMLElement2 interface for the element");
        }
	}
    else
    {
        throw IEBrowserException(L"Could not access the COM object of the element");
    }
}

Open in new window

Avatar of HalfAsleep
HalfAsleep

Although you can use a windows handle and the windows message queue to communicate with the other thread, it is a slight cheat.

Don't get me wrong, it is a well known shortcut, and it is even documented on microsoft's own msdn webpages, but it is still not the "correct" way of doing it.

I had first hand experience of this, when I inherited the job of maintaining an old ActiveX automation server.  This old control relied indeed on a windows handle to communicate to the main thread from all its worker threads.  This made the code fairly straightforward, but it had some drawbacks.

First, the component needed to visually be added to a form (it needs a windows handle, remember?).

Second, and this is the reason we had to do it with "proper" marshaling, some environments will not let you cheat with a windows handle like that.  When we wanted to get our old control to work with WinXP/.NET, we discovered that .NET refused to give us such a windows handle (unmanaged code etc? I'm not sure).  We found that the only way to get around this, was to properly implement marshaling.  It was a bit tricky to get right at first, and the other method/cheat sure is easier to find on the net.  But once we had the proper marshaling code into place, we were not dependent on the windows handle and the windows message queue/pump any more.  The control now works in any environment we throw at it, be it .NET, delphi etc, and it does not even need to be tied to a form, it can just be referenced/created in code.
I used to have a good link to this stuff at work.  I will see if I can find it tomorrow.
Avatar of hach-que

ASKER

Thank you so much!  This stumped me for ages.

For anyone reading this answer, make sure you replace _IHTMLElement and IID__IHTMLElement with IHTMLElement and IID_IHTMLElement respectively.