C++ at Work

Writing, Loading, and Accessing Plug-Ins

Paul DiLascia

Code download available at:CatWork0510.exe(273 KB)

Q In the January 2005 issue of MSDN Magazine you had an example of writing a mixed-mode application. Is it possible to dynamically load a .NET class/DLL and call those functions? Let's say I had an application that was a native C++ app and I wanted to allow users to write a plug-in for the C++ application in .NET. It would be like a LoadLibrary for .NET DLLs.

Q In the January 2005 issue of MSDN Magazine you had an example of writing a mixed-mode application. Is it possible to dynamically load a .NET class/DLL and call those functions? Let's say I had an application that was a native C++ app and I wanted to allow users to write a plug-in for the C++ application in .NET. It would be like a LoadLibrary for .NET DLLs.

Ravi Singh

Q I'm working on an existing Visual C++® 6.0 application that has a plug-in architecture supporting DLLs which export and accept pure virtual interface pointers. After loading the DLL, the EXE calls an exported C function in the DLL, which returns a pointer to a pure virtual interface. The EXE then calls methods on the interface, sometimes passing back a pointer to another interface for the DLL to manipulate.

Q I'm working on an existing Visual C++® 6.0 application that has a plug-in architecture supporting DLLs which export and accept pure virtual interface pointers. After loading the DLL, the EXE calls an exported C function in the DLL, which returns a pointer to a pure virtual interface. The EXE then calls methods on the interface, sometimes passing back a pointer to another interface for the DLL to manipulate.

Now people writing the plug-ins have asked to use C#, Visual Basic® .NET, and other languages. I have little experience with .NET-based programming and the issues around allowing managed and unmanaged code to communicate, and the more information I find about it the less clear it becomes. How can I let customers write plug-ins in .NET-based languages?

Daniel Godson

A In the October 2003 issue of MSDN®Magazine, Jason Clark wrote an article about plug-ins, but I don't mind revisiting the topic here, especially since plug-ins are something the .NET Framework is especially good for (see Plug-Ins: Let Users Add Functionality to Your .NET Applications with Macros and Plug-Ins). After all, one of the main purposes of the Microsoft .NET Framework is to provide a language-independent system for writing reusable software components. This has been the software Holy Grail ever since the first "Hello, world" program. Reusability evolved from copy/paste to subroutines to static link libraries to DLLs to specializations like VBX, OCX, and COM. While the last three are variations on a theme (they're all native DLLs), the .NET Framework marks a true departure because all code is compiled into Microsoft® intermediate language (MSIL). Interoperability is inherent because at the common language runtime (CLR) level, all code is the same. This makes it especially easy to write apps that support a language-neutral plug-in architecture.

A In the October 2003 issue of MSDN®Magazine, Jason Clark wrote an article about plug-ins, but I don't mind revisiting the topic here, especially since plug-ins are something the .NET Framework is especially good for (see Plug-Ins: Let Users Add Functionality to Your .NET Applications with Macros and Plug-Ins). After all, one of the main purposes of the Microsoft .NET Framework is to provide a language-independent system for writing reusable software components. This has been the software Holy Grail ever since the first "Hello, world" program. Reusability evolved from copy/paste to subroutines to static link libraries to DLLs to specializations like VBX, OCX, and COM. While the last three are variations on a theme (they're all native DLLs), the .NET Framework marks a true departure because all code is compiled into Microsoft® intermediate language (MSIL). Interoperability is inherent because at the common language runtime (CLR) level, all code is the same. This makes it especially easy to write apps that support a language-neutral plug-in architecture.

So how can you take advantage of this in your C++ app? Daniel's virtual function pointer system sounds like a roll-your-own COM. That's what COM objects essentially are: pure virtual function pointers. You can use COM for your plug-in model, and developers can write plug-ins in any .NET-targeted language since the Framework lets you both create and consume COM objects. But COM coding is notoriously burdensome because it requires lots of chores like registration, reference counting, type libraries, and so on—enough to make you think COM stands for Cumbersome Object Model. If you're writing new code and you want to simplify your life, you can implement a direct plug-in model using .NET, as I'll now describe.

But first let me answer Ravi's question, which is related: is there something analogous to LoadLibrary in .NET? Yes, you can load any Framework assembly (a DLL that contains .NET classes) using the static method System::Assembly::Load. Moreover, .NET supports reflection. Each Assembly provides all the information you need about what classes, methods, and interfaces it provides. There's no need for GUIDs, registration, reference counting, or any of that stuff.

I'll start with a simple example before I show you a more general plug-in system. Figure 1 shows a C# class that exposes a static function SayHello. Note that in .NET, unlike C/C++, there's no such thing as a lone extern function; every function must belong to some class, though it can be static, which means it doesn't require an instance. To compile MyLib.cs into a library, you would type the following:

csc /target:library MyLib.cs

The compiler produces a .NET assembly called MyLib.dll. To call SayHello from C++ using the managed extensions, you'd write:

#using <mscorlib.dll> #using <MyLib.dll> using namespace MyLib; void main () { MyClass::SayHello("test1"); }

Figure 1 MyLib.cs

using System; using System.Reflection; using System.Runtime.CompilerServices; namespace MyLib { // Class that exports a single static function public class MyClass { public MyClass(){} static public void SayHello(String who) { Console.WriteLine("Hello, world, from {0}", who); } } }

The compiler links to MyLib.dll and calls the right entry point. This is all very straightforward, .NET 101. But now suppose you don't want to link MyLib at compile time, you want to link on the fly, as with LoadLibrary in C/C++. After all, the whole idea with plug-ins is they get linked at run time, after you've already built and shipped your app. Figure 2 does the same thing as the preceding snippet, but loads MyLib dynamically. The key function is Assembly::Load. Once you've loaded the assembly, you can call Assembly::GetType to get Type information about a class (note that you need the fully qualified namespace/class name), and with that you can call Type::GetMethod to get information about a method, or even call it, like so:

MethodInfo* m = ...; // get it String* args[] = {"Test2"}; m->Invoke(NULL, args);

Figure 2 Loading MyLib Dynamically

#using <mscorlib.dll> #using <System.dll> #include <vcclr.h> #include <stdio.h> #include <tchar.h> using namespace System; using namespace System::Reflection; void main () { try { // Load library dynamically: Assembly* a = Assembly::Load("MyLib"); if (a) { Console::WriteLine("Assembly = {0}", a); Type* t = a->GetType("MyLib.MyClass"); if (t) { Console::WriteLine("Type = {0}", t); MethodInfo* m = t->GetMethod("SayHello"); if (m) { Console::WriteLine("Method = {0}\n", m); String* args[] = {"Test2"}; m->Invoke(NULL, args); } else { printf("Can't find SayHello!\n"); } } else { printf("Can't find MyLib.MyClass!\n"); } } else { printf("Can't load MyLib!\n"); } } catch (Exception* e) { Console::WriteLine("*** Oops: Exception occurred: {0}", e); } }

The first parameter is the object instance (which is NULL in this case, since SayHello is static); the second is an array of Objects, the method parameters. Got it?

Now before moving on, I have to point out that there are several different Load functions available, and this is where life can get confusing. One problem the .NET Framework was designed to solve is the so-called DLL Hell problem, which arises when several apps share a common DLL and you want to upgrade the DLL—which could break some apps. With .NET, different apps can load different versions of the same assembly/DLL. Unfortunately, DLL Hell has now become Load Hell, because the rules for loading assemblies are so complex I could write a whole column on it.

The process of loading and binding assemblies to your app is called fusion, and there's even a special program included with the Framework, fuslogvw.exe (Fusion Log Viewer), that you can use to figure out which versions of which assemblies get loaded. As I said, it would take several pages to fully describe how the Framework loads and binds assemblies, and how it defines "identity" (when one assembly is the same as another). But for the purposes of plug-ins, there are two functions to consider: Assembly::Load and Assembly::LoadFrom.

Assembly::Load takes a full or partial name (for example, "MyLib" or "MyLib, Version=xxx Culture=xxx"). The test program in Figure 2 loads "MyLib", then displays the full assembly name, as shown in Figure 3. Assembly::Load uses the Framework's discovery rules to decide which file to actually load. It looks in the Global Assembly Cache (GAC), the path in your app's manifest, the directory where the app lives, and so on.

Figure 3 Test Program

The other function, Assembly::LoadFrom, lets you load an assembly from an explicit path name. The confusing thing here is that if the same assembly (as determined by the rules of identity) has already been loaded from a different path, the Framework will use that. So LoadFrom doesn't always use exactly the assembly specified by the path name, though most times it does. Confused? There's yet another method, Assembly::LoadFile, that always loads the path requested—but you should almost never use LoadFile because it doesn't resolve dependencies and doesn't load the assembly into the appropriate (LoadFrom) context. Without going into all the details, I'll simply state that LoadFrom is a good function to use for a simple plug-in model.

The basic idea for such a model is to define an interface and then let other folks write classes that implement it. Your app can call Assembly::LoadFrom to load the plug-ins, and use reflection to find classes that implement your interface. But before you go running to the keyboard, there are two important questions to ask. Does your app need to unload or reload plug-ins while it's running? Does your app need to control the security access that plug-ins have to files and other resources? If you answer yes to either question, you'll need AppDomains.

In the .NET Framework, there's no way to directly unload an assembly. The only way is to load the assembly into a separate AppDomain, then unload the whole AppDomain. Each AppDomain can also have its own security permissions. AppDomains provide a processing unit with the isolation usually afforded by separate processes, without the overhead, and multiple AppDomains can exist within the same process. AppDomains are typically used in server apps, where the server runs 24/7 or nearly so, and needs to load and unload components on the fly without restarting. AppDomains are also used to limit the permission that plug-ins are granted, so that an application can load untrusted components without fear of them doing something malevolent. To enable this isolation, using multiple AppDomains requires remoting; objects in different AppDomains can't call each other directly, they must be marshaled across the AppDomain boundary. In particular, shared instances of classes must derive from MarshalByRefObject.

That's all I'll say about AppDomains for now. Next I'll describe a very simple plug-in model that works without AppDomains. Say you've built an image editor and you want to let other developers write plug-ins to perform special effects like solarize, blur, or making every other pixel green. Whatever. Or say you have a proprietary database and you want to let developers write specialized import/export filters to convert between your data and their own custom file formats. In these scenarios, the application loads all the plug-ins when it starts, and the plug-ins remain loaded forever. That is, until the user quits the program. This model doesn't require the reload capability of a server app, and the plug-ins can have the same security permissions as the application itself. So there's no need for AppDomains; all the plug-ins can be loaded into the main application domain. This is the typical scenario for a desktop application.

To implement this model in practice, first define the interface each plug-in must implement. An interface is more or less what it is in COM, an abstract base class that defines what properties and methods plug-ins must implement. By way of example, I wrote an extensible text editor called PGEdit with a plug-in interface ITextPlugin (see Figure 4). ITextPlugin has two properties, MenuName and MenuPrompt, and a Transform method that takes a string, does something to it, and returns the new result. I implemented three concrete plug-ins for PGEdit: PluginCaps, PluginLower, and PluginScramble to capitalize, lowercase, or scramble the text. Figure 5 shows PGEdit with the three plug-ins added to its Edit menu.

Figure 4 ITextPlugin

#pragma once using namespace System; using namespace System::ComponentModel; namespace TextPlugin { // plug-in interface definition public __gc __interface ITextPlugin { [ReadOnly(true)] __property String* get_MenuName(); [ReadOnly(true)] __property String* get_MenuPrompt(); String* Transform(String* text); }; }

Figure 5 PGEdit with Three Plug-Ins

Figure 5** PGEdit with Three Plug-Ins **

I wrote a class called CPluginMgr that manages the plug-ins (see Figure 6). PGEdit calls CPluginMgr::LoadAll to load all the plug-ins when the app starts:

BOOL CMyApp::InitInstance() { ... m_plugins.LoadAll(__typeof(ITextPlugin)); }

Here m_plug-ins is a CPluginMgr. The constructor takes a subdirectory name (the default is "Plugins"); LoadAll searches this folder for assemblies with classes that implement the requested interface. When it finds one, CPluginMgr creates an instance of the class and adds it to a list (STL vector). Here's the key code snippet:

for (/* each type in assembly*/) { if (iface->IsAssignableFrom(type)) { {Object* obj = Activator::CreateInstance(type); m_objects.push_back(obj); count++; } }

Figure 6 PluginMgr

PluginMgr.h

#pragma once #include <vector> using namespace std; using namespace System; using namespace System::Collections; // STL vector of managed Objects, wrapped as gcroot handle typedef vector <gcroot<Object*> > PLUGINLIST; //////////////// // .NET Plug-in Manager. This class will load all the DLLs in a folder, // looking for assemblies that contain classes that implement a specific // interface, and will instantiate any such classes it finds, adding them // to a list (STL vector). Note this is a native class, which is why I // have to use gcroot, because a native class can't hold pointers to // managed objects. // class CPluginMgr { public: CPluginMgr(LPCTSTR dir=NULL); virtual ~CPluginMgr(); PLUGINLIST m_objects; // list (vector) of plug-in objects // load all DLLs that implement given interface. int LoadAll(Type* iface, int nReserve=10); // Get ith plug-in Object* CPluginMgr::GetPlugin(int i) { return m_objects[i]; } // ditto, using [] Object* operator[](int i) { return GetPlugin(i); } protected: // helper: load single plug-in int LoadPlugin(Type* iface, LPCTSTR pathname); CString m_dir; // plug-in directory where DLLs are };

PluginMgr.cpp

#include "stdafx.h" #include "PluginMgr.h" using namespace System; using namespace System::Reflection; CPluginMgr::CPluginMgr(LPCTSTR dir) : m_dir(dir) { if (m_dir.IsEmpty()) { // default plug-in directory is exedir/PlugIns, where exedir is the // folder containing the executable LPTSTR buf = m_dir.GetBuffer(MAX_PATH); // buffer in which to copy GetModuleFileName(NULL, buf, MAX_PATH); // exe path PathRemoveFileSpec(buf); // remove file name part m_dir.ReleaseBuffer(); // free buffer m_dir += _T("\\PlugIns"); // append "PlugIns" } } CPluginMgr::~CPluginMgr() { } ////////////////// // Load and instantiate all plug-ins that implement a given interface. Note // this will load all DLLs in the plug-in directory, even ones that don't // implement the interface—there's no way to unload an Assembly w/o using // AppDomains. // int CPluginMgr::LoadAll(Type* iface, int nReserve) { ASSERT(iface); ASSERT(iface->IsInterface); m_objects.reserve(nReserve); // for efficiency // Use MFC to find *.dll in the plug-ins directory, and load each one CFileFind libs; CString spec; spec.Format(_T("%s\\*.dll"), m_dir); TRACE(_T("Loading %s\n"), spec); BOOL bMore = libs.FindFile(spec); while (bMore) { bMore = libs.FindNextFile(); LoadPlugin(iface, libs.GetFilePath()); } TRACE(_T("%d plugins found\n"), m_objects.size()); return m_objects.size(); } ////////////////// // Load single DLL file, looking for given interface // int CPluginMgr::LoadPlugin(Type* iface, LPCTSTR pathname) { int count=0; try { Assembly * a = Assembly::LoadFrom(pathname); Type* types[] = a->GetTypes(); for (int i=0; i<types->Length; i++) { Type *type = types[i]; if (iface->IsAssignableFrom(type)) { TRACE(_T("Found type %s in %s\n"), CString(type->Name), pathname); Object* obj = Activator::CreateInstance(type); m_objects.push_back(obj); count++; } } } catch (Exception* e) { TRACE(_T("*** Exception %s loading %s, ignoring\n"), CString(e->ToString()), pathname); } if (!count) TRACE(_T("*** Didn't find %s in %s\n"), CString(iface->Name), pathname); return count; }

In other words, if the type can be assigned to ITextPlugin, CPluginMgr creates an instance and adds it to the array. Since CPluginMgr is a native class, it can't hold managed objects directly, so the array m_objects is actually an array of gcroot<Object*>'s. If you're using the new C++ syntax in Visual C++ 2005, use Object^ instead. Note that CPluginMgr is generic; it can support any interface you design. Just instantiate and call LoadAll, and you'll end up with an array of plug-in objects. CPluginMgr reports the plug-ins it finds in the TRACE stream. If you have multiple plug-in interfaces, you probably should use a separate instance of CPluginMgr for each interface, to keep the plug-ins separate.

On a performance note, Joel Pobar from the CLR team wrote a terrific article for the July 2005 issue of MSDN Magazine (Reflection: Dodge Common Performance Pitfalls to Craft Speedy Applications) in which he discusses best practices for using reflection. He suggests using assembly-level attributes to specify which types in the assembly implement the plug-in interface. This allows the plug-in manager to quickly look up and instantiate the plug-ins rather than having to loop through each type in the assembly, an expensive operation if there are many types present. If you find that the code in this column for loading plug-ins gives poor performance for your situation, you should consider altering it to support Joel's recommendations. For general purposes, however, this code is more than adequate.

Once you've loaded the plug-ins, how do you use them? It depends on your application, but typically you'll have some code that looks like the following:

PLUGINLIST& pl = theApp.m_plugins.m_objects; for (PLUGINLIST::iterator it = pl.begin(); it!=pl.end(); it++) { Object* obj = *it; ITextPlugin* plugin = dynamic_cast<ITextPlugin*>(obj); plugin->DoSomething(); } }

(PLUGINLIST is a typedef for vector<gcroot<Object*>>.) PGEdit's CMainFrame::OnCreate function has a loop like this that appends each plug-in's MenuName to its Edit menu. CMainFrame assigns command IDs starting at IDC_PLUGIN_BASE. Figure 7 shows how the view uses ON_COMMAND_RANGE to handle the commands. You can download the source from the MSDN Magazine Web site for details.

void CMyView::OnPluginCmdUI(CCmdUI* pCmdUI) { CEdit& edit = GetEditCtrl(); int begin,end; edit.GetSel(begin,end); pCmdUI->Enable(begin!=end); }

Figure 7 Using ON_COMMAND_RANGE

#include "StdAfx.h" #include "View.h" #include "PGEdit.h" #using <TextPlugin.dll> using namespace TextPlugin; IMPLEMENT_DYNCREATE(CMyView, CEditView) BEGIN_MESSAGE_MAP(CMyView, CEditView) ON_COMMAND_RANGE(IDC_PLUGIN_BASE, IDC_PLUGIN_END, OnPluginCmd) ON_UPDATE_COMMAND_UI_RANGE(IDC_PLUGIN_BASE, IDC_PLUGIN_END, OnPluginCmdUI) END_MESSAGE_MAP() void CMyView::OnPluginCmd(UINT id) { CEdit& edit = GetEditCtrl(); int begin,end; edit.GetSel(begin,end); if (end>begin) { Object* obj = theApp.m_plugins.GetPlugin(id - IDC_PLUGIN_BASE); ASSERT(obj); ITextPlugin* plugin = dynamic_cast<ITextPlugin*>(obj); if (plugin) { CString text; edit.GetWindowText(text); text = text.Mid(begin, end-begin); text = plugin->Transform(text); edit.ReplaceSel(text); edit.SetSel(begin,end); } } }

I've shown how PGEdit loads and accesses the plug-ins, but how do you implement a plug-in? That's the easy part. You first generate an assembly that defines the interface—in this case, TextPlugin.dll. This assembly doesn't implement any code or classes, it merely defines the interface. Remember, .NET is language neutral so there's no source code, nothing analogous to C++ header files. Instead, you generate an assembly that defines the interface and distribute it to the developers who write the plug-ins. Plug-ins link with this assembly so they can derive from your interface. For example, see the following code in C#:

using TextPlugin; public class MyPlugin : ITextPlugin { ... // implement ITextPlugin }

Figure 8 shows the PluginCaps plug-in, written in C#. As you can see, it's quite simple. For all the details, download the source and build the solution PGEdit.sln.

Figure 8 CapsPlugin

using System; using System.Reflection; using System.Runtime.CompilerServices; using TextPlugin; public class CapsPlugin : ITextPlugin { public CapsPlugin() {} public String MenuName { get { return "Uppercase"; } } public String MenuPrompt { get { return "Convert selected text to ALL UPPERCASE"; } } public String Transform(String text) { return text.ToUpper(); } }

Happy programming!

Send your questions and comments for Paul to  cppqa@microsoft.com.

Paul DiLascia is a freelance software consultant and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). In his spare time, Paul develops PixieLib, an MFC class library available from his Web site, www.dilascia.com.