CodeProject上的文章,是外网,可能看起来不方便。 Plugins are the common way for extending applications. They are usually implemented as DLLs. The host application locates the plugins (either by looking in a predefined folder, or by some sort of registry setting or configuration file) then loads them one by one with This article will show how to create a host EXE with multiple plugin DLLs. We'll see how to seamlessly expose any of the host's classes, functions and data as an API to the plugins. There will be some technical challenges that we are going to solve along the way. We'll use a simple example. The host application 1) The plugin implements a standard (and usually small) set of functions. The host knows the names of the functions and can find the address using 2) The function returned by Interfaces are base classes where all member functions are public and pure virtual, and there are no data members. For example: The host keeps a list of all registered parsers. It adds the pointers returned by When the host needs to parse a BMP file it will call Another limitation of this approach is that you cannot expose any global data or global functions from the host to the plugins. Take a look at the USER32 module. It has 2 parts – This time instead of the host calling a function to get the parser and add it to the list, that part is taken over by the constructor of On the plugin side: For complete sources see the DLL+EXE folder in the download file. Usually an import library is created only when making DLLs. It is a little known trick that import library can be created even for EXEs. In Visual C++ 6 the ........ incomplete
本来是要下Visual Leak Detector才到CodeProject上注册的,后来订阅了 RSS,还不错Introduction
LoadLibrary
. The plugins are then integrated into the host application and extend it with new functionality. host.exe
is an image viewer. It implements a plugin framework for adding support for different image file formats (24-bit BMP and 24-bit TGA in this example). The plugins will be DLLs and will have extension .IMP (IMage Parser) to separate them from regular DLLs. Note however that this article is about plugins, not about parsing images. The provided parsers are very basic and for demonstration purpose only.
There are many articles describing how to implement a simple plugin framework. See [1], [2] for example. They usually focus on 2 approaches: GetProcAddress
. This doesn't scale well. As the number of functions grows the maintenance gets harder and harder. You can only do so much if you have to manually bind every function by name. GetProcAddress
is used to pass an interface pointer to the plugin or to obtain an interface pointer from the plugin. The rest of the communications between the host and the plugin is done through that interface. Here's how you do it: The interface way
// IImageParser is the interface that all image parsers
The actual image parsers inherit from the interface class and implement the pure virtual functions. The BMP plugin can look like this:
// must implement
class IImageParser
{
public:
// parses the image file and reads it into a HBITMAP
virtual HBITMAP ParseFile( const char *fname )=0;
// returns true if the file type is supported
virtual bool SupportsType( const char *type ) const=0;
};// CBMPParser implements the IImageParser interface
The host will use
class CBMPParser: public IImageParser
{
public:
virtual HBITMAP ParseFile( const char *fname );
virtual bool SupportsType( const char *type ) const;
private:
HBITMAP CreateBitmap( int width, int height, void **data );
};
static CBMPParser g_BMPParser;
// The host calls this function to get access to the
// image parser
extern "C" __declspec(dllexport) IImageParser *GetParser( void )
{
return &g_BMPParser;
}LoadLibrary
to load BmpParser.imp
, then use GetProcAddress("GetParser")
to find the address of the GetParser
function, then call it to get the IImageParser
pointer. GetParser
to that list.SupportsType(".BMP")
for each parser. If SupportsType
returns true
, the host will call ParseFile
with the full file name and will draw the HBITMAP
.
For complete sources see the Interface folder in the download file.
The base class doesn't really have to be pure interface. Technically the constraint here is that all members have to be accessible through the object's pointer. So you can have:
- pure virtual member functions (they are accessed indirectly through the virtual table)
- data members (they are accessed through the object's pointer directly)
- inline member functions (they are not technically accessed through the pointer, but their code is instantiated a second time in the plugin)
That leaves non-inline and static member functions. The plugin cannot access such functions from the host and the host cannot access such functions from the plugin. Unfortunately in a large application such functions can be the majority of the code.
For example all image parsers need the CreateBitmap
function. It makes sense for it to be declared in the base class and implemented on the host side. Otherwise each parser DLL will have a copy of that function.
So how can we improve this? Split the host into DLL and EXE
user32.dll
and user32.lib
. The real code and data is in the DLL, and the LIB just provides placeholder functions that call into the DLL. The best part is that all this happens automatically. You link with user32.lib
and automatically gain access to all functionality in user32.dll
.
MFC goes a step further – it exposes whole classes that you can use directly or inherit. They do not have the limitations of the pure interface classes we discussed above.
We can do the same thing. Any base functionality you want to provide to the plugins can be put in a single DLL. Use the /IMPLIB
linker option to create the corresponding LIB file. The plugins can then link with that library, and all exported functionality will be available to them. You can split the code between the DLL and the EXE any way you wish. In the extreme case shown in the sources the EXE only contains a one line WinMain function whose only job is to start the DLL.
Any global data, functions, classes, or member functions you wish to export must be marked as __declspec(dllexport)
when compiling the DLL and as __declspec(dllimport)
when compiling the plugins. A common trick is to use a macro:#ifdef COMPILE_HOST
Add
// when the host is compiling
#define HOSTAPI __declspec(dllexport)
#else
// when the plugins are compiling
#define HOSTAPI __declspec(dllimport)
#endifCOMPILE_HOST
to the defines of the DLL project, but not to the plugin projects.
On the host DLL side:// CImageParser is the base class that all image parsers
Now the base class is not constrained of being just an interface. We are able to add more of the base functionality there.
// must inherit
class CImageParser
{
public:
// adds the parser to the parsers list
HOSTAPI CImageParser( void );
// parses the image file and reads it into a HBITMAP
virtual HBITMAP ParseFile( const char *fname )=0;
// returns true if the file type is supported
virtual bool SupportsType( const char *type ) const=0;
protected:
HOSTAPI HBITMAP CreateBitmap( int width, int height,
void **data );
};CreateBitmap
will be shared between all parsers. CImageParser
. When the parser object is created its constructor will automatically update the list. The host doesn't need to use GetProcAddress
to see what parser is in each DLL any more.// CBMPParser inherits from CImageParser
When
class CBMPParser: public CImageParser
{
public:
virtual HBITMAP ParseFile( const char *fname );
virtual bool SupportsType( const char *type ) const;
};
static CBMPParser g_BMPParser;g_BMPParser
is created its constructor CBMPParser()
will be called. That constructor (implemented on the plugin side) will call the constructor of the base class CImageParser()
(implemented on the host side). That's possible because the base constructor is marked as HOSTAPI
.
Wait, it gets even better: Combine the host DLL and the host EXE
/IMPLIB
option is not available directly for EXEs as it is for DLLs. You have to add it manually to the edit box at the bottom of the Link properties. In Visual Studio 2003 it is available in the Linker\Advanced section, you just have to set its value to $(IntDir)/Host.lib
.
So there you go. You have a host EXE, a number of plugin DLLs, and you can share any function, class or global data in the host with all plugins. There is no need to use GetProcAddress
at all, ever, since the plugins can register themselves with the host's data structures.
For complete sources see the EXE folder in the download file.
2007年2月24日
插件系统-选择GetProcAddress还是Interfaces(原文)
订阅:
博文评论 (Atom)
没有评论:
发表评论