How to write a COM Extension

In this tutorial we will focus on using C# with Visual Studio 2008 (other versions of Visual Studio will work in much the same way). It is also relatively straight-forward to create extension objects in unmanaged C++ using ATL, and is preferable where raw performance is required. The TextFileReader sample project found in the installation directory of Tradesignal demonstrates how this is done.

Objective

Create an Equilla extension object that can compute a Simple Moving Average and play a sound from the local hard disk.

Creating a project file

The first task is to create an appropriate project:
  • Start Visual Studio 2008 (as administrator when running Windows Vista or above)
  • Select File > New > Project... > Visual C# > Class Library
  • Enter a name for the Project (e.g. "SoundAverage") and click OK
  • A project containing a single class called Class1 should have been created
Once the project is created we must add a reference to the EquillaExtension.dll:
  • Select Project > Add Reference... > COM > Tradesignal EquillaExtension 1.0 Type Library and click OK

Defining the interface

The next task is to define what the interface for our new object will be and then to make it visible via COM:

Open the Class1.cs file if it is not already open and inside the namespace SoundAverage block add the following:
namespace SoundAverage
{
    public interface ISoundAverage
    {
        double SimpleAverage(EquillaExtension.IDoubleSeries prices, int length);
        void PlaySound(string path);
    }

    public class Class1
    {
    }
}
Next we must make the interface visible to COM, this requires us to indicate it is COM visible and to specify a unique class ID (a GUID). Both of these can be set by specifying attributes as follows:
using System.Runtime.InteropServices;

namespace SoundAverage
{
    [ComVisible(true)]
    [Guid("E4AF0866-A1DB-462f-A304-4ED46EB7C2E6")]
    public interface ISoundAverage
    {
        double SimpleAverage(EquillaExtension.IDoubleSeries prices, int length);
        void PlaySound(string path);
    }

    public class Class1
    {
    }
}
Important: All classes and interfaces that we create must have a unique GUID, the tool under Tools > Create GUID is a simple way to create a new GUID.

Important: Once an interface is created and deployed it must never change, it is one of the ground rules of COM development. If you want to add more methods to a deployed interface you must create new interfaces.

Adding help strings to the interface

Extension objects are supported by the auto suggest and insight help features of the Equilla Editor. In order to display help messages we must adorn our interface and members with Description attributes:
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace SoundAverage
{
    [ComVisible(true)]
    [Guid("E4AF0866-A1DB-462f-A304-4ED46EB7C2E6")]
    [Description("Calculates a moving average and plays sounds")]
    public interface ISoundAverage
    {
        [Description("Returns the result of a simple moving average")]
        double SimpleAverage(EquillaExtension.IDoubleSeries prices, int length);

        [Description("Asynchronously plays the .WAV file specified by the file name")]
        void PlaySound(string path);
    }

    public class Class1
    {
    }
}

Implementing the extension object

Now that we have our interface, we can create a class that uses it.

First of all we must implement our interface (we will ignore error handling for the sake of brevity, and because the Equilla compiler and runtime engine will display exceptions in a useful way if they occur):
public class Class1 : ISoundAverage
{
    double ISoundAverage.SimpleAverage(EquillaExtension.IDoubleSeries prices, int length)
    {
        double sum = 0;
        for (int i = 0; i < length; ++i)
            sum += prices.GetValue(i);
        return sum / length;
    }

    void ISoundAverage.PlaySound(string path)
    {
        System.Media.SoundPlayer player = new System.Media.SoundPlayer(path);
        player.Play();
    }
}
Next we must implement the IEquillaExtension interface, without this interface Tradesignal will not recognize the object:
public class Class1 : ISoundAverage, EquillaExtension.IEquillaExtension
{
    double ISoundAverage.SimpleAverage(EquillaExtension.IDoubleSeries prices, int length)
    {
        double sum = 0;
        for (int i = 0; i < length; ++i)
            sum += prices.GetValue(i);

        return sum / length;
    }

    void ISoundAverage.PlaySound(string path)
    {
        System.Media.SoundPlayer player = new System.Media.SoundPlayer(path);
        player.Play();
    }

    void EquillaExtension.IEquillaExtension.Attach(EquillaExtension.IEquillaHost Host)
    {
    }

    void EquillaExtension.IEquillaExtension.Detach()
    {
    }
}
We do not need to use the IEquillaExtension methods in this case.

We must next make the class visible to COM, indicate the default COM interface, and provide a suitable name (Prog ID) with which we will create the object from Equilla:
[ComVisible(true)]
[Guid("516024B3-0D76-4d5b-97AE-65F3A30E0C8A")]
[ComDefaultInterface(typeof(ISoundAverage))]
[ProgId("SoundAverage.Class1")]
public class Class1 : ISoundAverage, EquillaExtension.IEquillaExtension
{
    double ISoundAverage.SimpleAverage(EquillaExtension.IDoubleSeries prices, int length)
    {
        double sum = 0;
        for (int i = 0; i < length; ++i)
        sum += prices.GetValue(i);

        return sum / length;
    }

    void ISoundAverage.PlaySound(string path)
    {
        System.Media.SoundPlayer player = new System.Media.SoundPlayer(path);
        player.Play();
    }

    void EquillaExtension.IEquillaExtension.Attach(EquillaExtension.IEquillaHost Host)
    {
    }

    void EquillaExtension.IEquillaExtension.Detach()
    {
    }
}
Our COM-based extension object is almost ready, we just need to build it and register it.
  • First indicate it should be registered automatically after it is built by checking the option: Project > SoundAverage Properties... > Build > Output > Register for COM interop
  • Now, select Build > Build Solution to build the DLL.
Now we have a built and registered COM-based Equilla Extension Object which could now be deployed to other users workstations.

Using the Extension from Equilla

To use the object from Equilla we must first create a new indicator called LoudSMA after starting Tradesignal.

Next we must create an instance of our object as follows:
Object:
    soundAverage( "SoundAverage.Class1" );

Notice how we use the same ID string as we declared in the ProgID attribute when we created the object.

Finally we can call the functions on the soundAverage object to plot a SMA and make an audio alert based on a sound file when a cross over occurs (we assume the file exists at the specified location).
Inputs:
    Price( Close ),
    Length( 14, 1 ),
    AlertFile( "C:\alert.wav" );


Object:
    soundAverage( "SoundAverage.Class1" );


Variable:
    lsma;


lsma = soundAverage.SimpleAverage( Price, Length );

DrawLine( lsma, "LSMA" );

If Price Crosses Over lsma Then
    soundAverage.PlaySound( AlertFile );