Introduction

This article is written for a mixed audience of COM developers and .NET programmers. Specifically, .NET programmers wanting to write a DLL. More specifically yet, .NET programmers wanting to implement a COM server. Also, COM developers (e.g. you're writing a VBA application) wanting to use the facilities provided by a COM server.

Scroll to the end of this article if you want to download the example project.

Writing a COM Server using .NET

We provide an example .NET solution XslTransformer. This solution is written in C# using Microsoft Visual Studio 2008. The solution won't build on earlier versions of Visual Studio because it needs the functionality provided by the .NET Framework v3.5. It defines a formal interface that can be called by both COM and .NET clients. However, its clients can be built with earlier version of Visual Studio and of course VBA (which predates the conception of .NET by several years). Read this article about writing a COM Server using .NET

The XslTransformer's task is simple. It is designed to transform an XML input file into an HTML output file using rules provided in an XSL transform file. We supply an example report.xml input file and html.xsl XSL file with the XslTransformer project.

XslTransformer is conceptually simple. Feed in an XML data file and the XSL rule file, and out comes an HTML file ready for viewing in your favourite browser. In fact, the XslTransformer provides a Display method that automates the display …

XML in, HTML out

XslTransformer is not original: it provides a service that you can obtain in several other ways. However, its purpose is also to make that service simple to use from a COM client or a .NET client. Also, since we publish the source code, it's a tutorial about the practicality of writing an interface-based software component using .NET. Judge for yourself whether it fulfulls the designer's intent.

XslTransformer's Interface

XslTransformer's VBA Interface

VB/VBA is designed to use COM. The Interactive Development Environment (IDE) scans a COM server to find its properties & methods. Once you're referenced a COM server in VBA, use the Object Browser (press F2) to see its contents. Here's the COM interface as seen by a VBA client using the IDE Object Browser …

Interface viewed from the VBA IDE

The XslTransformer.Engine class provides these properties and methods …

XslTransformer Properties
Property Data Type Description
XmlFile String Get/Set the XML input file path
XslFile String Get/Set the XSL input file path
HtmlFile String Get/Set the HTML output file path
ToString String Describes the current state of the Engine (read-only default Get property)
XslTransformer Methods
Method Description
Validate Verifies that you have provided valid file names & paths
Transform Processes the XML file into an HTML file, using the rules in your XSL file
Display Displays the file specified by HtmlFile using the default application for that file type

XslTransformer's .NET Interface

Here's the interface as seen by a .NET client. The GUIDs and the attributes such as [DispId(0), Description("XslTransformer description")] are processed by the C# compiler to create the COM TLB. They are not used by a .NET client. If you want to create a .NET-only client that is not COM-compatible you would omit those attributes …

namespace XslTransformer
{
   ////////////////////////////////////////////////////////////////////////////////////////////////////
   /// <summary>   The XslTransformer.Engine processes an XML data file
   ///             using XSL rules to create an HTML output file.
   ///             <para>Usage: set the three file names, then call the Transform method.</para>.
   /// 			<para>The Engine will complain if either of the two input files does not exist,
   ///             or if the specified folder of the output file does not exist.</para>.
   ///             </summary>
   ////////////////////////////////////////////////////////////////////////////////////////////////////
    [Guid("4A15229C-0872-4beb-AEC5-1047DFD0C255"), Description("XslTransformer class interface")]
    public interface IEngine
    {
       /// <remarks>
       /// DispId(0) is default COM property
       /// </remarks>
        [DispId(0), Description("XslTransformer description")]
        String ToString { get; }
       ////////////////////////////////////////////////////////////////////////////////////////////////////
       /// <summary>   Gets or sets the input XML file </summary>
        [DispId(1), Description("XML File Name")]
        String XmlFile { get; set; }
       ////////////////////////////////////////////////////////////////////////////////////////////////////
       /// <summary>   Gets or sets the input XSL transform file </summary>
        [DispId(2), Description("XSL File Name")]
        String XslFile { get; set; }
       ////////////////////////////////////////////////////////////////////////////////////////////////////
       /// <summary>   Gets or sets the output HTML file </summary>
        [DispId(3), Description("HTML File Name")]
        String HtmlFile { get; set; }
       ////////////////////////////////////////////////////////////////////////////////////////////////////
       /// <summary>   Transforms the XML input using the XSL rules to create the HTML output </summary>
        [DispId(4), Description("HTML File Name")]
        bool Transform();
       ////////////////////////////////////////////////////////////////////////////////////////////////////
       /// <summary>  Displays the HTML output in the default web browser </summary>
        [DispId(5), Description("Displays the HTML output")]
        bool Display();
       ////////////////////////////////////////////////////////////////////////////////////////////////////
       /// <summary>  Verifies that necessary files exist and that the output folder exists.</summary>
       /// <remarks>If argument showAlert is true, pop a message box</remarks>
        [DispId(6), Description("Verify that XML and XSL files exist, and that the HTML folder exists")]
        bool Validate(bool showAlert);
    }
}

The attributes instruct Visual Studio to create the information required by COM that it stores in the application's Type Library (TLB) file. You can read more about the Type Library in the accompanying article.

XslTransformer Consumers

The examples below show how different languages can use the services of the XslTransformer. We show how to use it from …

Another approach for C++ programmers is to ignore COM and interface directly with the .NET library. We show two such projects in an article about C++ clients.

VBA Consumer

With the Visual Studio project is a VBA Client. The VBA Client is hosted in an Excel 2007 workbook. The workbook is a macro-enabled file. Open the workbook with Excel, then click the security button that lets you run VBA macros. Open the VBA IDE (press Alt-F11) to see the VBA modules stored in the workbook.

Use VBA's Tools|References menu to make a reference to the XslTransformer …

VBA IDE References dialog

Here's the VBA function that tests the XslTransformer …

Public Function TestXslTransformer(ByVal xml As String, ByVal xsl As String, ByVal html As String) As Boolean
    TestXslTransformer = False
    Dim oEngine                             As New XslTransformer.Engine
    oEngine.XmlFile = xml
    oEngine.XslFile = xsl
    oEngine.HtmlFile = html
    Debug.Print oEngine.ToString
    If (oEngine.Validate(True)) Then
        If (oEngine.Transform()) Then
            Debug.Print "Created output '" & html & "'"
            TestXslTransformer = True
        Else
            MsgBox "XslTransformer.Engine failed", vbCritical Or vbOKOnly, "XslTransformer.Engine"
        End If
    End If
    Set oEngine = Nothing
End Function

If you want to display a file using the default application for a file type, call the Display method …

Public Sub TestDisplay ByVal file As String) As Boolean
    Dim oEngine                             As New XslTransformer.Engine
    oEngine.HtmlFile = file
    oEngine.Display
    Set oEngine = Nothing
End Function

C++ Consumer

The key to using a COM server from C++ is Microsoft's #import directive …

#import <XslTransformer\XslTransformer.tlb>

As you can see, #import processes the COM server's Type Library (TLB). The TLB is a formal definition of an interface and #import is able to deduce the properties & methods of the interface. It creates a number of #defines, typedefs and implementation wrappers in header files. It creates a couple of header files XslTransformer.tlh and XslTransformer.tli.

XslTransformer.tlh contains definitions of C++ smart pointers to the classes provided by the COM server. They make it easy, by C++ standards, to use the COM interface with a syntax not dissimilar to VBA's …

// #import generates XslTransformer.tlh
#import <XslTransformer\XslTransformer.tlb>
// XslTransformer.tlh and XslTransformer.tli are included automatically.
// It contains the smart pointer definitions for our interface

// A smart pointer to the XslTransformer.Engine object
XslTransformer::IEnginePtr  spEngine;

In our C++ implementation file, we create a XslTransformer.Engine object. In this case, the creation is performed in the constructor of a C++ wrapper class …

spEngine.CreateInstance (__uuidof (XslTransformer::Engine);

Now we have a class instance, we can assign properties like this …

//	tstring is a typedef for std::string or std::wstring
void   XslTransformer::XmlFile  (tstring const&	xml)
{
    spEngine->XmlFile = xml.c_str ();
}

 … and invoke methods like this …

VARIANT_BOOL  XslTransformer::Transform  () const
{
    return spEngine->Transform ();
}

.NET Consumer

The key to using a server from C# or VB.NET is the interface, where we mean here the formal language keyword. All you need do is to add a reference to the .NET DLL in your client project. See the Visual Studio documentation for help on creating a reference …

.NET client reference

With that reference added, you can use public classes within its namespace. In this case, the public classes are those that provide the interface implementation. We can write code like this …

// User pressed the Display button on a form
private void cmdDisplay_Click(object sender, EventArgs e)
{
    //  Create a new XslTransformer.Engine
    XslTransformer.Engine engine = new XslTransformer.Engine();
    //  Assign a property from a text box on a form
    engine.HtmlFile = this.txtHtmlFile.Text;
    //  Invoke a method
    engine.Display();
}

The DLL looks, from the programmer's point of view, just like any other .NET DLL she might have written. The COM attributes in the server source code make no difference to a .NET consumer. For more detail, read the article writing a COM Server using .NET.

However, there is one additional benefit to a .NET client that we have not revealed hitherto. The interface may contain additional properties or methods that are not visible to COM. There are several reasons why you might have a .NET interface richer than the COM interface.

For example, a .NET client is able to pass .NET data types that are not available in other languages. VBA and COM do not support the 64-bit integer data type (a Long in VBA is a 32-bit integer), whereas C# does. You might have a .NET property, invisible to COM, that gets and sets a 64-bit integer.

Here's the .NET extra: a COM-invisible property to get/set a string DotNetString. First, inside the DLL server interface definition …

String DotNetString { get; set; }

Second, the DLL server interface implementation. Note the attribute that makes this property invisible to COM …

[ComVisible(false), Description("Property visible only to .NET")]
public String DotNetString { get; set; }

This screenshot shows the client using our .NET server DLL. When we use the .NET-only property, the client pops a MessageBox to inform us …

.NET client dialog

Download

Download

You can download the XslTransformer Visual Studio solution. It requires Visual Studio 2008 or later. The solution includes several projects …

  1. The XslTransformer project that creates the DLL
  2. A C#.NET client project
  3. A VBA client implemented in an Excel workbook
  4. A C++/CLR client project
  5. A C++ client project with two components …
    1. A C++/CLR wrapper library around the DLL interface
    2. A standard C++ client

The first three projects are described above. The last two projects are described here.

Download the ZIP archive and unpack it to a suitable location in your Visual Studio project folder. Build the solution and run the various clients to verify that it works. Open the Excel workbook, acknowledge the security check that enables macros to work, then view & test the VBA client.


Return to .NET articles index.