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.
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 …
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.
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 …
The XslTransformer.Engine class provides these properties and methods …
| 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) |
| 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 |
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.
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.
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 …
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
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 ();
}
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 …
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 …
You can download the XslTransformer Visual Studio solution. It requires Visual Studio 2008 or later. The solution includes several projects …
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.