views:

215

answers:

3

I'm having trouble with my COM component written in .NET throwing warnings that look like:

Context 0x15eec0 is disconnected. No proxy will be used to service the request on the COM component. This may cause corruption or data loss. To avoid this problem, please ensure that all contexts/apartments stay alive until the application is completely done with the RuntimeCallableWrappers that represent COM components that live inside them.

It looks like this is caused by my GUI thread calling functions in the COM thread without necessary syncronization. For reference I'm using the guidelines set in http://msdn.microsoft.com/en-us/library/ms229609%28VS.80%29.aspx for creating my GUI thread in the COM component.

My code looks something like:

class COMClass {
  // this is called before SomeMethod
  public void Init() {
    ComObject comObject = new ComObject(); // this is imported from a TLB

    // I create my GUI thread and start it as in the MSDN sample
    Thread newThread = new Thread(new ThreadStart(delegate() {
      Application.Run(new GUIForm(comObject));
    }));
  }

  public void SomeMethod(){
    comObject.DoSomething();               // this is where the error occurs
  }
}

class GUIForm : Form {
  ComObject com;
  public GUIForm(ComObject com) {comObject = com;}

  public void SomeButtonHandler(object sender, EventArgs e) {
    comObject.SomeMethod();  // call on the GUI thread but the com object is bound to the COM thread...
  }
}

Is there an established method for dealing with this? Calls to the GUI are no problem (Invoke/BeginInvoke) but calling the other way seems to be more difficult...

edit: It is also not an option to modify the COM object in any way.

A: 

Since the COM object was created on the other thread, all calls to the COM object should be made from that thread. After launching the GUI thread, you'll need to have some kind of queueing mechanism set up to wait for calls to execute methods (probably a queue of delegates). Your GUI code can push a delegate into the queue and it will be executed (on the original thread) when the original thread processes the queue. See: http://www.yoda.arachsys.com/csharp/threads/deadlocks.shtml (producer/consumer example about mid-way down the page).

Patrick Steele
This isn't possible with the design of the COM system for this application. There is no exposed loop I can insert code to consume events from the queue; if I add a loop to any of functions called on my COM object it will never return to the calling application.
Ron Warholic
Frankly I was hoping there was some sort of ThreadBoundInvoker that I could post events to that would get executing on the bound thread.
Ron Warholic
Is the COM object only used by the GUIForm? If so, you could just instantiate it in the form and everything would be on the GUI thread.
Patrick Steele
The comObject is used by the COM thread as well although the calls could be Invoked on to the GUIForm. The problem is we have several thousand lines that would need refactoring if we tried to move it and we're too close to the deadline for that much work.
Ron Warholic
+1  A: 

It isn't very clear from your snippet how the all-important Init() method is called and how the thread got started. Clearly, the thread on which the COM object is created is not the same thread as the one where the SomeMethod() call is made. Further assuming that the COM server is apartment threaded, COM needs to marshal the SomeMethod() call to the thread that created the object. The one that called Init(). If that thread is no longer running, hilarity ensues.

There's one glaring problem, you forgot to call Thread.SetApartmentState().

Given that COM already marshals inter-thread calls, you are probably not gaining anything by starting your own thread. You can't magically make a COM server multi-threaded if it refuses to support it.

Hans Passant
The Init() method is called by the COM server app (of which we didn't write) which creates our COMClass on an STA thread. In the actual code I do have the SetApartmentState(ApartmentState.STA) call so both the GUI and the COMClass should be in separate STAs. Its my understanding that calls should be marshalled between them but the MDA error seems to indicate that the proxy object is being deleted for some reason.
Ron Warholic
Also we have little choice to create a thread; in order to run a WinForms GUI with our COMClass we need to have the WinForms message pump initialized which requires a new thread.
Ron Warholic
Well, Init() is called by a thread you don't control. It seems logical that this thread is no longer around anymore, judging from the message. It isn't clear exactly *why* you have to create the COM object on that thread.
Hans Passant
A: 

I found the problem, it wasn't the cross-thread operation persay. In my GUIForm I created a subwindow and used SetParent() to parent it to the COM server's app window. This seems to have caused the issues with the COM proxy being disconnected (although a more experience COM expert might have to enlighten me as to why it behaved like this).

Instead of parenting my control to the window I'm going to fully disconnect it and just hook WM_WINDOWPOSCHANGING to move my control with the main App window.

Ron Warholic