Write, load, and access plug-ins (plug-ins)

Source: Internet
Author: User

Original: Paul dilascia
Translation: northtibet

Download source code: catwork0510.exe (273kb)
Source: writing, loading, and accessing plug-ins

In the msdn magazine article published in January 2005, you wrote a subprogram code in a hybrid mode. Is it possible to dynamically load. Net classes or DLL classes and call those functions? Suppose I have a local C ++ application, and I want to allow users to write plug-ins for the c ++ program in. net. Just like loading DLLs using loadlibrary in. net.

Ravi Singh

I am writing a plug-in application using Visual C ++ 6.0. It is a DLL that outputs and receives pure Virtual Interface pointers. After the DLL is loaded, the EXE calls the C function output in the DLL. This function returns a pure virtual interface pointer. Then, exe calls the method on this interface and sometimes returns another interface pointer to the DLL for processing.
Currently, some people require that you use C #, Visual Basic. net, and other languages to write plug-ins. I have no. Net-based programming experience and do not understand communication issues between managed and unmanaged code. I have found a lot of information about this, but the more I see it, the more confused. How can I allow users to write plug-ins Based on. NET language?

Daniel godson

In the October 2003 issue of msdn magazine, there was an article about plug-ins written by Jason Clark, but I don't mind reviewing this topic here, especially because plug-ins themselves are. net Framework (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 the compilation of reusable software components. From the first "Hello, world" program to the present, this has become the highest principle of software development. Reusability starts from copying/pasting to child routines, to static link libraries, to DLLs, and more professional vbx, ocx, and COM. Although the last three things belong to different themes (both of them are native DLLs), the. NET Framework marks a real beginning because all code is compiled into Microsoft's intermediate language (msil ). Interoperability becomes an indispensable component, because all code is the same at the runtime level of the public language. This makes it especially easy to write programs that support language-neutral plug-in architecture.
How can we take advantage of this advantage in your c ++ program? Daniel's virtual function pointer system is a self-made COM. It is the essence of the COM Object: pure virtual function pointer. You can use Com for the plug-in model. developers can write plug-ins in any language oriented to. net, because this framework allows you to create and use COM objects. But as we all know, com encoding is very complicated because it has many details to consider, such as registration, reference counting, type Library and so on-these are enough to make you think that com is "cumbersome object model" (troublesome object model ). If you are writing new code and trying to simplify your daily work, use. Net to directly implement a plug-in model. I am discussing this topic now.
First, let me answer Ray's question: In. is there anything similar to loadlibrary in net? The answer is: Yes. You can use the static method system: Assembly: load to load any framework assembly (that is, a inclusion. net class DLL ). In addition,. Net supports reflection. Each assembly provides all the information you need, such as the classes, methods, and interfaces of the Assembly. You do not need to care about such events as guids, registration, and reference count.
Before I show more general plug-in systems, I will start with a simple example. Figure 1 is a C # class that provides a static function sayhello. Note that, unlike C/C ++, functions are not output independently in. net. Each function must belong to a class, although this class can be static, that is, it does not need to be instantiated. To compile mylib. cs into a library, you can do this:

csc /target:library MyLib.cs

The compiler generates a. Net Assembly named mylib. dll. To call sayhello from C ++ through managed extensions, you have to write as follows:

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

The compiler links to mylib. dll and calls the correct entry point. All of this is simple and clear, and it belongs to the. NET Foundation. Now let's assume that you don't want to link mylib during compilation, but want to do dynamic link, just like using loadlibrary in C/C ++. After all, plug-ins only need to be linked at runtime after the application you have generated and delivered. What Figure 2 does is the same as the preceding code snippet, except that it dynamically loads mylib. The key function is Assembly: load. Once you load the assembly, you can call Assembly: GetType to obtain the type information about the class (note that you must provide a fully qualified namespace and class name), and then call type:: getmethod to obtain information about the method, or even call it, as shown in the following code:

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

The first parameter is the object instance (null in this example, because sayhello is static), and the second parameter is the object (object) array. Do you understand?
Before proceeding to the discussion, I must point out several load functions, which can easily confuse us .. Net is designed to solve the so-called DLL hell problem, this issue often occurs when several applications share a common DLL and want to update it-it can crash some applications. In. net, different applications can load different versions of the same assembly/DLL. Unfortunately, the DLL hell is now the load hell, because the rules for loading the Assembly are so complicated that I can write a column to describe it.
Compile and bind the program set to the process of your program as a fuse (fusion(, with a special framework, fuslogyw.exe (fusion Log Viewer) to do this. You can use it to determine which version of the Assembly is loaded. As I said, it takes several pages to completely describe how the framework loads and binds the Assembly and how it defines the identity. But for the plug-in, you only need to consider two functions: Assembly: load and assembly: loadfrom.
Assembly: The load parameter can be a complete or partial name (for example, "mylib" or "mylib version = xxx" Culture = xxx "). The test program in Figure 2 loads "mylib" and then displays the complete assembly name, as shown in Figure 3:


Figure 3 Test Procedure

Assembly: Load uses the framework discovery rules to determine which file is actually loaded. It is found in GAC (global assembly Buffer: Global Assembly Cache), the path provided by your program, the directory where the application is located, and the path like this.
Another function, assembly: loadfrom, enables you to load an assembly from an external path. Here, the obfuscation is that if the same assembly (determined by the identity rule) has been loaded from different paths, the framework will use it. Therefore, loadfrom does not always correctly use the Assembly specified through this path, although it can be used correctly most of the time. Dizzy? Another method is Assembly: LoadFile, which always loads the Request Path-but you almost never use LoadFile because it cannot solve the dependency problem, and cannot load the assembly to the correct environment (loadfrom ). I don't need to know all the details. I will briefly discuss loadfrom to illustrate that it is a useful function for a simple plug-in model.
The basic idea of such a model is to define an interface and then let others write classes that implement this interface. Your application can call Assembly: loadfrom to load the plug-in, and use reflection to find the class that implements the interface you define. However, before you start, you have two important questions: Do your applications need to uninstall or reload the plug-in during running? Does your program need to consider secure access to the files or other resources required by the plug-in? If both of your answers are yes, you will need an appdomain.
In the Framework, there is no way to directly uninstall an assembly. The only way is to load the assembly to a separate appdomain and then uninstall the entire appdomain. Each appdomain can also have its own security license. AppDomains comes with an isolated Processing Unit, which is usually controlled by a separate process. They are generally used in server programs. The servers basically run around the clock (24x7 ), you need to dynamically load and uninstall components without restarting them. AppDomains are also used to restrict the permission of the plug-in so that an application can load untrusted components without worrying about malicious behavior. To enable this isolation, you need a remote mechanism to use multiple AppDomains. different AppDomains cannot be called directly from each other. They must be sent across the appdomain boundary. In particular, shared instances of classes must be derived from financialbyrefobject.
This is the AppDomains that I want to talk about now. Next I will describe a very simple plug-in model, which does not require AppDomains. Suppose you have generated an image editor and want other developers to write plug-ins to implement effects such as exposure, blur, or make some pixels green. In addition, if you have database ownership, you want other developers to write specialized import/export filters to convert your data and their custom file formats. In this case, the application loads all the plug-ins at startup, and the plug-ins remain loaded until the user exits the program. This model does not require the server program to have the reload function, and the plug-in has the same security license as the application itself. So there is no need to use AppDomains; all plug-ins can be loaded into the main application domain. This is a typical usage mode for desktop applications.
To implement this model, you must first define the interfaces that each plug-in must implement. An interface is actually like a COM interface. It is an abstract base class that defines the attributes and methods required by the plug-in. In the example in this article, I wrote an extensible text editor named pgdit with a plug-in interface itextplugin (see figure 4 ). Itextplugin has two attributes: menuname, menuprompt, and transform. This method includes a string parameter, processes the passed strings, and then returns a new string. I have implemented three specific plug-ins for pgdit: plugincaps, pluginlower, and pluginscramble. Their functions are uppercase, lowercase, and disrupted text characters. As shown in figure 5, the three pgedit plug-ins are added to the edit menu.


Figure 5 with three plug-ins

I wrote a class named cpluginmgr, which is responsible for managing the plug-in (see figure 6 ). When pgedit is started, call cpluginmgr: loadall to load all plug-ins:

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

The m_plug-ins here is an instance of cpluginmgr. The constructor parameter is a subdirectory name (the default value is "plugins"). loadall searches for the folder to find the assembly, and the classes contained in the Assembly implement the requested interface. When it finds such an assembly, cpluginmgr creates an instance of this class and adds it to a list (STL vector ). The following is a key code snippet:

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

In other words, if type can be assigned to itextplugin, cpluginmgr creates an instance and adds it to an array. Because cpluginmgr is a local class, it cannot directly store hosted objects, so the array m_objects is actually an array of the gcroot <object *> type. If you use the new C ++ syntax in Visual C ++ 2005, you can use object ^ instead. Note that cpluginmgr is a common class that supports any interface you design. You only need to instantiate and call loadall, And you will eventually use an array of plug-in objects. Cpluginmgr reports the plug-ins it finds in the trace stream. If you have multiple interfaces, you may have to use a separate cpluginmgr instance for each interface to maintain isolation between plug-ins.
In terms of performance, the CLR team's Joel pobar wrote a horrible article (reflection: Dodge common performance pitfalls to craft speedy applications) in the July 2005 msdn magazine ), in this article, he discussed best practices for using reflection. It is recommended that you use the Assembly-level attributes to specify the type of the set that implements the plug-in interface. This allows the plug-in Manager to quickly find and instantiate the plug-in, instead of having to repeatedly search for each type in the Assembly. If there are too many types, it will be an expensive operation. If you find that the code in this column has poor performance when loading your own plug-ins, you should consider using the method recommended by Joel. But in general, this code is sufficient.
Once you load the plug-ins, how do you use them? This depends on your application. Generally, you will have some typical code 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 used for vector <gcroot <object *> ). The cmainframe: oncreate function of pgedit has a loop similar to this. Add the menuname of each plug-in to the edit menu of pgedit. Cmainframe specifies that the command IDs starts from idc_plugin_base. Figure 7 demonstrates how the view uses on_command_range to process commands. For details, download the source code.

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

I have already demonstrated how to load and access the pgeins for pgdit, but how do you implement the INS? That's easy. First, generate an assembly that defines the interface-in this example, textplugin. dll. This assembly does not implement any code or classes, but only defines interfaces. Remember,. NET is language neutral, so there is no source code, and it is completely different from the C ++ header file. Instead, you generate an assembly that defines the interface and distribute it to the developers who compile the plug-in. The plug-in is linked to the assembly, so they are derived from the interface you provide. For example, the following C # code:

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

Figure 8 shows the plugincaps plug-in written in C. As you can see, it is very simple. For more information, see the source code in this article.

Wish you a pleasant programming!

Your questions and comments can be sent to Paul's mailbox: cppqa@microsoft.com.
 

Author Profile
Paul dilascia
He is a freelance writer, software consultant, and designer of a large Web/UI. He is the author of the writing reusable windows code in C ++ book (Addison-Wesley, 1992. In his spare time he developed pixelib, an MFC class library that can be obtained from Paul's website http://www.dilascia.com.
This article is from the October 2005 Journal of msdn magazine and can be obtained through a local newsstand, or preferably subscribed

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.