Click here to Skip to main content
Click here to Skip to main content

C# COM Object for Use In JavaScript / HTML, Including Event Handling

, 22 Apr 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
A complete example of how to create a C# COM object for use in JavaScript / HTML, including event handling

Introduction

I wanted to be able to use a pre-built .NET object inside a web browser. After searching the web (including CodeProject), I found that a possible solution would be to create a .NET COM component and use the ActiveXObject in JavaScript to interact with the COM object. A Google search turned up many helpful articles, but nothing that showed how to create a COM component in C#, surface .NET events through COM, and use the COM object in JavaScript to my satisfaction. In this article, I will put together all the pieces to show how to implement a complete solution, to use C# COM objects inside JavaScript.

Overview

This article describes how to create a C# COM Object (using Visual Studio .NET 2005) that can be used in JavaScript inside a web browser (only tested on IE 8 RC on Windows Vista). Complete source code for the COM object, as well as a simple web page that demonstrates how to use the COM object is provided, including how to make calls into the COM Object, and how to handle .NET events raised from the COM object.

Creating the C# COM Library

Create a new C# Class Library project called MyComComponent, and enable the setting “Register for COM interop” in the build settings for the project. If you are using Vista, you will need to run Visual Studio as an administrator to build your project after changing this setting. Exclude Class1.cs from the project.
Add a class named MyComObject.cs to the project, change the access modifier to public, and import the System.Runtime.InteropServices namespace:

using System;
using System.Runtime.InteropServices;

namespace MyComComponent
{
    public class MyComObject
    {
    }
} 

Add the interfaces that define the operations and events that will be exposed to COM. Add empty interface definitions named IComObject and IComEvents to the MyComObject.cs file:

using System;
using System.Runtime.InteropServices;

namespace MyComComponent
{
    public class MyComObject
    {
    }

    public interface IComObject
    {
    }

    public interface IComEvents
    {
    }
}

Expose the MyComObject class and the IComObject and IComEvents interfaces to COM. First, add the attribute [ComVisible(true)] to the class and interfaces.

using System;
using System.Runtime.InteropServices;

namespace MyComComponent
{
    [ComVisible(true)]
    public class MyComObject
    {
    }

    [ComVisible(true)]
    public interface IComObject
    {
    }

    [ComVisible(true)]
    public interface IComEvents
    {
    }
}

Since we are defining our own custom COM interface, we need to tell the Type Library Exporter to not generate an interface for us. To do this, add the ClassInterface attribute to the MyComObject class with value of ClassInterfaceType.None.

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class MyComObject
{
}

We need to tell the Type Library Exporter that the interface for IComEvents is IDispatch in order to get events to be raised out of our COM object. To do this, add the InterfaceType attribute to the IComEvents interface, with value of ComInterfaceType.InterfaceIsIDispatch.

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComEvents
{
}

Now we can start decorating the MyComObject with the interface definitions. IComObject is simple enough to implement, just by explicitly implementing it. The Type Library Exporter will automatically expose all events in the IComObject interface for us.

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class MyComObject : IComObject
{
}

Adding the IComEvents interface is a little different. Use the ComSourceInterfaces attribute on the MyComObject class, with typeof(IComEvents) as the value. The ComSourceInterfaces attribute is used to define the list of interfaces that are exposed as COM event sources.

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IComEvents))]
public class MyComObject : IComObject
{
}

Each type exposed from COM has a globally unique ID. Since we haven't defined the GUID’s, the Type Library Exported will do this for us. However, we want to be able to use this GUID to load the type, so we will now add the GUIDs to all three types. Use the guidgen utility to do this.

We now have the code structure in place for a COM object that explicitly implements an interface, and has an events source interface defined. The code at this point should be something like:

using System;
using System.Runtime.InteropServices;

namespace MyComComponent
{
    [Guid("4794D615-BE51-4a1e-B1BA-453F6E9337C4")]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(IComEvents))]
    public class MyComObject : IComObject
    {
    }

    [Guid("4B3AE7D8-FB6A-4558-8A96-BF82B54F329C")]
    [ComVisible(true)]
    public interface IComObject
    {
    }

    [Guid("ECA5DD1D-096E-440c-BA6A-0118D351650B")]
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IComEvents
    {
    }
}

Now we will add some methods to the interfaces. To IComObject, add a method called MyFirstComCommand that accepts a string as an argument and returns an integer, as well as a method called Dispose that has no parameters and no return value.

[Guid("4B3AE7D8-FB6A-4558-8A96-BF82B54F329C")]
[ComVisible(true)]
public interface IComObject
{ 
    [DispId(0x10000001)]
    int MyFirstComCommand(string arg);

    [DispId(0x10000002)]
    void Dispose();
}

To IComEvents, add a single method called MyFirstEvent that accepts a string as an argument and has no return value. Note the absence of the event keyword.

[Guid("ECA5DD1D-096E-440c-BA6A-0118D351650B")]
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComEvents
{
    [DispId(0x00000001)]
    void MyFirstEvent(string args);
}

All that’s left to do is to implement the methods and events in the MyComObject class. The implementation of MyFirstCommand just raises the MyFirstEvent event and returns a random number. The Dispose method displays a message box. Add the following code to the MyComComponent class, and add a reference to System.Windows.Forms.dll to the project:

[ComVisible(false)]
public delegate void MyFirstEventHandler(string args);

public event MyFirstEventHandler MyFirstEvent;

public int MyFirstComCommand(string arg)
{
    if (MyFirstEvent != null)
        MyFirstEvent(arg);
    return (int)DateTime.Now.Ticks;
}

public void Dispose()
{
    System.Windows.Forms.MessageBox.Show("MyComComponent is now disposed");
}

Now we have a fully functional COM object, written in C#. The code for the COM object should look like this:

using System;
using System.Runtime.InteropServices;

namespace MyComComponent
{
    [Guid("4794D615-BE51-4a1e-B1BA-453F6E9337C4")]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(IComEvents))]
    public class MyComObject : IComObject
    {
        [ComVisible(false)]
        public delegate void MyFirstEventHandler(string args);

        public event MyFirstEventHandler MyFirstEvent;

        public int MyFirstComCommand(string arg)
        {
            if (MyFirstEvent != null)
                MyFirstEvent(arg);
            return (int)DateTime.Now.Ticks;
        }

        public void Dispose()
        {
            System.Windows.Forms.MessageBox.Show("MyComComponent is now disposed");
        }
    }

    [Guid("4B3AE7D8-FB6A-4558-8A96-BF82B54F329C")]
    [ComVisible(true)]
    public interface IComObject
    {
        [DispId(0x10000001)]
        int MyFirstComCommand(string arg);

        [DispId(0x10000002)]
        void Dispose();
    }

    [Guid("ECA5DD1D-096E-440c-BA6A-0118D351650B")]
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IComEvents
    {
        [DispId(0x00000001)]
        void MyFirstEvent(string args);
    }
}

Creating an Instance of the COM Component

To use the COM component in a web page, embed an <object /> tag in the head of the page

<object name="myComComponent" id="myComComponent" 
	classid="clsid:4794D615-BE51-4a1e-B1BA-453F6E9337C4" />

Hooking Up Events to the COM Component

Hooking up the events from the COM component is a little tricky. What you need to do is embed a <script/> tag in the body of the web page as follows:

<script for="myComComponent" event="MyFirstEvent(args)" language="javascript">
function myComComponent::MyFirstEvent(args) {
}

The "for" attribute should be set to the name of instance of your COM component. The "event" attribute should be set to the JavaScript function signature for the event. The name of the JavaScript function is the instance name of your COM component, followed by double colons, followed by the JavaScript signature of the event.

Executing Commands on the COM Component

Here's the JavaScript code to execute MyFirstComCommand:

var returnCode = myComComponent.MyFirstComCommand("Hello World!");

Disposing of the COM Component

In Internet Explorer 8, the COM component is not destroyed until the web browser is fully closed - closing of the tab that contained the COM component does not dispose of the object! If the user continuously hits the refresh button, new instances of the COM component will be created, but the old instances won't be released until the web browser is closed. You can use the onunload event to detect when the page is unloaded, then dispose of your COM component.

In our example, the Dispose method we added could be used for this purpose. Just change the implementation of the Dispose method to clean up any resources used by the COM component. The implementation in this example just shows a Message Box, so we know when the Dispose method is getting called.

Putting it all Together

Below is the source code to a web page that creates an instance of the MyComComponent COM object, executes methods on the COM component, and listens for events from the COM component.

<html>
    <head>
        <title>My Com Component</title>
        <object id="myComComponent" name="myComComponent" 
		classid="clsid:4794D615-BE51-4a1e-B1BA-453F6E9337C4"></object>
        <script language="javascript" type="text/javascript">
            function myButton_click() {
                var returnCode = myComComponent.MyFirstComCommand(myArgs.value);
                var msg = "myComComponent.MyFirstComCommand returned " + returnCode;
                appendText(msg);
                appendText("\r\n");
            }
            
            function setText(txt) {
                myTextBox.value = txt;
            }
            
            function appendText(txt) {
                myTextBox.value = myTextBox.value + txt;
            }
            
            function MyComComponent_onload() {
                setText("");
                myComComponent.MyFirstComCommand("Hi");
            }
            
            function MyComComponent_onunload() {
                myComComponent.Dispose();
            }
        </script>
    </head>
    <body onload="MyComComponent_onload();" onunload="MyComComponent_onunload();">
        <h1>My Com Component</h1>
        <table>
            <tr>
                <td>
                    <button id="myButton" onclick="myButton_click();">Com Method</button>
                    <label>Args</label>
                    <textarea id="myArgs" rows="1" cols="16">Hello World!</textarea>
                </td>
            </tr>
            <tr>
                <td>
                    <textarea id="myTextBox" rows="10" cols="80"></textarea>
                </td>
            </tr>
        </table>
        
        <script for="myComComponent" event="MyFirstEvent(args)" language="javascript">
        function myComComponent::MyFirstEvent(args) {
            appendText("myComComponent raised MyFirstEvent. args: ");
            appendText(args);
            appendText("\r\n");
        }
        </script>
    </body>
</html>

Using the Code

To use the sample web page, you first need to compile the MyComComponent solution file. The generated assembly will automatically be registered for COM Interop when you build the solution, so you don't need to put the assembly in the same folder as the web page.

After building the solution, open the web page MyComComponent.htm in your browser (make sure ActiveX and JavaScript are enabled).

Links

History

  • 22 April 2009 - Original Post

License

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

Share

About the Author

Jerome Terry
Software Developer Desire2Learn
Canada Canada
Professional software developer in St. John's, Newfoundland, Canada
 
http://www.jerometerry.com
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
QuestionRegistering this dll on another machine Pinmemberseyersoft28-Dec-11 10:17 
AnswerRe: Registering this dll on another machine PinmemberJerome Terry28-Dec-11 12:23 
AnswerRe: Registering this dll on another machine PinmemberMember 831849929-Nov-12 19:13 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 22 Apr 2009
Article Copyright 2009 by Jerome Terry
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid