Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / Windows Forms
Tip/Trick

ActiveX in .NET Applications

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
9 Sep 2015CPOL6 min read 24.8K   5   12
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)


Written By
Software Developer
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHow to create ActiveX pool Pin
Member 810403812-Oct-16 18:26
Member 810403812-Oct-16 18:26 
AnswerRe: How to create ActiveX pool Pin
Vladyslav Chernysh12-Oct-16 21:44
Vladyslav Chernysh12-Oct-16 21:44 
GeneralRe: How to create ActiveX pool Pin
Member 810403812-Oct-16 22:26
Member 810403812-Oct-16 22:26 
GeneralMy vote of 5 Pin
David A. Gray11-Sep-15 7:53
David A. Gray11-Sep-15 7:53 
QuestionActiveX Should be Avoided Pin
#realJSOP9-Sep-15 3:43
mve#realJSOP9-Sep-15 3:43 
AnswerRe: ActiveX Should be Avoided Pin
Vladyslav Chernysh9-Sep-15 7:23
Vladyslav Chernysh9-Sep-15 7:23 
GeneralRe: ActiveX Should be Avoided Pin
#realJSOP10-Sep-15 23:56
mve#realJSOP10-Sep-15 23:56 
AnswerRe: ActiveX Should be Avoided Pin
Tom Spink10-Sep-15 2:18
Tom Spink10-Sep-15 2:18 
GeneralRe: ActiveX Should be Avoided Pin
#realJSOP10-Sep-15 23:54
mve#realJSOP10-Sep-15 23:54 
GeneralRe: ActiveX Should be Avoided Pin
David A. Gray11-Sep-15 7:50
David A. Gray11-Sep-15 7:50 
GeneralRe: ActiveX Should be Avoided Pin
#realJSOP16-Sep-15 8:26
mve#realJSOP16-Sep-15 8:26 
I was careful to say almost always...

However, I still think that if you're going to refactor an application that uses an Active-X control, it would be worth considering replacing the active-x control with a native assembly.
".45 ACP - because shooting twice is just silly" - JSOP, 2010
-
You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010
-
When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013

GeneralRe: ActiveX Should be Avoided Pin
David A. Gray16-Sep-15 8:52
David A. Gray16-Sep-15 8:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.