views:

343

answers:

2

hi there,

I have a .NET application containing a WinForm. This WinForm contains an unmanaged ActiveX control and a few other controls. This application works fine in standalone mode. Now we want to publish some kind of interface so this Winform can be used from unmanaged C++/MFC applications using Interop functionality. The client applications will Show this form in a modeless manner. When the user inputs the details the same are passed to the client using COM event source/sink (or connection point) method.

I have followed various articles and one of the method that works for us is http://codebetter.com/blogs/peter.van.ooijen/archive/2005/06/03/64041.aspx

But if I start more than one thread from the same client application, the second instance of form throws access violation exceptions because the ActiveX control on the form expects the thread to always be a UI thread (based on my observation).

Could you tell me what is the right way to expose a WinForm functionality to unmanaged client applications via Interop?

Many thanks

+1  A: 

The article you linked gives you all the info you need to properly host a Form in an unmanaged app. You should look for the source of the AV elsewhere. Get that started by using the debugger. You can force it to stop on the exception with the Debug + Exceptions dialog, Thrown checkbox.

The STA requirement is almost always sufficient to keep an ActiveX control operating properly in a multi-threaded application. It provides the guarantee that all method calls are made on the exact same thread in which the coclass was created. In other words, any instance variables inside the coclass implementation are guaranteed to be used in a thread-safe manner.

One guarantee you don't have here though is whether the control properly handles global state. Instance variables are safe, global variables are not when you use the control in different threads. A possible workaround for that is to use only one thread instead of one STA thread for each form instance. That's a bit tricky, you'll need an invisible helper form that manages the thread. Keeping it alive and creating new form instances through its Invoke() method.

Before you go there, find out what's crashing first.

Hans Passant
Thank you. I believe the activex control explicitly requires a ui thread. If I instantiate two forms from the main ui thread it works ok. I am now looking at http://msdn.microsoft.com/en-us/library/ms229591(VS.80).aspxWith the above articles, I don't see how to close the form it is shown using Show and not ShowDialog. So from my interop class if I call form.Close then Dispose method is called twice - once by Close which calls Dispose which inturn calls MyBase.Dispose which calls ac_ThreadExit (see article) and in this routine a call to Form.Dispose is made explicitly. Any help with this?
byte
Okay, clearly the control is having trouble with global state. The article you linked solves a different problem. One you don't have, you are already pumping your own message loop (Application.Run). You can't call the form's Close() method directly from your interop class, it must be made on the correct thread. That's why I suggested using the invisible helper form so you can use its Invoke() method.
Hans Passant
Thanks. I agree and I have observed the same behavior when calling Close - the threads are different. I'll try implementing an invisible helper form to see if this solves the issues.
byte
Basically, I am maintianing old code base and in my spare time I am trying to make the design better. At present, the way it's designed is not elegant (I have removed a lot of inter dependencies between classes and introduced interfaces and events/delegates). I had put work arounds to make the app work in the past whenever we upgrade to a new version of activex control. At a high level I have an unmanaged COM EXE server calling a .NET application (winform) which in turn is hosting an unmanaged activex control. The application works fine in standalone mode, the problems are when we Interop.
byte
+1  A: 

You need to set your C++/MFC host to use apartment threading model for the C# forms COM object. This will serialize all the calls on the proxy through the original thread that instantiated the COM object. You must also properly marshal the original interface between your threads in the C++/MFC code, using CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream

Remus Rusanu
Thank you. I have done that but I think the problem is with the activeX control.
byte