Inveigled By Design

The art of persuasive design

Silverlight 4: Integrating With COM


One new feature introduced in the Silverlight 4 beta that caught my eye was the ability to communicate with a Silverlight app via COM. It definitely opens up some powerful ways to interact with the Windows desktop environment in Silverlight. I mocked up this quick demo just to see how it might work and hope it’s useful to someone else trying to figure out how to do this.

Here are the tools you need to get started:

The Silverlight 4 Beta Tools will give you the Silverlight 4 runtime, integrated project support in Visual Studio, and the Silverlight 4 SDK. Remember that these are beta tools, so I do not recommend installing them on a production developer machine just yet.

There’s more information on getting set up with Silverlight 4 here:
Silverlight 4 Beta Information

Get the Code:

Download the code here:
SilverlightComDemo.zip

A Preview:

In this article, we’ll build a simple COM library that will display a user interface with four buttons and a Silverlight app displaying a simple circle inside a grid. The user will be able to move the circle in the Silverlight app by pressing the buttons being displayed by the COM library. This demonstrates the concept of interacting with Silverlight apps via COM.

While this application is very simple and not that useful, the same techniques can be applied to interact with custom hardware, office applications, or other COM components on the user’s system.

Step 1: Create the COM Library

Your COM Library may be a c++ library, or something like Excel or Word that already exist. For purposes of this demo, I’ll create the COM library in .Net. There are some good references for how to do this on CodeProject if you need more information.

Before you fire up Visual Studio 2010, note that if you are running Windows Vista or Windows 7, you have to right-click on Visual Studio and choose “Run as administrator” to create the project. This is because we are going to configure Visual Studio to register our COM library automatically, which requires administrator privileges.

Here are the steps to get started:

  • Create an empty solution in Visual Studio and name it SilverlightComDemo.
  • Create a new C# class library called ComDemoLib.
  • Right-click on the project in Solution Explorer and choose Properties and then the Build tab.
  • Check the “Register for COM interop” checkbox. When you build the library, Visual Studio will register the com library so that it can be accessed by the Silverlight app we’ll create below.
  • Click on the Signing tab and check the “Sign the assembly” checkbox and create a new key that our assembly will be signed with. You will not be able to register the COM library unless the assembly has a strong name (is signed with a key).

Setting up .NET classes so that they can be seen as COM objects requires using the System.Runtime.InteropServices library and decorating your classes and interfaces with some attributes. I found this article to be a good reference for seeing how to set things up:
C# COM Object for Use in Javascript.

Continuing on:

  • Remove the Class1 class that is created by default in the ComDemoLib project
  • Right-click the ComDemoLib project and add a reference to the .Net System.Windows.Forms assembly.
  • Add a windows form to the project and call it DemoForm

Now add four buttons so that DemoForm looks like this:

The code behind defines four directional events and hooks up the button click handlers to fire them:

using System;
using System.Windows.Forms;

namespace ComDemoLib
{
    public partial class DemoForm : Form
    {
        public event EventHandler<EventArgs> MoveUp;
        public event EventHandler<EventArgs> MoveDown;
        public event EventHandler<EventArgs> MoveRight;
        public event EventHandler<EventArgs> MoveLeft;

        public DemoForm()
        {
            InitializeComponent();
        }

        private void button_up_Click(object sender, EventArgs e)
        {
            fire(MoveUp);
        }

        private void button_right_Click(object sender, EventArgs e)
        {
            fire(MoveRight);
        }

        private void button_left_Click(object sender, EventArgs e)
        {
            fire(MoveLeft);
        }

        private void button_down_Click(object sender, EventArgs e)
        {
            fire(MoveDown);
        }

        private void fire(EventHandler<EventArgs> eventToFire)
        {
            EventHandler<EventArgs> temp = eventToFire;

            if (temp != null)
                temp(this, EventArgs.Empty);
        }
    }
}

Now create three classes:

  • ComDemoClass will be our main COM object that the Silverlight app will interact with
  • IComDemoClass defines a very simple interface for our ComDemoClass
  • IComDemoClassEvents defines an interface for our COM events that the Silverlight app will receive

In order to register with COM, our ComDemoClass and interfaces must be public and have Guids assigned to them via the Guid attribute. When I tried to access “Create GUID” from the Tools menu in Visual Studio 2010 beta, it was grayed out. If this is true for you as well, you can access the Guid Generator tool by typing “guidgen” at the Visual Studio 2010 command prompt. If you run this project on your machine, you should generate new guids for any new classes that you create.

Our ComDemoClass will have two methods: ShowForm and HideForm that control the visibility of the demo form we created above. It will also fire the directional events that will tell the Silverlight app which button was pushed.

Here is the code for the ComDemoClass:

using System;
using System.Runtime.InteropServices;

namespace ComDemoLib
{
    [Guid("C920762D-5819-4B06-A9FE-F42EA615771D")]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(IComDemoClassEvents))]
    public class ComDemoClass : IComDemoClass
    {
        private DemoForm m_form;

        [ComVisible(false)]
        public delegate void moveEventSignature();

        public event moveEventSignature Left;
        public event moveEventSignature Right;
        public event moveEventSignature Up;
        public event moveEventSignature Down;

        public void ShowForm()
        {
            if (m_form == null)
            {
                m_form = new DemoForm();
                m_form.MoveLeft += form_Left;
                m_form.MoveRight += form_Right;
                m_form.MoveUp += form_Up;
                m_form.MoveDown += form_Down;
                m_form.Show();
            }
        }

        public void HideForm()
        {
            if (m_form != null)
            {
                m_form.MoveLeft -= form_Left;
                m_form.MoveRight -= form_Right;
                m_form.MoveUp -= form_Up;
                m_form.MoveDown -= form_Down;
                m_form.Dispose();
                m_form = null;
            }
        }

        private void form_Left(object sender, EventArgs e)
        {
            fire(Left);
        }

        private void form_Right(object sender, EventArgs e)
        {
            fire(Right);
        }

        private void form_Up(object sender, EventArgs e)
        {
            fire(Up);
        }

        private void form_Down(object sender, EventArgs e)
        {
            fire(Down);
        }

        private void fire(moveEventSignature eventToFire)
        {
            moveEventSignature temp = eventToFire;

            if (temp != null)
                temp();
        }
    }
}

This is pretty simple. It creates a demo form and then fires events when the user presses the buttons on the form. Note that for COM, our events have to be defined in a separate interface and our class declares the interface via the ComSourceInterfaces attribute.

Here is the code for the IComDemoClass interface:

using System.Runtime.InteropServices;

namespace ComDemoLib
{
    [Guid("3BCA5655-05EC-42D8-A046-5C0567DB7901")]
    [ComVisible(true)]
    interface IComDemoClass
    {
        [DispId(0x10000001)]
        void HideForm();

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

And finally the IComDemoClassEvents inteface:

using System.Runtime.InteropServices;

namespace ComDemoLib
{
    [Guid("9942C070-D524-420F-AEBA-30A50D5A5291")]
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IComDemoClassEvents
    {
        [DispId(0x00000001)]
        void Left();

        [DispId(0x00000002)]
        void Right();

        [DispId(0x00000003)]
        void Up();

        [DispId(0x00000004)]
        void Down();
    }
}

Now build the library and verify that it was registered on the system. Any messages about failures to register will be logged to the output window. Once the build succeeds, if you right-click on the ComDemoLib project and choose “Add Reference”, you should see ComDemoLib listed in the COM tab (this is for verification only, hit cancel and don’t actually add the reference).

Step 2: Create and Configure the Silverlight Application

Now that we have the COM library ready to go, we need a silverlight application that can interact with it. Add a new Silverlight Application project to the solution and name it “SilverlightComDemo”. Allow Visual Studio to create a web hosting project (SilverlightComDemo.Web) and ensure that you are using Silverlight 4.

In order to interact with COM, the Silverlight app needs to run out of browser and have elevated permissions on the user’s system. In order to use COM, it also needs to reference the Microsoft.CSharp.dll. To configure this:

  • Right-click on the SilverlightComDemo project and choose properties
  • On the Silverlight tab, check the “Enable running application out of the browser” box
  • Press the “Out-of-Browser Settings” button. At the bottom of the window, check the “Require elevated trust when running outside the browser” checkbox.
  • Right-click the SilverlightComDemo project again and choose “Add Reference”. Click on the “Browse” tab and then add a reference to the Microsoft.CSharp library, which was installed as part of the Silverlight 4 SDK. The default path is here:
    C:\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\Microsoft.CSharp.dll

Our MainPage will be pretty simple. It includes some installation stuff (that will only show when the app is not installed or not running outside the browser) and the grid with the circle in it whose position will be controlled via the COM library:

Here’s the xaml for the page:

<UserControl
    x:Class="SilverlightComDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400"
    Loaded="UserControl_Loaded">

    <Grid x:Name="LayoutRoot" Background="Navy">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TextBlock Foreground="White" Text="Silverlight Com Demo"
            Grid.Row="0"  FontSize="20" Margin="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        <Grid Name="demoGrid" Grid.Row="1" >
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Ellipse Name="ellipseControl" Grid.Row="1" Grid.Column="1" Fill="Yellow" Width="40" Height="40"/>
        </Grid>
        <StackPanel x:Name="installationControls" Grid.Row="2">
            <TextBlock x:Name="installText" Text="Please run in out of browser mode"
                Width="200" Visibility="Collapsed" TextWrapping="Wrap" TextAlignment="Center" FontSize="14" Foreground="Red" />
            <Button x:Name="installButton" Click="installButton_Click" Content="install"
                Margin="80, 5" Height="40" Foreground="Red" FontSize="18" />
        </StackPanel>
    </Grid>
</UserControl>

Here’s the full code for the MainPage.xaml.cs code behind file, which I’ll discuss below:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;

namespace SilverlightComDemo
{
    public partial class MainPage : UserControl
    {
        private dynamic m_comDemoClass;

        delegate void motionEventDelegate();

        public MainPage()
        {
            InitializeComponent();
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            bool ready = true;

            demoGrid.Visibility = Visibility.Visible;
            installationControls.Visibility = Visibility.Collapsed;

            #region installation Checks

            // see if the app is installed
            if (App.Current.InstallState != InstallState.Installed)
            {
                ready = false;
                installText.Text = "Please install the application";
            }

            // make sure the app is running out of browser
            if (!App.Current.IsRunningOutOfBrowser)
            {
                ready = false;
            }

            if (!ready)
            {
                demoGrid.Visibility = Visibility.Collapsed;
                installationControls.Visibility = Visibility.Visible;
                return;
            }

            #endregion   

            // we want to hide the form when the app exits
            App.Current.Exit += application_exit;

            // if we got here, we're ready to go, create the com object
            m_comDemoClass = ComAutomationFactory.CreateObject("ComDemoLib.ComDemoClass");

            // if the class was created, show our control form
            if (m_comDemoClass != null)
            {
                m_comDemoClass.ShowForm();

                m_comDemoClass.Left += new motionEventDelegate(moveLeft);
                m_comDemoClass.Right += new motionEventDelegate(moveRight);
                m_comDemoClass.Up += new motionEventDelegate(moveUp);
                m_comDemoClass.Down += new motionEventDelegate(moveDown);
            }
        }

        private void installButton_Click(object sender, RoutedEventArgs e)
        {
            App.Current.Install();
        }

        private void application_exit(object sender, EventArgs e)
        {
            if (m_comDemoClass != null)
            {
                m_comDemoClass.Left -= new motionEventDelegate(moveLeft);
                m_comDemoClass.Right -= new motionEventDelegate(moveRight);
                m_comDemoClass.Up -= new motionEventDelegate(moveUp);
                m_comDemoClass.Down -= new motionEventDelegate(moveDown);
                m_comDemoClass.HideForm();
            }
        }

        private void moveLeft()
        {
            int currentColumn = Grid.GetColumn(ellipseControl);

            if (currentColumn-- == 0)
                currentColumn = 2;

            Grid.SetColumn(ellipseControl, currentColumn);
        }

        private void moveRight()
        {
            int currentColumn = Grid.GetColumn(ellipseControl);

            if (currentColumn++ == 2)
                currentColumn = 0;

            Grid.SetColumn(ellipseControl, currentColumn);
        }

        private void moveUp()
        {
            int currentRow = Grid.GetRow(ellipseControl);

            if (currentRow-- == 0)
                currentRow = 2;

            Grid.SetRow(ellipseControl, currentRow);
        }

        private void moveDown()
        {
            int currentRow = Grid.GetRow(ellipseControl);

            if (currentRow++ == 2)
                currentRow = 0;

            Grid.SetRow(ellipseControl, currentRow);
        }
    }
}

In order to set the app up for debugging and interacting with COM, we need to get it installed on the system. Build the app and run it. It should open up your default web browser and show you the installation controls:

Go ahead and press the “install” button. You’ll get a popup window that lets you choose where to put shortcuts for the app and warns you that the app will have potentially dangerous access to your system. Your users will see this same warning whenever you deploy a Silverlight application that requires elevated trust on a user’s system.

Now that the app is installed, you can configure it for easy debugging by following the directions here:
Debug Silverlight OOB Applications Without Attaching

Step 3: Integrate the COM Library into the Silverlight Application

From the code listing for MainPage.xaml.cs above, you’ll see that we created an object from our COM library (m_comDemoClass) by declaring a dynamic type. This tells the compiler that it is a type that will be defined at runtime.

The code to create the object and set up the event handlers is here:

 // if we got here, we're ready to go, create the com object
            m_comDemoClass = ComAutomationFactory.CreateObject("ComDemoLib.ComDemoClass");

            // if the class was created, show our control form
            if (m_comDemoClass != null)
            {
                m_comDemoClass.ShowForm();

                m_comDemoClass.Left += new motionEventDelegate(moveLeft);
                m_comDemoClass.Right += new motionEventDelegate(moveRight);
                m_comDemoClass.Up += new motionEventDelegate(moveUp);
                m_comDemoClass.Down += new motionEventDelegate(moveDown);
            }

Since we don’t have a direct reference to the COM library, we have to create the object using the ComAutomationFactory.CreateObject call. This is defined in the System.Windows.Interop library. We create the object and then hook up listeners for each of the events.

We also hooked up a listener to the application_exit event so that we can clean up the COM class when the application is closing:

     private void application_exit(object sender, EventArgs e)
     {
            if (m_comDemoClass != null)
            {
                m_comDemoClass.Left -= new motionEventDelegate(moveLeft);
                m_comDemoClass.Right -= new motionEventDelegate(moveRight);
                m_comDemoClass.Up -= new motionEventDelegate(moveUp);
                m_comDemoClass.Down -= new motionEventDelegate(moveDown);
                m_comDemoClass.HideForm();
            }
        }

And that’s it! Now when you run the app, you should see the DemoForm window pop up. When you click on any of the buttons, the yellow circle’s position should move around according to which button you clicked.

Leave a Reply

Creative Commons License
Content on this site is licensed under a Creative Commons Attribution-Share Alike 3.0 License
Copyright © 2009 Inveigled Software