Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

ActiveX in .NET Applications

5.00/5 (3 votes)
9 Sep 2015CPOL6 min read 27K  
The goal of this tip is to share knowledge and experience on ActiveX usage in .NET applications (back-end & front-end).

Introduction

ActiveX technology is very often used by 3rd party companies to distribute their API. Usually, it is represented as a Windows Forms control. Visual Studio makes it very easy to integrate such control into your Windows Forms application. But what if you want to borrow functionality from ActiveX control and use it in Console application or in a Class Library?

In this tip, I will show how to use ActiveX in back-end applications. Also, I will describe the most common issues that may happen with ActiveX and solutions for them.

Register/Unregister ActiveX

First of all, ActiveX component should be registered on the target computer. Otherwise, you will get the following exception:

System.Runtime.InteropServices.COMException (0x80040154): Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))

Usually, ActiveX component represented as one ".ocx" file and several ".dll" files. For installation - run the following command in CMD:

BAT
regsvr32 "path_to_activex\activex_name.ocx"

For uninstall - run the following command in CMD:

BAT
regsvr32 /u "path_to_activex\activex_name.ocx"

If you are not an Administrator in the system, you may receive the following errors:

The module "C:\[file being registered]" was loaded but the call to DllRegisterServer failed with error code 0x80040200.
The module "C:\[file being registered]" was loaded but the call to DllUnregisterServer failed with error code 0x80040200.?

This message may appear when registering or unregistering a file on Windows 7 when User Account Control (UAC) is enabled on the computer. User Account Control may limit or prevent certain tasks that the Windows User Account does not have permissions to.

If the security settings do not allow registering or unregistering a file from the Windows Run box, it is necessary to register or unregister the file from an elevated Windows Command Prompt. Please follow the steps below to register or unregister the necessary file from an Elevated Command Prompt:

  1. Close all programs.
  2. Click "Start > All Programs > Accessories".
  3. Right-click on "Command Prompt" and select "Run as administrator".
  4. Execute the necessary regsvr32 command to register or unregister the corresponding DLL or OCX file.

Using ActiveX in Class Library

Basically, ActiveX was designed to be used in GUI applications. That is why most of them require STA Thread, application message loop and many other stuff that is present in Windows Forms projects but absent in Class Library projects. Next, I will show how to avoid all this headache by creating a wrapper around ActiveX object.

First, we need to create Class Library project in Visual Studio. If ActiveX compiled as 32 bit application, make sure your project also compiles with x86 platform setting.

Next step is to add Windows Form to your project. Let's call it "SdkWrapperForm". The only purpose of this form is to host our ActiveX control. So, you need to add it on the form: Toolbox->right-click->Choose Items...->COM Components. Select target ActiveX and press OK. New control will appear in Toolbox\General. Drag&Drop it on the form. Automatically, Visual Studio will add two DLLs to your project: AxInterop.activex_name, Interop.activex_name. These two DLLs should be distributed along with your project output DLL.

Now go to 'SdkWrapperForm.cs' and add the following code:

C#
public partial class SdkWrapperForm : Form
    {
        public SdkWrapperForm()
        {
            InitializeComponent();
        }

        public activex_type SDK
        {
            get
            {
                return activex_obj;
            }
        }
    }

This property allows us to access ActiveX object, that we placed on the form, from other classes.

Now, we are ready to write the main part of the wrapper. Add new class 'SdkWrapper' to your project. Paste the following code:

C#
public class SdkWrapper : IDisposable
    {
        #region Private members

        private Thread _uiThread;
        private SdkWrapperForm _form;

        #endregion

        #region Constructor

        public SdkWrapper()
        {
            ManualResetEvent initFinished = new ManualResetEvent(false);

            _uiThread = new Thread(() =>
            {
                _form = new SdkWrapperForm();
                //TODO: implement specific initialization here
                //example:
                //_form.SDK.some_method();
                //_form.SDK.some_event += SDK_some_event;

                initFinished.Set();

                while (true)
                {
                    try
                    {
                        Application.Run();
                    }
                    catch (ThreadAbortException)
                    {
                        //exit loop
                        return;
                    }
                    catch (Exception ex)
                    {
                        //log exception
                    }
                }
            }
            );
            _uiThread.SetApartmentState(ApartmentState.STA);
            _uiThread.IsBackground = true;
            _uiThread.Start();

            initFinished.WaitOne(1000);
        }

        public void Dispose()
        {
            _uiThread.Abort();
            _uiThread = null;
            Application.Exit();
        }

        #endregion
    }

This code creates an instance of our 'SdkWrapperForm' in the thread that is marked with STA attribute and runs application message loop. Note that Dispose should be called before application close.

Now, if we want to use some methods from ActiveX object, we need to create wrappers for them. Here are few examples:

C#
public void AX_connect(string url, string username, string password)
        {
            if (_form.InvokeRequired)
            {
                _form.Invoke(new Action(() => { AX_connect(url, username, password); }));
            }
            else
            {
                _form.SDK.TS_connect(url, username, password);
            }
        }
C#
public string AX_get_version()
       {
           if (_form.InvokeRequired)
           {
               return (string)_form.Invoke(new Func<string>(() => { return AX_get_version(); }));
           }
           else
           {
               return _form.SDK.AX_get_version();
           }
       }
C#
public string AX_get_some_info(short id)
        {
            if (_form.InvokeRequired)
            {
                return (string)_form.Invoke(new Func<string>(() => { return AX_get_some_info(id); }));
            }
            else
            {
                return _form.SDK.AX_get_some_info(id);
            }
        }

As you can see, methods from ActiveX object should be called from the same thread that ActiveX was created.

Basically that's it! Class 'SdkWrapper' may be used in any place of your code.

Points of Interest

There are few issues that I faced while integrating ActiveX component into my application.

First one related to incorrect disposing. I got the following error:

COM object that has been separated from its underlying RCW cannot be used.

at System.RuntimeType.InvokeDispMethod(String name, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32 culture, String[] namedParameters) at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams) at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData)

This issue happens if I call some methods from ActiveX when host form is already disposed.

Another issue that I faced with related to unmanaged resource allocation (I didn't dive into details what exactly happens there). My application crashed every 2 hours (+/- 15 mins) with the following exceptions that I took from Windows Event Log:

Application: my_app.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.AccessViolationException Stack: at System.Windows.Forms.UnsafeNativeMethods.PeekMessage(MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32, Int32) at System.Windows.Forms.Application+ ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.
FPushMessageLoop(IntPtr, Int32, Int32) at System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext) at System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext) at System.Windows.Forms.Application.Run() at my_app.SdkWrapper+<>c__DisplayClassf.<.ctor>b__6() at System.Threading.ThreadHelper.ThreadStart_Context(System.Object) at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) at System.Threading.ThreadHelper.ThreadStart()

And like this:

Application: my_app.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.Reflection.TargetInvocationException Stack: at System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[], System.Signature, Boolean) at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(System.Object, System.Object[], System.Object[]) at System.Reflection.RuntimeMethodInfo.UnsafeInvoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo) at System.Delegate.DynamicInvokeImpl(System.Object[]) at System.Delegate.DynamicInvoke(System.Object[]) at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry) at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(System.Object) at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) at System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry) at System.Windows.Forms.Control.InvokeMarshaledCallbacks() at System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.ScrollableControl.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.ContainerControl.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.Form.WndProc(System.Windows.Forms.Message ByRef) at my_app.MainForm.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef) at System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef) at System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr) at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef) at System.Windows.Forms.Application+ ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.
FPushMessageLoop(IntPtr, Int32, Int32) at System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext) at System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext) at System.Windows.Forms.Application.Run(System.Windows.Forms.Form) at my_app.MainForm.Main(System.String[])

I found that it happens because my application creates and deletes ActiveX objects during work. After some amount of created objects, I got those exceptions. Probably, I didn't delete those objects fully and some unmanaged resources remain in memory. As a workaround, I created ActiveX object pool. The application first looks at an available object in the pool; if pool is empty - create a new object; if pool is not empty - re-use an existing object. Works like a charm for me since my app uses maximum 16 objects simultaneously.

History

  • 9.9.2015 - First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)