Solved

Using WebBrowser2 Interface in Multithreaded application.

Posted on 2003-10-31
5
453 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 3
  • 2
5 Comments
 
LVL 86

Accepted Solution

by:
jkr earned 500 total points
ID: 9657616
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
ID: 9659852
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
ID: 9660162
>>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
ID: 9660182
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
ID: 9660220
jkr,

You're the best!  Thanks again.
0

Featured Post

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
IdTelnet1DataAvailable handler freezes the Application 4 93
How to convert MFC::CString to UTF8 wchar_t* 10 384
max float value 3 59
C++ help/ Toy problem 19 53
Errors will happen. It is a fact of life for the programmer. How and when errors are detected have a great impact on quality and cost of a product. It is better to detect errors at compile time, when possible and practical. Errors that make their wa…
Often, when implementing a feature, you won't know how certain events should be handled at the point where they occur and you'd rather defer to the user of your function or class. For example, a XML parser will extract a tag from the source code, wh…
The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relat…
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.

733 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