Solved

Using WebBrowser2 Interface in Multithreaded application.

Posted on 2003-10-31
5
447 Views
Last Modified: 2009-12-16
david_johns and I are working on an application together.  I had more points available so I am repeating his question with more points to hopefully attract an answer:

We are using a WebBrowser2 interface to do some work for us in our application.  We are trying to introduce multithreading allowing the browser to pick up information in the background and let the user continue using the application.

We seem to lose control of the WebBrowser2 (it will no longer navigate or recieve when one thread that was using it exits and another tries to access it.  The browser is still running, but the WebBrowser2 seems to be a corrupt pointer.

500 points for the person who can figure out how to acess the same WebBrowser2 interface inside of a thread different from the one it was created in or somehow store a reference to the browser itself from which we can restore the WebBrowser2 interface before starting to use it in the second thread.  Thanks for your help.

0
Comment
Question by:stringsandbeyond
  • 3
  • 2
5 Comments
 
LVL 86

Accepted Solution

by:
jkr earned 500 total points
Comment Utility
As I pointed out in http://www.experts-exchange.com/Programming/Programming_Languages/Cplusplus/Q_20780954.htm (that ended up with *no* feedback from your co-worker)

See see http://support.microsoft.com/default.aspx?scid=kb;en-us;150777 ("INFO: Descriptions and Workings of OLE Threading Models")

See also http://support.microsoft.com/default.aspx?scid=kb;en-us;206076 ("206076 - MARSHAL.EXE: How To Marshal Interfaces Across Apartments")

To elaborate (http://msdn.microsoft.com/msdnmag/issues/1100/wicked/default.aspx):

"Always Call CoInitialize(Ex)
A few months ago, I received an e-mail message from a friend at a prominent hardware company. His company had written a fairly sophisticated COM-based application that employed a number of in-proc and local (out-of-process) COM components. At startup, the application created COM objects to serve various client threads running in multithreaded apartments (MTAs). The objects were also consigned to MTAs, which meant that interface pointers could be freely exchanged among client threads. In testing, my friend discovered that everything worked fine until the application was ready to shut down. Then, for no apparent reason, calls to Release (calls that had to be made in order to properly free the interface pointers that the clients were holding) were locking up. His question: "What on earth is going wrong?"
The answer, as it turned out, was quite simple. The application's developers had done everything right with one very important exception: they weren't calling CoInitialize or CoInitializeEx in all of their client threads. One of the fundamental rules of modern-day COM is that every thread that uses COM should first initialize COM by calling either CoInitialize or CoInitializeEx. There are no exemptions to this rule. Among other things, CoInitialize(Ex) places a thread inside an apartment and initializes important per-thread state information that is required for COM to operate properly. Failure to call CoInitialize(Ex) typically manifests itself early in the application's lifetime in the form of failed COM API functions, most commonly activation requests. But sometimes the problems are more insidious and don't manifest themselves until it's too late, as in the case of calls to Release that disappear and never return. Once the dev team added CoInitialize(Ex) calls to all the threads that touched COM, their problems went away.
Ironically, Microsoft is one of the reasons that COM programmers sometimes don't call CoInitialize(Ex). The Microsoft® Knowledge Base contains documents stating that calls to CoInitialize(Ex) aren't strictly necessary for MTA-based threads (see article Q150777 for an example). Yes, there are cases in which you can get away with skipping CoInitialize(Ex). No, you shouldn't do it unless you know what you're doing and can be absolutely sure that you'll suffer no debilitating effects. It's never harmful to call CoInitialize(Ex), so my advice to COM programmers is to always call it from any thread that does anything whatsoever with COM.

Don't Pass Raw Interface Pointers Between Threads
One of the first COM projects I ever consulted on involved a distributed application comprising about 100,000 lines of code, written by a large software company on the West Coast. The application created dozens of COM objects on various machines and called into those objects from background threads launched by client processes. The development team was stymied by calls that would disappear into never-never land or simply fail for no obvious reason. One of the most egregious symptoms that they demonstrated for me was that following a call that failed to return, starting other COM-enabled applications (including Microsoft Paint, of all things) on the same machine would frequently induce those applications to lock up too.
An examination of their code revealed that they had broken one of the fundamental laws of COM concurrency, which says that if one thread wants to share an interface pointer with another thread, it should first marshal the interface pointer. Marshaling an interface pointer enables COM to create a new proxy if necessary (and a new channel object, pairing the proxy with a stub) to permit callouts from another apartment. Passing a raw interface pointer (a 32-bit address in memory) to another thread without marshaling it bypasses COM's concurrency mechanism, and can produce all sorts of undesirable behavior if the sending and receiving threads reside in different apartments. (In Windows® 2000, because two objects can share an apartment but reside in different contexts, it can even get you in trouble if the threads are in the same apartment.) A typical symptom involves calls that fail and return RPC_E_WRONG_THREAD_ERROR.
Windows NT® 4.0 and higher make it easy to marshal interface pointers between threads with a pair of API functions named CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream. Let's say one thread in your application (thread A) has created a COM object and received an IFoo interface pointer in return, and that another thread in the same process (thread B) wants to place calls to that object. In preparation for passing the interface pointer to thread B, thread A should marshal the interface pointer like this:

CoMarshalInterThreadInterfaceInStream (IID_IFoo, pFoo, &pStream);

Once CoMarshalInterThreadInterfaceInStream has returned, thread B can safely unmarshal the interface pointer:

IFoo* pFoo;
CoGetInterfaceAndReleaseStream (pStream, IID_IFoo, (void**) &pFoo);"

 
 
>>so I am repeating his question with more points to hopefully attract an answer

Rule of thub: Provide feedback. Don't abandon questions.
0
 

Author Comment

by:stringsandbeyond
Comment Utility
jkr,

Wow!  Thanks for all the great info.  Just one more question before I close this out.  As David mentioned we have developed a class that attempts to make the process of using the browser really easy.  As of yet the class does not have any knowledge about the thread that creates it and the one that is operating it.

I am guessing that the best way to handle it is to create the browser instance in the first thread that needs it and go ahead and call CoMarshalInterThreadInterfaceInStream() in the Open() routine.  Then make an Initialize() function that calls CoIntializeEx() and CoGetInterfaceAndReleaseStream() to get control of the WebBrowser2.  Then make an UnInitialize() function that calls CoUnitialize() and CoMarshalInterThreadInterfaceInStream() again to pass the WebBrowser2 back into the next thread that needs it.  Does this approach seem reasonable?  Will the method crash if the thread that calls CoMarshalInterThreadInterfaceInStream() exits before the interface is passed into the next thread that uses it?

Thanks so much for your help.
0
 
LVL 86

Expert Comment

by:jkr
Comment Utility
>>Does this approach seem reasonable?

Hum, there is no need to multiply call 'CoInitializeEx()' - just do that once when your application starts up or creates a thread (then you need to call it agin in the new thread). Also, there is no need to pass the WebBrowser2 'back' again, once it is marshalled across the thread boundaries, both threads can use it. Just be sure to release the interface in both threads, so the proxy (if any) can be released by the marshaling layer.

>>Will the method crash if the thread that calls CoMarshalInterThreadInterfaceInStream()
>>exits before the interface is passed into the next thread that uses it?

I'd create the interface in the applications main thread and pass it to the others when they 'need' it.

BTW, if you want to avoid all the marshalling hassle, take a look at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/htm/cmi_d2l_5omd.asp ("IGlobalInterfaceTable"), which provides aninterface is an efficient way for a process to store an interface pointer in a memory location that can be accessed from multiple apartments within the process, such as processwide variables and agile (free-threaded marshaled) objects containing interface pointers to other objects (last half of the sentence shamelessly stolen from that page :o)

0
 
LVL 86

Expert Comment

by:jkr
Comment Utility
BTW, on the last issue, see also

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/htm/aptnthrd_4ew5.asp ("Creating the Global Interface Table")

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/htm/aptnthrd_6uat.asp ("When to Use the Global Interface Table")

http://www.microsoft.com/msj/0997/activex0997.aspx ("ActiveX Q&A, MSJ September, 1997 - Apartment Neutrality: The Free Threaded Marshaler and the Global Interface Table")
0
 

Author Comment

by:stringsandbeyond
Comment Utility
jkr,

You're the best!  Thanks again.
0

Featured Post

Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

Join & Write a Comment

What is C++ STL?: STL stands for Standard Template Library and is a part of standard C++ libraries. It contains many useful data structures (containers) and algorithms, which can spare you a lot of the time. Today we will look at the STL Vector. …
This article will show you some of the more useful Standard Template Library (STL) algorithms through the use of working examples.  You will learn about how these algorithms fit into the STL architecture, how they work with STL containers, and why t…
The goal of the tutorial is to teach the user how to use functions in C++. The video will cover how to define functions, how to call functions and how to create functions prototypes. Microsoft Visual C++ 2010 Express will be used as a text editor an…
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now