//
you're reading...
COM Apartment Issues, Programming Issues/Tips

Why an ActiveX Control on a Managed Window Form Must Be STA-based.

1. Introduction.

1.1 A Managed Windows Form application may freely incorporate an ActiveX control on its window.

1.2 However, an important requirement is that the thread which creates the window (that contains the ActiveX control) must be an STA thread.

1.3 Otherwise, a System.Threading.ThreadStateException will be thrown with the following being a typical accompanying error message :

An unhandled exception of type ‘System.Threading.ThreadStateException’ occurred in System.Windows.Forms.dll

Additional information: ActiveX control ‘xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx’ cannot be instantiated because the current thread is not in a single-threaded apartment.

1.4 This article examines the reasons why the ActiveX thread must be an STA thread.

1.5 In section 2, we shall develop a very simple ActiveX control using ATL. Then in section 3, we will create a simple Managed Windows Form client application which imports the ActiveX control. We will use this client app to investigate the need for the ActiveX control to be STA-based.

2. Sample ActiveX Control.

2.1 The following is an IDL listing which contains the definition of coclass TestActiveXCtrl which is the ActiveX control that we will use throughout this article :

// TestActiveX.idl : IDL source for TestActiveX
//

// This file will be processed by the MIDL tool to
// produce the type library (TestActiveX.tlb) and marshalling code.

#include "olectl.h"
import "oaidl.idl";
import "ocidl.idl";

[
	object,
	uuid(815851D3-5592-433B-A1A4-0BFF0AB760F9),
	dual,
	nonextensible,
	helpstring("ITestActiveXCtrl Interface"),
	pointer_default(unique)
]
interface ITestActiveXCtrl : IDispatch{
};

[
	uuid(B49C6661-4B01-40B5-84B5-62B15B9E5361),
	version(1.0),
	helpstring("TestActiveX 1.0 Type Library")
]
library TestActiveXLib
{
	importlib("stdole2.tlb");
	[
		uuid(E1A08A31-7E38-472D-A547-9B80D408C84A),
		control,
		helpstring("TestActiveXCtrl Class")
	]
	coclass TestActiveXCtrl
	{
		[default] interface ITestActiveXCtrl;
	};
};

The implemented interface for the TestActiveXCtrl ActiveX, ITestActiveXCtrl, does not even contain any methods. We shall use TestActiveXCtrl only for creation purposes.

2.2 The following is a full listing of the ATL class CTestActiveXCtrl which is the implementation for the TestActiveXCtrl coclass :

// TestActiveXCtrl.h : Declaration of the CTestActiveXCtrl
#pragma once
#include "resource.h"       // main symbols
#include <atlctl.h>
#include "TestActiveX_i.h"

#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, \
such as the Windows Mobile platforms that do not include full DCOM support. \
Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating \
single-thread COM object's and allow use of it's single-threaded COM object implementations. \
The threading model in your rgs file was set to 'Free' as that is the only threading model \
supported in non DCOM Windows CE platforms."
#endif

// CTestActiveXCtrl
class ATL_NO_VTABLE CTestActiveXCtrl :
	public CComObjectRootEx<CComSingleThreadModel>,
	public IDispatchImpl<ITestActiveXCtrl, &IID_ITestActiveXCtrl, &ampLIBID_TestActiveXLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
	public IPersistStreamInitImpl<CTestActiveXCtrl>,
	public IOleControlImpl<CTestActiveXCtrl>,
	public IOleObjectImpl<CTestActiveXCtrl>,
	public IOleInPlaceActiveObjectImpl<CTestActiveXCtrl>,
	public IViewObjectExImpl<CTestActiveXCtrl>,
	public IOleInPlaceObjectWindowlessImpl<CTestActiveXCtrl>,
	public ISupportErrorInfo,
	public IPersistStorageImpl<CTestActiveXCtrl>,
	public ISpecifyPropertyPagesImpl<CTestActiveXCtrl>,
	public IQuickActivateImpl<CTestActiveXCtrl>,
#ifndef _WIN32_WCE
	public IDataObjectImpl<CTestActiveXCtrl>,
#endif
	public IProvideClassInfo2Impl<&CLSID_TestActiveXCtrl, NULL, &LIBID_TestActiveXLib>,
#ifdef _WIN32_WCE // IObjectSafety is required on Windows CE for the control to be loaded correctly
	public IObjectSafetyImpl<CTestActiveXCtrl, INTERFACESAFE_FOR_UNTRUSTED_CALLER>,
#endif
	public CComCoClass<CTestActiveXCtrl, &CLSID_TestActiveXCtrl>,
	public CComControl<CTestActiveXCtrl>
{
public:

	CTestActiveXCtrl()
	{
	}

	~CTestActiveXCtrl()
	{
	}

DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE |
	OLEMISC_CANTLINKINSIDE |
	OLEMISC_INSIDEOUT |
	OLEMISC_ACTIVATEWHENVISIBLE |
	OLEMISC_SETCLIENTSITEFIRST
)

DECLARE_REGISTRY_RESOURCEID(IDR_TESTACTIVEXCTRL)

BEGIN_COM_MAP(CTestActiveXCtrl)
	COM_INTERFACE_ENTRY(ITestActiveXCtrl)
	COM_INTERFACE_ENTRY(IDispatch)
	COM_INTERFACE_ENTRY(IViewObjectEx)
	COM_INTERFACE_ENTRY(IViewObject2)
	COM_INTERFACE_ENTRY(IViewObject)
	COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)
	COM_INTERFACE_ENTRY(IOleInPlaceObject)
	COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)
	COM_INTERFACE_ENTRY(IOleInPlaceActiveObject)
	COM_INTERFACE_ENTRY(IOleControl)
	COM_INTERFACE_ENTRY(IOleObject)
	COM_INTERFACE_ENTRY(IPersistStreamInit)
	COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
	COM_INTERFACE_ENTRY(ISupportErrorInfo)
	COM_INTERFACE_ENTRY(ISpecifyPropertyPages)
	COM_INTERFACE_ENTRY(IQuickActivate)
	COM_INTERFACE_ENTRY(IPersistStorage)
#ifndef _WIN32_WCE
	COM_INTERFACE_ENTRY(IDataObject)
#endif
	COM_INTERFACE_ENTRY(IProvideClassInfo)
	COM_INTERFACE_ENTRY(IProvideClassInfo2)
#ifdef _WIN32_WCE // IObjectSafety is required on Windows CE for the control to be loaded correctly
	COM_INTERFACE_ENTRY_IID(IID_IObjectSafety, IObjectSafety)
#endif
END_COM_MAP()

BEGIN_PROP_MAP(CTestActiveXCtrl)
	PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
	PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
	// Example entries
	// PROP_ENTRY_TYPE("Property Name", dispid, clsid, vtType)
	// PROP_PAGE(CLSID_StockColorPage)
END_PROP_MAP()

BEGIN_MSG_MAP(CTestActiveXCtrl)
	CHAIN_MSG_MAP(CComControl<CTestActiveXCtrl>)
	DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
// Handler prototypes:
//  LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
//  LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
//  LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);

// ISupportsErrorInfo
	STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid)
	{
		static const IID* arr[] =
		{
			&IID_ITestActiveXCtrl,
		};

		for (int i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
		{
			if (InlineIsEqualGUID(*arr[i], riid))
				return S_OK;
		}
		return S_FALSE;
	}

// IViewObjectEx
	DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE)

// ITestActiveXCtrl
public:
		HRESULT OnDraw(ATL_DRAWINFO& di)
		{
		RECT& rc = *(RECT*)di.prcBounds;
		// Set Clip region to the rectangle specified by di.prcBounds
		HRGN hRgnOld = NULL;
		if (GetClipRgn(di.hdcDraw, hRgnOld) != 1)
			hRgnOld = NULL;
		bool bSelectOldRgn = false;

		HRGN hRgnNew = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);

		if (hRgnNew != NULL)
		{
			bSelectOldRgn = (SelectClipRgn(di.hdcDraw, hRgnNew) != ERROR);
		}

		Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom);
		SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE);
		LPCTSTR pszText = _T("ATL 8.0 : TestActiveXCtrl");
#ifndef _WIN32_WCE
		TextOut(di.hdcDraw,
			(rc.left + rc.right) / 2,
			(rc.top + rc.bottom) / 2,
			pszText,
			lstrlen(pszText));
#else
		ExtTextOut(di.hdcDraw,
			(rc.left + rc.right) / 2,
			(rc.top + rc.bottom) / 2,
			ETO_OPAQUE,
			NULL,
			pszText,
			ATL::lstrlen(pszText),
			NULL);
#endif

		if (bSelectOldRgn)
			SelectClipRgn(di.hdcDraw, hRgnOld);

		return S_OK;
	}

	DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct()
	{
		return S_OK;
	}

	void FinalRelease()
	{
	}
};

OBJECT_ENTRY_AUTO(__uuidof(TestActiveXCtrl), CTestActiveXCtrl)

The ActiveX that we present here will be built into TestActiveX.dll.

3. Sample Windows Form Client Application.

3.1 In this section, we build a sample managed Windows Form client application. The name of the project that we will build is CSWinFormApp01.

3.2 The following screen shot displays the design of the main Window Form of the application :

As can be seen, the TestActiveXCtrl ActiveX is placed on the window of the main Window Form.

3.3 The following is a listing of the source codes of Form1 :

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace CSWinFormApp01
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
        }
    }
}

3.4 Listed below is Program.cs which is a source file automatically generated by the Visual Studio wizard and included as part of the project :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace CSWinFormApp01
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Program is a static class which contains the static Main() method which serves as the starting point of the application. It is in Main() that Form1 is instantiated and subsequently displayed.

3.5 The line of interest to us is the STAThreadAttribute declaration for the Main() method. Now, temporarily change this attribute to the MTAThreadAttribute :

        [MTAThread]
        static void Main()
        {
           ...
        }

3.6 The project will recompile successfully. However, when this program is run, the exception described in point 1.3 will be displayed :

3.7 We know that Form1 will run on the same thread as the Main() method. If Main() had been declared to run on an STA thread (by being decorated with the STAThreadAttribute), the exception will not occur.

3.8 In the next section we will begin to investigate the reason for the STA thread requirement.

4. The AxHost Object.

4.1 When you first position an ActiveX control on a Windows Form, 2 assemblies will be automatically generated and incorporated into your project :

  • An interop assembly.
  • An AxHost assembly.

4.2 The interop assembly contains managed definitions of COM types. The interop assembly is exactly the type of assembly that would have been produced by TLBIMP.EXE. After all, the ActiveX control is a COM DLL.

4.3 The AxHost assembly contains the definition of a System.Windows.Forms.AxHost derived class an instance of which is required in order to host the ActiveX control (an unmanaged control) on a Windows Form as if the ActiveX was actually a Windows Forms control (i.e. a managed control).

4.4 Of course, if there are multiple ActiveX controls on the ActiveX DLL or OCX, then there will be multiple System.Windows.Forms.AxHost derived classes defined in the AxHost assembly, one for each ActiveX control.

4.5 When the TestActiveXCtrl ActiveX control is first placed on Form1, AxInterop.TestActiveXLib.dll (the AxHost assembly) and Interop.TestActiveXLib.dll (the interop assembly) will be generated by the AxImporter under the command of Visual Studio. These assemblies will be automatically included by as references of the project :

4.6 Upon close examination of the wizard generated code for Form1, we see that it contains a private member axTestActiveXCtrl1 of type AxTestActiveXLib.AxTestActiveXCtrl which is contained in the AxInterop.TestActiveXLib assembly (the AxHost assembly).

4.7 AxTestActiveXLib.AxTestActiveXCtrl is the AxHost-derived control class that hosts the unmanaged TestActiveX control on a managed Windows Form :

4.8 Now that we have familiarized ourselves with the AxHost control for the TestActiveX control, we will go deeper into understanding its need to run in an STA-thread.

5. The Location where the System.Threading.ThreadStateException is Thrown.

5.1 The exception is thrown in the Form1.InitializeComponent() function in the Form1 constructor :

public Form1()
{
    InitializeComponent();
}

More specifically, it happens when a new instance of the AxTestActiveXLib.AxTestActiveXCtrl class is constructed :

private void InitializeComponent()
{
    ...
    this.axTestActiveXCtrl1 = new AxTestActiveXLib.AxTestActiveXCtrl();
    ...
}

5.2 Hence it is the AxHost class that is the one complaining that the current thread is not an STA thread. Control has not even reached the TestActiveX control itself.

5.3 When a new AxTestActiveXLib.AxTestActiveXCtrl class is constructed as in the line highlighted in InitializeComponent(), the constructor for AxTestActiveXLib.AxTestActiveXCtrl will be called.

5.4 We can actually see what goes on inside this constructor by observing the source codes of the following items :

  • AxImporter generated AxTestActiveXLib.AxTestActiveXCtrl class.
  • The AxHost class itself.

5.5 We can obtain the source codes of the AxTestActiveXLib.AxTestActiveXCtrl class by manually invoking AxImp.exe and using the /source command line option. This will produce a .cs file which contains the source codes of the output AxHost assembly. For the TestActiveX control, AxTestActiveXLib.cs will be produced. We will examine this in section 6 below.

5.6 The source codes of the AxHost class can be obtained from the following web resource :

AxHost.cs source code in C# .NET.

We will look into this in greater detail in section 6.

6. Observing the Source Codes of the AxTestActiveXLib.AxTestActiveXCtrl and the AxHost classes.

6.1 The point of interest in this class is the construtor for the AxTestActiveXLib.AxTestActiveXCtrl class :

        public AxTestActiveXCtrl() :
                base("e1a08a31-7e38-472d-a547-9b80d408c84a") {
        }

Here, we see that it simply calls the constructor of the base System.Windows.Forms.AxHost class.

6.2 Let’s examine the constructor for System.Windows.Forms.AxHost using the URL provided in point 5.6 :

        ///
        ///
        ///     Creates a new instance of a control which wraps an activeX control given by the
        ///     clsid parameter and flags of 0.
        ///
        protected AxHost(string clsid) : this(clsid, 0) {
        }

        ///
        ///
        ///    Creates a new instance of a control which wraps an activeX control given by the
        ///       clsid and flags parameters.
        ///
        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        protected AxHost(string clsid, int flags) : base() {
            if (Application.OleRequired() != ApartmentState.STA) {
                throw new ThreadStateException(SR.GetString(SR.AXMTAThread, clsid));
            } 

            this.oleSite = new OleInterfaces(this);
            this.selectionChangeHandler = new EventHandler(this.OnNewSelection);
            this.clsid = new Guid(clsid);
            this.flags = flags;

            this.axState[assignUniqueID] = !this.GetType().GUID.Equals(comctlImageCombo_Clsid);
            this.axState[needLicenseKey] = true;
            this.axState[rejectSelection] = true; 

            isMaskEdit = this.clsid.Equals(AxHost.maskEdit_Clsid);
            this.onContainerVisibleChanged = new EventHandler(this.OnContainerVisibleChanged);
        }

6.3 The AxHost constructor that is invoked by the AxTestActiveXCtrl constructor is the one which takes a single CLSID string parameter. This constructor will internally call the version of the AxHost constructor which takes 2 parameters : the CLSID string and a flags integer.

6.4 Notice right at the beginning of this constructor code that a call will be made to the Application.OleRequired() method.

6.5 This method will first check to see if OLE has been initialized on the current thread. If not it will initialize the current thread for OLE using the OleInitialize() API.

6.6 If the current thread has already been initialized as an MTA thread, Application.OleRequired() will not call OleInitialize() and it will return ApartmentState.MTA.

6.7 Now if Application.OleRequired() returns any value other than ApartmentState.STA, the AxHost constructor will throw the ThreadStateException together with the “…cannot be instantiated because the current thread is not in a single-threaded apartment.” message :

            if (Application.OleRequired() != ApartmentState.STA) {
                throw new ThreadStateException(SR.GetString(SR.AXMTAThread, clsid));
            }

7. Why must AxHost be created in an STA Thread ?

7.1 Of course the conclusion of section 6 begs the question : why does the AxHost class enforce the STA thread requirement ?

7.2 The comments in this section are my own opinions on this subject. Before we go deeper, please note that as per COM protocol, if an STA object were to be instantiated inside an incompatible apartment (e.g. an MTA), the COM sub-system will internally arrange for the object to run in an STA thread (either a new STA thread or an existing “Legacy” STA thread).

7.3 However, note that an ActiveX Control is a considerably complex COM object. It must implement and expose many OLE interfaces including : IOleControl, IOleObject, IOleWindow, IOleInPlaceObject, IOleInPlaceActiveObject, IOleInPlaceObjectWindowless, etc just to name a few.

7.4 Any ActiveX Control hosting window (including an AxHost-derived object) must implement IOleControlSite, IOleInPlaceSite, IOleClientSite, etc.

7.5 These interfaces are part of the OLE Control Protocol (dating back way before .NET) which centers around the management of an ActiveX control’s window (the in place active object) and its relation with its hosting parent window (the site object).

7.6  Note the following :

  • For smooth and proper fulfillment of the protocol, it would be make good sense to ensure that the ActiveX control is physically located on top of its hosting window so that a parent-child window relationship can emerge and that they share a common message pump.
  • Being essentially a window (that uses a message pump) as well as a COM object (which implements various OLE interfaces listed in point 7.4 above), it would make little to no sesne to implement the host as an MTA object.

7.7 In a strained sort of way, a host probably can be implemented as an MTA object and live in an apartment separate from its ActiveX control. But this flies in the face of simplicity and good design.

7.8 Conformance to the points in 7.6 are especially important if a host is to support “windowless” ActiveX controls. A “windowless” ActiveX control is one which uses its host’s own window area for drawing.

7.9 Hence in my opinion, it is probably a necessity for AxHost to insist that it be created in an STA thread.

8. In Conclusion.

8.1 I hope that this article will serve as a good insight into the workings of the AxHost control.

8.2 Another interesting scenario that can also cause problems related to STA is with managed controls that accept drag and drop.

8.3 Managed controls which accept drag and drop must be placed on a form that is created in an STA. Otherwise the drag and drop initialization will fail.

8.4 I shall be doing a write-up on this in the future.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

One thought on “Why an ActiveX Control on a Managed Window Form Must Be STA-based.

  1. Hi Lim,
    Thanks for detailed explanation.
    I am working in project To create .net where I have to call AxActivexContrl in MTA mode. Like below.
    I am getting exception in calling AxActivex Control.
    Project structure is .
    API{
    Function GetData()
    Dim tt As New Thread(New Thread(New ThreadStart(Function() GetFunction())))

    Function GetFunction()
    Dim frm As Form1
    frm.Show()
    frm.close()
    End Function

    End Function

    }

    Form1 is used to load AxActivexContrl programmatically in Form1 load event.

    Form1_Load()
    {
    Me.Controls.add(objActiveXCntrl)
    }

    I am calling this .dll function from client app. When I am running from .exe , it is working fine but When I am debugging I am getting exception.
    When setting thread apartment state to sta, exception will not come but due to project requirement I have to call it MTA mode.
    Is there any way to suppress this exception?

    Thanks in Advance,
    Sumit

    Posted by sumit | November 12, 2012, 5:56 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: