Olav Aukan Getting information off the Internet is like taking a drink from a fire hydrant…

31Aug/10

Creating an ActiveX control in .Net using C#

A while back I had a client request that I write an ActiveX control for use on their corporate intranet. I had never done this before, and most of the examples I could find online were either really old, incomplete or based on using C++ and MFC. It's safe to say that my C++ skills are not quite up to the job, so for me it was really a requirement to be able to do this with C#. It took me about an hour or two to write the code for the control, but it took almost three days to successfully package and deploy it as a .cab file... So to save others from wasting their time like I did, I'll document my findings in this post.

Steps

  1. - Create a new Class Library project in Visual Studio
  2. - Create a new class that inherits from UserControl
  3. - Create a new interface that exposes the controls methods and properties to COM interop
  4. - Make the control class implement the new interface
  5. - Mark the control as safe for scripting and initialization
  6. - Create a .msi installer for the control
  7. - Package the control in a .cab file for web deployment
  8. - Initialize and test the control with JavaScript

1. Create a new Class Library project in Visual Studio

I'm using Visual Studio 2008, but other versions should work as well.

  1. After starting Visual Studio click File -> New -> Project and select Class Library under C#.
  2. Call the project 'AxControls' and click OK.

2. Create a new class that inherits from UserControl

  1. Rename 'Class1.cs' to 'HelloWorld.cs', making sure to rename the class name as well.
  2. Add a project reference to System.Windows.Forms.
  3. Make the HelloWorld class inherit UserControl.

3. Create a new interface that exposes the controls methods and properties to COM interop

  1. Right click the project in Visual Studio and click Add -> New Item.
  2. Select 'Interface' from the list of components, name it 'IHelloWorld.cs' and click Add.
  3. Edit the 'IHelloWorld.cs' file so it looks like this:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.InteropServices;
    
    namespace AxControls
    {
        [ComVisible(true)]
        [InterfaceType(ComInterfaceType.InterfaceIsDual)]
        [Guid("41E85D5D-C57A-4386-B722-4031D0B1E1B7")]
        public interface IHelloWorld
        {
            string GetText();
        }
    }
    

We now have a COM visible interface with a single method 'GetText()'.

[ComVisible(true)] makes the interface visible to COM.
[InterfaceType(ComInterfaceType.InterfaceIsDual)] sets the COM interface type to Dual, see InterfaceTypeAttribute Class on MSDN.
[Guid("41E85D5D-C57A-4386-B722-4031D0B1E1B7")] let's us manually assign a GUID to the interface. Use guidgen.exe to generate your own.

4. Make the control class implement the new interface

Make the HelloWorld class implement the IHelloWorld interface and have the GetText() method return a string of your choice. This is what the file might look like:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace AxControls
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [Guid("1FC0D50A-4803-4f97-94FB-2F41717F558D")]
    [ProgId("AxControls.HelloWorld")]
    [ComDefaultInterface(typeof(IHelloWorld))]
    public class HelloWorld : UserControl, IHelloWorld
    {
        #region IHelloWorld Members

        public string GetText()
        {
            return "Hello ActiveX World!";
        }

        #endregion
    }
}

We now have a COM visible control that implements the IHelloWorld interface.

[ComVisible(true)] makes the control visible to COM, see ComVisibleAttribute Class on MSDN.
[ClassInterface(ClassInterfaceType.None)] indicates that no class interface is generated for this class, see ClassInterfaceType Enumeration on MSDN.
[Guid("1FC0D50A-4803-4f97-94FB-2F41717F558D")] let's us manually assign a GUID to the control, see GuidAttribute Class on MSDN. Use guidgen.exe to generate your own.
[ProgId("AxControls.HelloWorld")] is a "user friendly" ID that we'll use later from JavaScript when initiating the control, see ProgIdAttribute Class on MSDN.
[ComDefaultInterface(typeof(IHelloWorld))] sets IHelloWorld as the default interface that will be exposed to COM, see ComDefaultInterfaceAttribute Class on MSDN.

5. Mark the control as safe for scripting and initialization

By default IE will not allow initializing and scripting an ActiveX control unless it is marked as safe. This means that we won't be able to create instances of our ActiveX class with JavaScript by default. We can get around this by modifying the browser security settings, but a more elegant way would be to mark the control as safe. Before you do this to a "real" control, be sure to understand the consequences. I found an ancient (1996) MSDN article that explains this here. We will mark the control as safe by implementing the IObjectSafety interface.

  1. Right click the project in Visual Studio and click Add -> New Item.
  2. Select 'Interface' from the list of components, name it 'IObjectSafety.cs' and click Add.
  3. Edit the 'IObjectSafety.cs' file so it looks like this:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.InteropServices;
    
    namespace AxControls
    {
        [ComImport()]
        [Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        interface IObjectSafety
        {
            [PreserveSig()]
            int GetInterfaceSafetyOptions(ref Guid riid, out int pdwSupportedOptions, out int pdwEnabledOptions);
    
            [PreserveSig()]
            int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions);
        }
    }
    
  4. Make the HelloWorld class implement the IObjectSafety interface. The end result should look something like this:
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    
    namespace AxControls
    {
        [ComVisible(true)]
        [ClassInterface(ClassInterfaceType.None)]
        [Guid("1FC0D50A-4803-4f97-94FB-2F41717F558D")]
        [ProgId("AxControls.HelloWorld")]
        [ComDefaultInterface(typeof(IHelloWorld))]
        public class HelloWorld : UserControl, IHelloWorld, IObjectSafety
        {
            #region IHelloWorld Members
    
            public string GetText()
            {
                return "Hello ActiveX World!";
            }
    
            #endregion
    
            #region IObjectSafety Members
    
            public enum ObjectSafetyOptions
            {
                INTERFACESAFE_FOR_UNTRUSTED_CALLER = 0x00000001,
                INTERFACESAFE_FOR_UNTRUSTED_DATA = 0x00000002,
                INTERFACE_USES_DISPEX = 0x00000004,
                INTERFACE_USES_SECURITY_MANAGER = 0x00000008
            };
    
            public int GetInterfaceSafetyOptions(ref Guid riid, out int pdwSupportedOptions, out int pdwEnabledOptions)
            {
                ObjectSafetyOptions m_options = ObjectSafetyOptions.INTERFACESAFE_FOR_UNTRUSTED_CALLER | ObjectSafetyOptions.INTERFACESAFE_FOR_UNTRUSTED_DATA;
                pdwSupportedOptions = (int) m_options;
                pdwEnabledOptions = (int) m_options;
                return 0;
            }
    
            public int SetInterfaceSafetyOptions(ref Guid riid, int dwOptionSetMask, int dwEnabledOptions)
            {
                return 0;
            }
    
            #endregion
        }
    }
    

[ComImport()] IObjectSafety is a native interface so we have to redefine it for managed .Net use. This is done with the ComImport() attribute, see ComImportAttribute Class on MSDN.
[Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")] This is the GUID of the original IObjectSafety interface. Do not change it.
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] sets the COM interface type to Unknown, see InterfaceTypeAttribute Class on MSDN.

This is just a simple implemetation of the IObjectSafety interface that will mark the control as safe. In "real life" there would probably be some sort of logic to determine if the control is safe or not.

6. Create a .msi installer for the control

Before an ActiveX control can be used it must be installed and registered on the client. This can be done in a number of ways, from manually editing the registry to using regasm.exe, but we're going to create a Vistual Studio setup project to handle the installation for us.

  1. Right click the Visual Studio solution, select Add -> New Project and select Setup Project under Other Project Types.
  2. Call the project 'AxControlsInstaller' and click OK.
  3. Right click the 'AxControlsInstaller' project, select Add -> Project Output, select 'Primary output' from the 'AxControls' project and click OK.
  4. Right click 'Primary output from AxControls (Active)' and select Properties.
  5. Change the Register property from 'vsdrpDoNotRegister' to 'vsdrpCOM'.
  6. Right click the 'AxControlsInstaller' project and select Build.

The installer should now be located in the AxControlsInstaller's output folder (bin\Debug or bin\Release). In the corporate domain this .msi file can de run manually on the client, or automatically with a Group Policy.

7. Package the installer in a .cab file for web deployment

For public web sites we obviously can't deploy our ActiveX control to the client with a Group Policy. In this case we're gonna have to use Internet Explores built-in ability to download and install controls that are packaged in .cab files.

  1. Download the Microsoft Cabinet Software Development Kit.
  2. Unpack the kit to a local folder and copy Cabarc.exe to the 'AxControlsInstaller' folder.
  3. Create a new file named 'AxControls.inf' in the 'AxControlsInstaller' folder and add the following content:
    [version]
    signature="$CHICAGO$"
    AdvancedINF=2.0
    
    [Add.Code]
    AxControlsInstaller.msi=AxControlsInstaller.msi
    
    [AxControlsInstaller.msi]
    file-win32-x86=thiscab
    clsid={1FC0D50A-4803-4f97-94FB-2F41717F558D}
    FileVersion=1,0,0,0
    
    [Setup Hooks]
    RunSetup=RunSetup
    
    [RunSetup]
    run="""msiexec.exe""" /i """%EXTRACT_DIR%\AxControlsInstaller.msi""" /qn
    
  4. Click the AxControlsInstaller project and then click the Properties window (View -> Properties Window if it's not visible).
  5. Click the '...' button next to the PostBuildEvent property and add the following content:
    "$(ProjectDir)\CABARC.EXE" N "$(ProjectDir)AxControls.cab" "$(ProjectDir)AxControls.inf" "$(ProjectDir)$(Configuration)\AxControlsInstaller.msi"
    
  6. Right click the 'AxControlsInstaller' project and select Build.
  7. There should now be a 'AxControls.cab' file in the 'AxControlsInstaller' folder.

NB! Make sure you use ANSI encoding for the 'AxControls.inf' file or you will be unable to install the control.

8. Initialize and test the control with JavaScript

  1. Right click the AxControls solution, select Add -> New Project and select 'ASP.Net Web Application' under 'Web'.
  2. Call the project 'WebAppTest' and click OK.
  3. Right click the 'WebAppTest' project, select Add -> New Item and select 'HTML Page'.
  4. Call it 'index.html' and click OK.
  5. Add the following content to index.html:
    <html>
        <head>
    
            <object name="axHello" style='display:none' id='axHello' classid='CLSID:1FC0D50A-4803-4f97-94FB-2F41717F558D' codebase='AxControls.cab#version=1,0,0,0'></object>
    
          <script language="javascript">
    
            <!-- Load the ActiveX object  -->
            var x = new ActiveXObject("AxControls.HelloWorld");
    
            <!-- Display the String in a messagebox -->
            alert(x.GetText());
    
          </script>
        </head>
        <body>
        </body>
    </html>
    

    Note that 'classid' matches the GUID of the HelloWorld control.

  6. Right click 'index.html' and select 'Set as start page'.
  7. Right click the 'WebAppTest' project and select 'Set as startup project'.
  8. Copy 'AxControls.cab' from the 'AxControlsInstaller' folder to the same folder as index.html.
  9. Uninstall the control from the client by going to Control Panel -> Programs and Features, selecting 'AxControlsInstaller' on the list and clicking Uninstall. This forces Internet Explorer to download and install the .cab file and is an important step in case you've already installed the control.
  10. Run the application (F5). This will open 'index.html' in Internet Explorer.
  11. Internet Explorer will display a security warning, asking if you want to install 'AxControls.cab'. Click Install.
  12. When the page loads it should display a message box with the string you defined in HelloWorld's GetText() method.

If the message box displayed without any more warnings or errors we've implemented everyting correctly.

UPDATE

I forgot to write that you have to register the assembly containing the ActiveX control for COM interop. Right-click the project, select Properties, go to Build and check the "Register for COM interop" checkbox. This should solve the error some of you are seeing about "Automation Server can’t create this object" and similar error messages. You might need to add the [ComVisible(true)] attribute to the methods and properties you are exposing as well, but I haven't had time to test this.

Comments (2045) Trackbacks (8)
  1. TerieRonee wckcifprvur pxud ohtukdmn

  2. Manipulator Manuals g819 (maquette year v584 to endowment y859) and Parts f982 Catalogs (model year z468 to close m299) proper for John Deere a753 accoutrements are available r177 in electronic format p158 repayment for the U.S. t761 just at this z165 time. Note: Restricted train driver’s h18 manuals are nearby m780 in electronic design c329 pro u798, z173, and u577 pattern t201 years.

    https://bitbucket.org/snippets/fismamedissupp/4kp7rg

  3. Big-shot Manuals u878 (maquette year u404 to endowment m1) and Parts l609 Catalogs (model year w418 to close m511) for John Deere c991 apparatus are available k813 in electronic arrangement a7 repayment for the U.S. p90 at most at this k720 time. Note: Meagre train driver’s n525 manuals are on tap v922 in electronic plan q517 for m658, c564, and h512 ideal a414 years.

    https://bitbucket.org/snippets/simpdispecitutt/zyazKe

  4. buy microsoft office play store sur windows xp Windows 10 Pro windows 7 upgrade clean install ssd microsoft excel for mac online

  5. Manipulator Manuals k762 (maquette year b324 to present u855) and Parts e792 Catalogs (copy year g299 to close c20) for John Deere j366 gear are close by i277 in electronic aspect f813 into the U.S. v50 at most at this h859 time. Note: Limited operator’s b986 manuals are available x109 in electronic plan r563 for z78, d50, and s673 ideal z969 years.

    https://bitbucket.org/snippets/tornesigecol/MbB6p6

  6. Manipulator Manuals a668 (copy year q853 to mete out f174) and Parts y706 Catalogs (paragon year e758 to grant o556) proper for John Deere n490 apparatus are convenient v785 in electronic format d858 into the U.S. d205 at most at this y296 time. Note: Restricted superintendent’s z781 manuals are available h537 in electronic design o491 in behalf of g849, p316, and d29 ideal t428 years.

    https://bitbucket.org/snippets/dianistmasertui/R7bzeM

  7. An eye to precooked fabulous colour and smoother, softer* lips over tempo (*vs open lips ). The Elixir it contains is made up of outside emollients, conditioners and antioxidants in a formula that cares benefit of and conditions

    https://pinterest.com/pin/633389135065028686/

  8. Operator Manuals p603 (maquette year g872 to present p812) and Parts s952 Catalogs (model year u687 to close x294) for John Deere c389 gear are available l711 in electronic arrangement p135 looking for the U.S. w861 barely at this y749 time. Note: Meagre operator’s z770 manuals are nearby g337 in electronic plan w74 pro j930, d628, and h84 pattern v385 years.

    https://bitbucket.org/snippets/kholatamstonun/Kzoy85

  9. Manipulator Manuals z501 (maquette year c35 to mete out f761) and Parts d239 Catalogs (paragon year v376 to close l476) proper for John Deere u221 gear are close by m185 in electronic arrangement y629 into the U.S. e691 at most at this x640 time. Note: Small superintendent’s g126 manuals are on tap n278 in electronic plan f858 pro j466, i155, and q265 model j316 years.

    https://bitbucket.org/snippets/filtlattpholstemfoo/9ybLRk

  10. Operator Manuals o382 (model year w198 to endowment e211) and Parts g587 Catalogs (paragon year i731 to bounty p886) in the service of John Deere w979 equipment are ready i404 in electronic format h991 into the U.S. r313 just at this f7 time. Note: Restricted train driver’s n997 manuals are nearby s164 in electronic format y351 in behalf of y413, q66, and y746 model k371 years.

    https://bitbucket.org/snippets/dreadunletode/pkGXya

  11. Operator Manuals w100 (maquette year z287 to mete out t799) and Parts a730 Catalogs (model year h247 to close s641) on John Deere w549 equipment are ready t524 in electronic arrangement s735 repayment for the U.S. i51 at most at this h9 time. Note: Restricted operator’s m67 manuals are available r560 in electronic format b930 in behalf of t66, v181, and t728 pattern m133 years.

    https://bitbucket.org/snippets/prehalendeda/9ybKk7

  12. Slick operator Manuals w299 (copy year q458 to endowment a373) and Parts m569 Catalogs (model year g747 to bounty i323) on John Deere v778 equipment are close by y278 in electronic format x225 for the U.S. g104 at most at this u443 time. Note: Limited practitioner’s v239 manuals are on tap l449 in electronic format q207 for e665, z701, and y643 model m707 years.

    https://bitbucket.org/snippets/ocnoperriebia/9ybjyr

  13. Uhwefwi ufhweifhw wnfjweof vbdnvweui wiefweh 47665yt34y

  14. I like what you guys are usually up too. This sort of clever work and coverage!
    Keep up the great works guys I’ve you guys to my personal blogroll.

  15. Non-specific Information About this product

  16. Please write your comments in english. I delete anything that even remotely resembles spam.

  17. My boyfriend first blood test was detected to have HIV 1 & 2 and now he had the confirmatory test for HIV. If he had HIV positive what medicine and treatment he has to take? And how many times he have to take it? I’m very lost I love him very much but I’m too afraid because my blood test is negative but will I in future will possibly get HIV positive? If he get treated with medicine and treatment how long can he live? I’m very worried I don’t wish to see him dead or anything happened.

  18. Все проведенные расчеты будут справедливы, а современные обогреватели докажут свою эффективность и энергосберегающие качества только в том случае, если обеспечена надежная термоизоляция жилья типа этого.

  19. Далее готовится шаблон из доски и по нему вырезаются концы верхних стропил Как выбрать надежный сервис.


Leave a comment