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 (112) Trackbacks (5)
  1. First of all, This is great work.
    I have a strange issue running it on WinXP.
    My proyect uses .Net Remoting
    When I try to call any method from JS an exception in IExplorer is launched saying the method is not supported by the object.
    But the same proyect works on Win7.
    In WinXP I have IExplorer 8.0.6001 and in Win7 8.0.7601.

    Is there any know security issue with IExplorer?
    I hope you can help to me

  2. hi,
    i have used this method to access the ActiveX control using web server it’s working fine .
    But when i have put something into the user control it will not display anything ,message box displayed into the web page but i want to access the user control design can you please tell me how to access the ActiveX control into the web page through web server .

  3. Hello,

    How can I debug my ActiveX control?

  4. Hi Olav,

    I followed the exact same steps . I get this Automation Server can’t create this object. But I was able to resolve it by using the ID directly instead of Instantiating the ActiveXControl (in your case).

    Its so strange that the IE doesn’t popup a dialog asking whether you want to install the CAB file.

    And also I get this error “Object doesn’t support this property or method” when trying a access an active x control’s function. It works fine incase of properties though.

    Please help me resolve this issue.

  5. 10x , work just fine …

  6. This is the best activex/msi/.net installer article out there.. And I’ve read them all :) Kudos Olav!

  7. hi,

    I am not able to create cab file ,
    Getting following message:-

    Creating new cabinet ‘D:\users\jiwan\documents\visual studio 2010\Projects\AxControls\AxControlsInstaller\AxControls.cab’ with compression ‘MSZIP’:
    FCIAddFile() failed: code 1 [Failure opening file to be stored in cabinet]

    Please help me resolve this problem. It would be great if you post the sample code.

    Jiwan

  8. Hi Olav,
    Thanks a lot for nice article,I have created a sample solution with you article. Its working fine on my own pc with html. but when this html page is putting on asp.net solution i cant run active X. ActiveX control is installed on client side automatically but when calling “x.GetText()” there is an error occuring and shows this error………….. “Microsoft JScript runtime error: Automation server can’t create object” please help me…

  9. Hi Olav,

    Yes We have created a cab file to install ActiveX on the client machine, and meanwhile we want the cab install a Win32 app on the client target machine as well, We signed CAB file which conatins the msi installer which install and register ActiveX control dll and Win32 app.

    Following are the contents of the cab file:

    Cab File contains:
    •Setup.msi
    •Setup.inf

    Setup.msi contains:
    •ActiveX.dll
    •App.exe (Win32 app)

    Setup.inf contains:

    [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

    Scenario in detail:
    We have to install our .dll file using .msi installer on traget machine ,but activeXcontrol not installling
    Properly. our solution simple Hello activeX control we are not able to Embed with Wegpage not server its not working on Client Machine.please help us with Quick Reply

    Thanks &Regards
    Bharathi Kudumula

  10. Gr8 Article,

    I am core java and j2ee guy with not even a distant relation which .NET ( m not averse to it but i did not get a chance to it ) . Recently i was looking for such article as I need to develop a ActiveX component. Worked like a charm. Also please update the article with info for changes to a IE secuity setting.

    Gr8 Job Man

  11. Please help me to use this control in Word 2007. I could not see this control in the controls listed in active x window of Word

  12. Well Done !!

    Perfect article and there isnt any problem..
    Thanks Olav :) )

  13. Very helpful !!

  14. excellent article, described in simple understanding way. Here i have one question.. This cab file how to use in winforms.i want to use this .cab file in c# windows project, how can i use this.

  15. I also get the same issue with the Automation Server error but Srikanth’s post does workaround it. I can use properties to accomplish what I need but it would be nicer if I could get methods to work though.

  16. A tip to others having trouble: After experiencing issues on some machines I’ve found that even though IE (I’m running IE9) prompted to install the control and proceeded without an install error message, the control was NOT installing in my case. When I right-clicked on the installer project and ran it manually, then started the browser, the control worked perfectly. So it’s worth checking that the control actually is installing, even when it may already appear to be doing so.

  17. Hi Olav,

    Nice Article ,

    I have few questions. It works on the development machine but not on the client machines. On the client machine, after the user clicks on install, it says object or property not supported. And when I look at the Downloaded Program Files I see my controls status as unknown and not installed. Can you please help me out here. Thanks

    • Check the IE security settings. If your control is not signed you need to allow IE to run unsigned controls. And check that the client has the right .Net Framework installed.

      -Olav

  18. Great artical – you could charge for this.

  19. Hi Olav,
    I want to make an activeX control to take screenshots. ON clicking a button on the webpage the plugin should be invoked and should be able to take screenshots of any active windows(not necessarily of browser only). Can you kindly guide me in the process.
    I am thinking of following your above forum. It will be very kind of you if you could guide me the steps. I have a doubt that the project should be “Class Library”project or “windows application project” ??
    please reply at your earliest.
    Thanking You..
    -
    Rajesh

  20. I have done all the path correctly but when run the index.htm page i got following error:
    Windows has blocked this software because it cant verify the publisher.
    Name : AxControlls
    Publisher: unknown publisher

    What am i suppose to do with this?

  21. I have change IE security tab settings and it solved but an other question is how can i update ActiveX and make sure that it takes affect and how can i debug it please provide me an article about this things.
    Thnaks Olav

  22. this ActiveX needs to install .NET Framework on client computer ?

    • Yes, and it will fail with a stupid error message if the right .Net framework is not installed on the client.”Right” being whatever version you are compiling against.

  23. Thank buddy.., it was very useful for me..,

  24. Hi its working fine in XP but bot in windows 7.., please tell me what are the changes made this project for windows 7

    • No special changes should be needed for Windows 7. Verify that the client has the right .Net Framework, and that the control is properly signed (or security settings in IE modified to allow unsigned controls).

      -Olav

  25. I’m new to the world of ActiveX and WinCE. I’ve generated my Activex control acording to this article (Kudos on a great article, by the way), and it works fine. My question is can this type of control be ported to a WinCE device? Please forgive my ignorance.

  26. hi
    this solution working fine
    but i facing a problem
    can i add another activeX control with this activeX control
    i tried to Add GDTwain activeX control in this interface it does not return any error
    but when i call from html
    it returns “object reference not set to an instance of an object” error
    look like the GDTwain activex does not created

    • Hi Siva,

      Are you packaging the GDTwain control along with your own custom control? If not, then the client won’t be able to use it.

      Did you get your custom control working before you tried to include GDTwain? If not, then make sure you get the “Hello World” example up and running first.

      Usually people forget about the signing part, and then IE – by default – won’t run the control and you get one of those “object reference not set to an instance of an object” errors. You want to set IE to allow unsigned controls while developing this, if you can’t sign the control.

      Also make sure the ID of the control is correct in your HTML.

      Last thing I can think of is to make sure that any other dependencies (ie. like .Net Framework) are installed on the client. If you suspect that the problem is dependencies, then try running the .msi installer directly on the client. It should give a more meaningfull error message.

      -Olav

  27. Regarding the GUID for IObjectSafety, you said not to change from the original GUID, but you in fact used a different one in your sample code. Why? Thanks!

    • Hi Lawrence,

      Well spotted. I don’t actually change the Guid, but in the comments below I list the wrong Guid for the interface.

      I wrote:

      [Guid("DCE0B4D4-FA5C-43e4-AE3E-0C881A6DD293")] This is the GUID of the original IObjectSafety interface. Do not change it.

      But I should have written:

      [Guid("CB5BDC81-93C1-11CF-8F20-00805F2CD064")] This is the GUID of the original IObjectSafety interface. Do not change it.

      See: http://msdn.microsoft.com/en-us/library/aa768224(v=vs.85)

      I’ve updated the post.

      With regards
      Olav

  28. Hello Sir,
    This article was very useful for one of my application.I am facing a problem when using this code in a webpage with masterpage,but works fine in a normal webpage without a masterpage.Please let me know what has to be done.Awaiting for ur reply…

    • Hi Uday,

      Sorry but I don’t know what’s causing your issue. There shouldn’t be any difference if your using a masterpage or not. Do you get any error messages?

      -Olav

  29. Great article. Thanks

  30. i tried the example it was worked for me now i just changed the return message to Hello Ravindra and build the same Example(AxControls).I again copied the AxControls.cab file to the same folder where index.html exists and test it in browser but still i am getting “Hello ActiveX World!” (the previous message) please help on this

  31. Hi Olav.
    Great article….I’ve a problem with debugging.
    I use VS2010, .NET 4 (.. mandatory stuff), i’ve set “platform target” to x86.
    Then i run a Win32 application “client” (a simply vb6 app) , and everything works fine.

    Now…

    Try to debug the COM object from VS2010. Set the class library Debug option to start an external program, point to the executable Win32 application ..nothing….
    Everything works well but i can’t debug my C# code…

    Any suggest?
    Thanks in advance.

  32. Hey, great article, but i got a few problems…

    at 7.6 “Right click the ‘AxControlsInstaller’ project and select Build.” the project fails to build
    ‘PostBuildEvent’ failed with error code ’1′ ‘Unspecified error’
    i cannot generate the cap file.

    Microsoft removed the cabsdk and i had to download it from somewhere else, im not sure if this is whats causing my problems. Do you recommend another link to download the sdk?

    I am using VS2010 .net4.0 c#

  33. This is greate work.
    But when the activex control call more time ,Internet Explorer is hang.I not resolve this issue more time. Please help me !

  34. This is great work.
    But when the activeX control call more time ,Internet Explorer is hang.I not resolve this issue more time. Please help me !

  35. Great article
    works perfect!

  36. Hi , this is nice article….but i am not getting one point

    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”

    wht’s mean on “…” …can you plz let me know?

  37. wonderful points altogether, you simply gained a new reader.

    What could you suggest about your post that you just made some days ago?
    Any positive?


Leave a comment