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