Excel and You, Part II: .NET, C++/CLI, and Mixed-Mode DLLs
Last time, we looked at a straightforward way to create DLL “wrappers” allowing us to call libraries written in various languages from Excel and VBA, with a C++ library as our example. The general technique of providing works for any language or platform that supports an API for calling from C: O’Caml, Haskell, and Python, to name just a few.
Today I’d like to share some ideas for another common case: exposing functionality from a .NET library (assembly, really). The Microsoft .NET framework has seen remarkable uptake in the oil and gas industry and—regardless of its merits and the rumors of an impending return to a preferred native-application development model on Windows 8—it’s clearly here to stay for a while. The use of a platform providing automatic memory management and an “everything but the kitchen sink” library has considerable advantages for the development of many line-of-business and even engineering applications, and given the Microsoft monoculture we seem stuck with anyway, many companies in the industry have standardized on .NET (and C#, more often than not) as the only development platform for in-house application development.
Well, at least it’s not Java (kidding aside, while Java-the-language is dead and rotting, the JVM itself is the target platform for plenty of interesting projects). Let’s return to the case where we have some interesting functionality implemented in one or more .NET classes, compiled into a .NET assembly DLL. We may or may not have access to the (C#, VB.NET, …) source for the library, but the .DLL exposes all the metadata we’ll need to make use of it anyway. First, I’ll mention two solutions that I won’t elaborate on: one is prohibitively expensive, the other requires alteration to the library itself.
Microsoft Visual Studio Tools for Office (VSTO), which appears now to have simply been subsumed into certain editions of Visual Studio, is Microsoft’s recommended solution for creating Office add-ins in .NET. Presumably this includes some templates and tools that make this less difficult; in any case I’ve never had the opportunity to use it. Visual Studio is prohibitively expensive for a non-full-time software developer, and it’s simply unnecessary for most tasks: the .NET framework and Windows SDK are both available for free and contain all the compilers, linkers, headers, and libraries necessary to develop either native or .NET applications for Windows.
A more promising avenue is the .NET framework’s built-in capability for exposing types through COM. This can be done straightforwardly enough, but imposes constraints on the types to be exposed, requiring the library to have been built with COM interop in mind from the start. While a COM-visible type will provide trivial access to VBA (Visual Basic 6 essentially is COM reified as a language), this avenue may be untenable for a closed-source library, or one which simply can’t be altered to meet the prerequisites.
Love it or hate it (or more likely, never heard of it), C++/CLI is one of the most interesting things to come out of Microsoft in a while. Superseding the dead and little-mourned “Managed Extensions for C++”, C++/CLI takes a more radical approach to grafting the .NET Framework onto ISO C++. Syntactic extensions including new declarator syntax for GC’d handles and references to .NET types, “contextual keywords” like for each
, and even the now-standard nullptr
can make C++/CLI look like a completely new language, and some of the semantic additions—not to mention the “fast-track” ECMA standardization process—set C++ purists’ teeth on edge. All that aside, while I’d prefer to write ISO C++ (or Haskell, for that matter) given the chance, I think it’s a neat little tool for specific purposes that seems sadly to have fallen by the wayside at Microsoft.
While there doesn’t seem to be much of a future for C++/CLI, it’s still supported by the most recent Visual C++ compiler, and provides us with a nice way to expose arbitrary .NET types to native DLL clients such as Excel/VBA by creating mixed-mode assemblies. A mixed-mode assembly contains both .NET (managed) code and native code, allowing us to create a DLL which uses .NET types internally but exports a C-style interface for use by native clients.
Let’s assume we have a .NET assembly which exposes a type that does some interesting calculation which we want to expose. Here’s a C# sketch of the interface of this type:
namespace Util { public class CylonDetector : IDisposable { public CylonDetector() { ... } public bool isCylon(string name) { ... } public void Dispose() { // release resources---files, sockets, nuclear isotopes ... } }; }
Assuming this type is found in Util.dll
, we can write a C++/CLI header importing the type and describing a C-style interface to it like so:
// In CylonDetectorNative.h #using "Util.dll" #include "msclr\auto_gcroot.h" typedef msclr::auto_gcroot detector; extern "C" { detector* get_cylon_detector(); void release_cylon_detector(detector *det); bool is_cylon(detector *mdl, const char *name); }
We can’t use a CylonDetector^
handle as an opaque “token” to return to native clients, as we did a FluxCapacitor*
pointer in the last entry. Handles to CLR types on the .NET heaps aren’t simple integer “indices” like pointer but must track a single object across potential relocation due to garbage collection. As such, we’ll make use of the handy Microsoft-provided auto_gcroot<T>
class template. As with the more common gcroot<T>
template, auto_gcroot
allows us to instantiate a C++ type wrapping a .NET handle. Unlike ordinary gcroot
, its destructor will also invoke the appropriate Dispose
method for the object behind the wrapped handle, if any. Microsoft’s (minimal) documentation for auto_gcroot
can be found here, but its use is straightforward.
A simple implementation of the necessary glue code might look like:
// In CylonDetectorNative.cpp #include "CylonDetectorNative.h" detector* get_cylon_detector() { try { return new detector(gcnew Util::CylonDetector); } catch (...) { return nullptr; } } void release_cylon_detector(detector *det) { delete det; } bool is_cylon(detector *det, const char *name) { try { return det->get()->isCylon(gcnew System::String(name)); } catch (...) { // innocent until proven guilty... return false; } }
Note that we must use the get
member function of auto_gcroot
to acquire the underlying .NET handle (CylonDetector^
).
Compile and link the mixed-mode DLL using the /clr
switch to the MSVC compiler, provide the appropriate VBA Declare
statements as described last time, and we should be able to access our .NET type’s functionality from VBA, as below:
Public Sub UseDetector Dim detector As Long detector = get_cylon_detector() If is_cylon(detector, "Starbuck") Debug.Print "uh oh" End If release_cylon_detector detector End Sub
As before, I’ve left out some preprocessor noise related to managing DLL name export, calling convention, and so on. I’ll end the post with links to full versions of the C++/CLI header and implementation files as well as the VBA declarations, but I first want to share some key knowledge gained during an all-day troubleshooting session for a DLL created as described above.
Suffice it to say that I had created a C++/CLI wrapper for a type much like CylonDetector
, using the general technique described above. I compiled and linked a mixed-mode DLL, and was successfully able to link to and call the C-style exported interface from other C++/CLI code.
However, when I attempted to call my DLL functions from Excel, I was met with a rude surprise. On calling the equivalent of get_cylon_detector
, I received neither a valid nor a null pointer. Instead, Excel crashed. At first I suspected that the fault was to be found in the component actually provided by the .NET library (hint: it was MATLAB).
After reproducing the crash several times, each yielding a slightly different error dialog (and some yielding no error dialog at all), I finally received a Windows error reporting dialog which revealed the presence of an unhandled Windows (SEH—not C++ or .NET—) exception. Expecting to find that I was seeing an error inside the MATLAB runtime wrapped by the .NET library, I google’d the strange exception code 0xE0434F4D
and made a shocking discovery:
The Win32 exception was a generic wrapper for an underlying CLR (.NET) exception! But how could my code, which diligently caught any and all exceptions from the .NET library, be throwing a CLR exception? After a few minutes (well, more than a few) of consternation, I decided to attach to EXCEL.EXE in a debugger and attempt to reproduce the crash, hoping something would “pop out”.
And it did. An item in the output window at the time of the crash referenced an EEFileLoadException
. Another Google search led me directly to a StackOverflow page, and an answer. When a mixed-mode assembly needs to load a pure CLR assembly, the places it looks aren’t intuitively obvious, viz., they do not include the present working directory. The CLR exception thrown when loading failed wasn’t being caught because it didn’t conceptually occur inside any try-catch block, rather, my function get_cylon_detector
had been invisibly rewritten as:
detector* get_cylon_detector() { // not real code, may throw EEFileLoadException LoadManagedAssembly("Util.dll"); try { return new detector(gcnew Util::CylonDetector); } catch (...) { return nullptr; } }
As there was no way to catch the EEFileLoadException
, it was silently translated to a Win32 exception and passed on to Excel, resulting in disaster. The solution, as presented on the StackOverflow page, was to directly add the correct path to the .NET assembly resolution process. Full code will be presented below, but the get_cylon_detector
function ended up split into two parts to catch the potential “invisible” exception:
// "Invisible" // { // LoadManagedAssembly("Util.dll") static detector* real_initialize() { return new detector(gcnew Util::CylonDetector); } // } may throw EEFileLoadException DLL detector* DLLCALL get_cylon_detector() { try { System::AppDomain::CurrentDomain->AssemblyResolve += gcnew System::ResolveEventHandler( AssemblyResolver::MyResolveEventHandler); return real_initialize(); } catch (...) { return nullptr; } }
Finally, I’ve uploaded the full header, implementation, and declaration files for the CylonDetector
example. Note the trick used in CylonDetectorNative.h
to allow the header to be included from native C++ code to call the mixed-mode dll.
That’s all for yet another long post! Next time, hopefully something a little higher-level with less bit-slinging.