This article is written for an audience of C++ programmers. Specifically, C++ programmers wanting to write a client that uses a DLL written in a .NET language.
We discuss here two approaches to writing a C++ client using Visual Studio …
Scroll to the end of this article if you want to download the example project.
![]() |
A third approach using COM is
discussed elsewhere.
If you
create a COM Server
using Visual Studio, you can
use its functionality via the Microsoft C++ #import directive.
A simple .NET DLL project is the XSL Transformer. This project is a DLL having both a .NET and a COM interface. The C++ clients discussed here use only the .NET interface.
A C++ project compiled with the /CLR switch can use .NET CLR assemblies.
The technology for this approach is Microsoft-specific.
If that doesn't concern you, then the C++/CLR route is simple to implement and has little overhead.
Here's the source code of the C++/CLR client …
using namespace System;
using namespace XslTransformer;
int main(array<System::String ^> ^args)
{
if (2 < args->Length)
{
Console::WriteLine (L"XSL Transformer C++ Client xml: {0} xsl:{1} html:{2}", args [0], args [1], args [2]);
XslTransformer::Engine^ engine = gcnew XslTransformer::Engine ();
engine->XmlFile = args [0];
engine->XslFile = args [1];
engine->HtmlFile = args [2];
if (engine->Validate (true))
{
if (engine->Transform ())
{
engine->Display ();
}
}
}
else
{
Console::WriteLine(L"XSL Transformer: insufficent args. Usage: CPlusPlusClient [in] xml, [in] xsl, [out] html");
}
return 0;
}
A C++ project compiled without the /CLR switch can not use .NET CLR assemblies.
However, you may want to use exactly such a project to avoid clashes between the
CLR and other standard C++ technology.
The solution shown here, and illustrated above, uses a double pointer-to-implementation (PIMPL) idiom …
Acknowledgement: the approach described here was published by Jim Dill in his article Calling .NET from Unmanaged C++.
The PIMPL idiom lets us hide the CLR details from the C++ client.
That is, the client is plain C++ and is compiled without the /CLR switch …
/CLR switch.
it provides
Here's the wrapper header (.h) file seen by the C++ client …
// XslTransformerWrapper.h // Forward declaration of our PIMPL exposes no internals class XslTransformerWrapperImpl; class XslTransformerWrapper { XslTransformerWrapperImpl* pimpl_; public: // Get/Set properties CString XmlFile () const; void XmlFile (CString const& xml); CString XslFile () const; void XslFile (CString const& xsl); CString HtmlFile () const; void HtmlFile (CString const& html); CString DotNetString () const; void DotNetString (CString const& html); // Methods bool Validate (bool alert = false) const; bool Display () const; bool Transform () const; // Construction XslTransformerWrapper (); ~XslTransformerWrapper (); };
Here's the wrapper implementation (.cpp) file.
It is not seen by the C++ client and is compiled with the /CLR switch …
// XslTransformerWrapper.cpp #include "XslTransformerWrapper.h" #include <vcclr.h> // Microsoft CLR extensions for Visual C++ using namespace System; using namespace XslTransformer; //////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Xsl Transformer façade implementation class. /// The class definition and implementation are both in this file.</summary> //////////////////////////////////////////////////////////////////////////////////////////////////// class XslTransformerWrapperImpl { // ^ (CLR handle) is a Microsoft C++ extension gcroot<XslTransformer::Engine ^> engine_; public: //////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Pass-through methods </summary> //////////////////////////////////////////////////////////////////////////////////////////////////// bool Display () const { return engine_->Display (); } bool Transform () const { return engine_->Transform (); } bool Validate (bool alert) const { return engine_->Validate (alert); } //////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> Pass-through properties </summary> //////////////////////////////////////////////////////////////////////////////////////////////////// CString XmlFile () const; void XmlFile (CString const& s); CString XslFile () const; void XslFile (CString const& s); CString HtmlFile () const; void HtmlFile (CString const& s); CString DotNetString () const; void DotNetString (CString const& s); XslTransformerWrapperImpl () { // gcnew is a Microsoft C++ extension engine_ = gcnew XslTransformer::Engine (); } ~XslTransformerWrapperImpl () { } }; //////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> The C++ wrapper class. </summary> //////////////////////////////////////////////////////////////////////////////////////////////////// XslTransformerWrapper::XslTransformerWrapper () : pimpl_ (new XslTransformerWrapperImpl ()) { // The Implemention class is constructed in the wrapper constructor } XslTransformerWrapper::~XslTransformerWrapper () { // The Implemention class is destroyed in the wrapper destructor delete pimpl_; } // Façade implementation of other properties elided for clarity …
The client of the wrapper solution is plain old C++. It doesn't use the CLR and knows nothing of it, because all it can see is the wrapper header file shown above.
Here's the C++ client implementation (.cpp) file …
// CPlusPlusSimpleClient.cpp #include <XslTransformerWrapper.h> #include <iostream> int _tmain(int argc, _TCHAR* argv[]) { enum StringControl { BufferSize = 128, }; TCHAR buffer [BufferSize]; _sntprintf_s (buffer, BufferSize, BufferSize, _T("%s: argc %d\n"), argv [0], argc); std::wcout << buffer; for (int i = 0; i != argc; ++i) { _sntprintf_s (buffer, BufferSize, BufferSize, _T("arg [%d] %s\n"), i, argv [i]); std::wcout << buffer; } XslTransformerWrapper wrapper; if (3 < argc) { // With C++, argv [0] is the application name wrapper.XmlFile (argv [1]); wrapper.XslFile (argv [2]); wrapper.HtmlFile (argv [3]); if (wrapper.Validate (true) && wrapper.Transform ()) { wrapper.Display (); } } return 0; }
You can download the XslTransformer Visual Studio solution. It requires Visual Studio 2008 or later. The solution includes several projects …
The last two projects are described above. The first three 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.