DLL application - design can be switchable
Author: Cai Huanlin Date: Jan-2-2001
Summary: Introduction to DLL to cut an application, including the techniques of the interfacial design, and the design of Design Patterns to solve the problem.
Foreword
DLL (Dynamic Link Library, Dynamic Joint Funct Library) If there is no need for the technology, the book is written, and the document of Delphi can find DLL, less this article Not less, the reason why this article is written, on the one hand, it is a record of learning, on the one hand, it also provides a person in need; and the subject of this article - the design dynamic loaded module - It's just to provide a practical method in the DLL in the DLL, nevertheless, I still hope that you can find some more different things.
Since the existing documentation of the DLL has already been more duplicated, there is not much repetition, so some DLL's basics or DLL's writing experience is required, so that it will be relaxed. Some basic ideas are listed below with the following attention:
Differences in static links and dynamic links. Learn how to declare the DLL Output Functions and how to call them outside. Differences in various call conventions (CALLING Conventions). What is DLL Hell (1) and it affects the maintenance of the application.
DLL has the difference between static and dynamic load, so-called "static loaded DLL" means which one is determined in the compilation period, and will be loaded in the step initialization phase, Delphi VCL Packages is this class. Dynamic loaded DLL is loaded when it is required, and it is more troublesome than static loading, but more flexible, and the application starts faster.
What is discussed herein is to actually be switchable application modules with dynamic loaded DLL.
Cut application with DLL
In general, there are the following advantages to using the DLL:
Save memory. When multiple applications use the same DLL, the DLL will only be loaded once, or even loaded into the DLL and release it immediately. The program is reused to allow different programming languages. Application modularization, reduces the complexity of the application, which is more convenient when the program is maintained. Support for the design of multi-language. You can put a string resource for each language in a DLL, and you can dynamically switch the language used by the program.
But some difficulties must be overcome. When we want to cut the app into several DLL modules, it usually encounters the following questions:
How does DLL output (export) VCL object? How to put an FORM package in the DLL for external use? How to share variables between DLL?
Basically, if you write a Package, there is no problem, but you may encounter other troubles, such as the problem of name conflict, including the conflict of type, unit name, and letter name, before this I also tried to write a switchable module in a package, but the problem of the name conflict made me feel quite troubled. I also mentioned this in another document. The following text is from this text (2):
"After writing a few Package test, I still didn't use the package application in the actual project development, and still use the DLL, the most important reason, is Package is better than DLL - you can share variables. The intention of the item is very good, but it also brings another limit. It is mainly the problem of the name conflict, so that the shared Unit must be placed inside Package, otherwise, when two packages contain the same Unit, one is not available In addition, we feel that this will cause trouble. In addition, because other group members are not familiar with Package, it is prone to Trouble (for example: Project to join .dcp), this is one of the considerations. "Sharing between DLLs The problem of variables can be solved through the memory mapped file, and you can find relevant information in the referenced reference materials, here you don't have explained. When you output a VCL object (for example: string) in the DLL, you should pay attention to the following points:
The first unit in the USES clause in the DLL and its user's terms must be ShareMem. Borlndmm.dll must be released with your app. If you modify the category definition of the output, the original object's memory layout changes, for example, the private member of an Integer type, the user end must recompile if the old user is used to call new DLL functions, the application will have errors even causing a machine.
Attention to these rules, maybe choose how to completely avoid these issues, I mean to use Windows standard type to deliver information, such as to deliver a string, use PCHAR instead of String. For other more complicated structures, you can use the interface to solve it, which means two things:
The output type is an abstract class (Abstract class) or interface (Interface). The object should be established by the DLL (the user end does not know the memory layout of the object).
Meet the above rules, how to make the FORM object package in the DLL, you will be solved, and this part will tell this part.
Interface is an important concept in the field of object-oriented, which describes the responsibility between service providers and users, or defines communication between two objects, usually this way of communication The development will not be modified (in the ideal case), so the interface can also be considered as a contract between the object. From an OOP point of view, the interface is a set of public methods. The difference between the category is that it does not have the difference between the access level of Private and Protected, and cannot contain information members, and there is no active program code. It is just very simple ................... In a complex system, this simple look is particularly precious, often after a deep thinking, it can extract more abstract ingredients, which not only helps you think about the abstract level when designing, but also designed The interface is also compared to reuse again. From the perspective of the user, the interface is hidden behind the complexity behind the system. The client only needs to focus on the part it needs, it will be easier to use.
Design can be switchable
The so-called set of modules refers to a module that is dynamily loaded and released during program execution. For applications that are large and more complex, the application is cut into several independently operated modules. The following advantages: The configuration of the application deployment is more flexible (for example, some modules are only packaged in some versions). Reduce the file size each updated each time you update. It is conducive to clearly dividing the members of the group member. Effectively reduce the complexity of a single program, and the program is easier to maintain.
The following will make a specific and micro example, you can treat this example as a foundation framework (Framework), which can be used to apply to the actual project development.
Description
Let us simply analyze the needs of the application, assuming that the original development method is to connect all the program units into one executable, and now cut the various functions of the application into a separate module, for example::
-- Customer Data Maintenance (Customer.dll)
Main model (main.exe) - - Product data maintenance operation (Employee.dll)
- Order Data Maintenance (ORDERS.DLL)
Each of these DLLs is dynamically loaded when the user performs the function, and at least one form is included in each DLL. In order to be different from the general DLL, the following will be called Plugin. We expect Forms included in each Plugin DLL to have some common attributes and behaviors, so put them in a foundation window category, so that other forms have inherited from this basic category. Their relationship looks like this:
Each maintenance work needs to open a window, so one of the main rules is to create and display the window inside the DLL. We hope that each of the windows of each maintenance can only perform another maintenance job, so use ShowModal to display the window. After doing some simple analysis, you can get the common steps you need when you perform each job:
Load the specified PLUGIN DLL. Create and display the FORM object in the Plugin DLL. Release the FORM object. Release the Plugin DLL.
The work loaded and is the work of the PLUGIN DLL is responsible for the main program, and the object in front of the DLL must be built by the DLL yourself, so the work of establishing, displaying, and releaseing the FORM object is responsible for providing a function of the Plugin DLL The main course will only go to call them as appropriate.
Main program
In the main program, a method of executing a plugin is added. This method requires a parameter to specify a DLL file to load it, like this:
Procedure TMAINFORM.RUNPLUGIN (const filename: String);
VAR
Adllhandle: Thandle;
Aplugin: iplugin;
AFORMHANDLE: THANDLE;
Begin
AdllHandle: = SAFELOADLIBRARY (FileName);
IF adllhandle <> 0 THEN
Begin
Aplugin: = DLLCREATEPLUGIN (AdllHandle, Application.Handle);
Try
AFORMHANDLE: = APLUGIN.CREATEFORM (HANDLE);
Aplugin.showmodalform;
Aplugin: = NIL;
Freelibrary (AdllHandle);
Except
Freelibrary (AdllHandle);
Raise;
END;
end
Else
ShowMessage ('Can't load the library:' filename);
END;
From the above-described program, you can see that DLLCReatePlugin will call DLLCReatePlugin to create a PLUGIN article and achieve its interface reference, then the main program uses the interface reference to access the service provided by the PLUGIN object, including the establishment window, display window, etc. Wait. Obviously, the iPlugin interface is a bridge between the main function and the PLUGIN DLL, and the iPlugIN interface is at least the following method:
Createform - Establishing FORM Items ShowModalform - Display Window Destroyform - Destroy FORM Items
The readers of the eye may find that there is no call in the program, and there is no call similar to DLLDESTROYPLUGIN to destroy the Plugin object. When will these items will be released? They are automatically released. Since the establishment of the FORM object is done through the Plugin object, I intend to give the responsibility to the FORM object to the PLUGIN object, that is, when the Plugin object is destroyed, the FORM object will be automatically released; in order to simplify the destroyed PLUGIN object Action, I let the Plugin object have the ability to automatically refer to counts, so as long as the object does not use it (the reference count is 0) will be automatically released, the practice is simple, as long as the category of the actual IPLUGIN is inherited Since TinterfaceObject, the other details are all done by the VCL.
LoadLibrary and FreeLibrary also have their own reference counts, and use it to determine whether to load and release the DLL. That is to say, repeat call loadLibrary ('a.dll') does not load A.dll twice, the second call will only increment the reference count; the same, Freelibrary will decrease the DLL reference count until count 0 will truly release the DLL.
Then look at the DllcreatePlugin function:
Type
TcreatePluginfunc = Function (HAPP: THANDLE): IPLUGIN; STDCALL
Const
SDLLCREATEPLUGINFUNCNAME = 'CreatePlugin';
IMPLEMentation
ResourceString
SERRORLOADINGDLL = 'Unable to load the module!';
SERRORDLLPROC = 'Unable to call DLL Versions:% s';
Function DllcreatePlugin (HLIB, HAPP: THANDLE): IPLUGIN
VAR
PPROC: TFARPROC;
CreatePluginfunc: tcreatepluginfunc;
Begin
PPROC: = GetProcaddress (Hlib, Pchar (SDLLCReatePluginfuncName);
IF pproc = nil dam
Raise Exception.createFMT (SERRORDLLPROC, [SDLLCREATEPLUGINFUNCNAME];
CreatePluginfunc: = TCREATEPLUGINFUNC (PPROC);
Result: = CREATEPLUGINFUNC (HAP);
END;
DllcreatePlugin will try to create a PLUGIN article from the specified DLL module to create a PLUGIN object, and return to the interface reference of the PLUGIN object. The parameter HLIB is a DLL code, and the HAPP is passed directly to the DLL CreatePlugin Function, this parameter The role will be explained later. The program code required to this main program is roughly completed, and then look at the DLL's createPlugin.
DLL output
Our PLUGIN DLL only outputs a Chinese for external calls, which is the CreatePlugin mentioned earlier, and its function prototype is:
Function CreatePlugin (HAPP: THANDLE): IPLUGIN; EXPORT; stdcall;
The CreatePlugin is created a TPLUGIN article and is sent back to the IPLugIN interface. Since the PLUGIN object is only necessary to be established, we can make simple Singleton (3) with a national variation.
VAR
g_pluginintf: iplugin = nil;
IMPLEMentation
Function CreatePlugin (HAPP: THANDLE): IPLUGIN;
Begin
Application.handle: = happ; // Lets EXE and DLL use the same Application Handle.
IF g_pluginintf = nil dam
g_pluginintf: = TPLUGIN.CREATE; // Tplugin item reference count = 1
Result: = g_pluginintf; // Tplugin object reference count = 2
END;
CreatePlugin needs to pass a parameter HAPP that represents the Handle of the Application object of the caller program, usually incoming Application.Handle, so that the main model and the DLL Application object can "synchronize". The reason is to do this because when your DLL task does not use the "Build With Runtime Package" option, the executive file and the loaded DLL will each have an Application object, but only the Application object of the executive file has a window, DLL Nothing, therefore the DLL Application.Handle property is always 0. If this synchronization action is, when the DLL's Form is turned on, you will see a window button on the desktop's work column, it looks like performing another application, we don't want to see this. Situation.
Of course, if your main models and the DLL use "Build With Runtime Packages" to build (you should do this), you don't need this synchronization action (think about why?).
There are two rows in the program code about the reference count. It is a basic idea to express the interfacial design: the reference count of the item will be incremented when a mesh reference is passed in a Pass By Value in a communication (Pass) By Reference will not be). This concept helps you correctly master the life of the item.
Finally, don't forget to add this to the exports clause of the original code of the project:
Exports
CreatePlugin;
IPLUGIN interface and TPLUGIN category
The iPlugin interface is defined as follows:
IPLUGIN = Interface ['{D3F4445A-C704-42BC-8283-822541668919}'] // Press Ctrl Shift G to generate GUID
Function Createform (Hmainform: Thandle): thandle;
Procedure destroyform;
Function ShowModalform: Integer;
END;
In fact, the above functions can also be written into a general DLL function. When it is a DLL interface, the reason why it is also defined in this interface, on the one hand, it is desirable to simplify the interface of the DLL itself, and on the other hand, the management program code can be concentrated, if necessary If you increase the interface method, just add it in the iPlugin interface, you don't have to find the existing DLL original code one by one, which also helps to simplify the DLL writing and future maintenance work.
The interface does not contain the actual, and the actual must be provided by the category.
Then define a TPLUGIN category to do an IPLUGIN interface:
TPLUGIN = Class (TinterFaceDObject, IPLUGIN)
Private
FFORM: TFORM;
public
DESTRUCTOR DESTROY; OVERRIDE;
Function Createform (Hmainform: Thandle): thandle;
Procedure destroyform;
Function ShowModalform: Integer;
END;
Plus GUID in iPlugin and let TPLUGIN inherit from TinterFaceDObject purpose, in order to make objects have the ability of InterfaceD RTTI and automatic reference count, so our TPLugin object will automatically release it when anyone uses it. Private member Fform records the reference of the window established by this Plugin object to control its life, and its type can also be changed to TBASEFORM, then your TBASEForm is designed to be modified often, or designed to be abstract, Let these cores operate on the abstract hierarchy.
The names of each method are expected to be literate, the program code is also very simple, I believe you can guess eight nine do not leave ten, here is not listed here, it is worth mentioning that CreateForm functions and deconstruction dollars destroy are as follows:
TPLUGIN.CREATEFORM - Create items using category
In the Createform card, the trip to establish a FORM object is written:
FFORM: = g_concreteclass.create (application);
Where g_concreteclass is a national variable, it is defined as:
VAR
g_concreteclass: = nil;
And TBASEFormClass is a Class-Reference Type, which is defined in the same unit with TBASEFORM:
Type
TBASEFORMCLASS = Class of TBASEForm;
TBASEFORM = Class (TFORM)
.....
END;
That is to say, we use a variable g_concreteclass of a category reference to record the category type of the entity, so you must set the g_concreteclass before establishing the FORM object, so TPLUGIN can be physically made in the correct form category. action.
You may want to be so troublesome, write directly to the imaging Tcustomerform.create, is this not good? Simply put, it is based on maintenance considerations. Since the only TPLugin is in the future, the only way to change often is to be entry to the form, using the category reference to write the category type in the actual program code in TPLUGIN, if To physicalize other Form categories, just modify the change in g_concreteclass, do not have to mention someone else and replace the text, you have to worry that there is no change to it; in other words, we are equal to the use of category reference to make compilation To help us complete this movement of this replacement, and ensure that you will not miss anywhere.
I alternately use two vocabulary "establishing objects" and "physicalization", in fact, they refer to the same thing: establish a category entity (Instance).
This trick is also good for team development. You only need to announce two units of TPLUGIN and TBASEFORM, and then tell the team members to do it in two steps:
From TBASEForm, a new category (you can use Delphi's object treasure "to simplify this work). The unit of the TPLugIN class is added to the USES clause of this new category, and the category name is specified to the g_concreteclass variable in the initialization phase.
In this example, we only have a TBASEForm's future generation called TForm1, so there will be this paragraph in the unit of TFORM1:
Uses
DLLEXPORT; // TPLUGIN Category is in this unit
.....
INITIALIZATION
g_concreteclass: = tform1;
Tplugin.destroy
The deconstruction of the DESTROYFORM will release the FORM object to release it and restore the DLL Application Handle:
DESTRUCTOR TPLUGIN.DESTROY;
Begin
DESTROYFORM;
Application.handle: = g_dllapphandle;
Inherited destroy;
END;
Where g_dllapphandle is a national variable, which is announced as follows:
VAR
g_dllapphandle: thandle;
And we must save the Application Handle of the DLL itself when the DLL is initialized:
INITIALIZATION
g_dllapphandle: = application.handle;
In fact, if the DLL project uses the "Build With Runtime Package" option, this save and restore the application handle can be exempted. Conversely, if there is no saving and restore actions, and the DLL task does not use the "Build With Runtime Package" option, the main window is also closed when the DLL is released.
Enabling the original power
The important part of this should have been mentioned, you may find that I have not explained more to TBaseform, the reason is that TBASEFORM does not have a special place in this example, just a basic category reserved when expanding in the future. You may want to focus on this category in this category in this category to simplify the writing of each module, and make the application have a consistent operational way and behavior, this part of each person's demand is different. Just play it yourself.
If you think that the above program is too fragment, it is impossible to obtain the overall concept. It is recommended that you look directly at the original code of the example, implement the exemplary process to observe the process of observing the procedure, and then come back to find explanation, this Maybe it is more likely. In order to facilitate reading, I also listed two elements of the paradigm in the table and the table two. List one. Dllutils.Pasunit DllUtils;
Interface
Uses
Windows, Messages, Sysutils, Classes, Forms, Controls
Type
IPLUGIN = Interface
['{D3F4445A-C704-42BC-8283-822541668919}']
Function Createform (Hmainform: Thandle): thandle;
Procedure destroyform;
Function ShowModalform: Integer;
END;
TcreatePluginfunc = Function (HAPP: THANDLE): IPLUGIN; STDCALL
Function DllcreatePlugin (HLIB, HAPP: THANDLE): IPLUGIN
IMPLEMentation
ResourceString
SERRORLOADINGDLL = 'Unable to load the module!';
SERRORDLLPROC = 'Unable to call DLL Versions:% s';
Const
SDLLCREATEPLUGINFUNCNAME = 'CreatePlugin';
Function DllcreatePlugin (HLIB, HAPP: THANDLE): IPLUGIN
VAR
PPROC: TFARPROC;
CreatePluginfunc: tcreatepluginfunc;
Begin
Result: = NIL;
IF HLIB = 0 THEN
EXIT;
PPROC: = GetProcaddress (Hlib, Pchar (SDLLCReatePluginfuncName);
IF pproc = nil dam
Raise Exception.createFMT (SERRORDLLPROC, [SDLLCREATEPLUGINFUNCNAME];
CreatePluginfunc: = TCREATEPLUGINFUNC (PPROC);
Result: = CREATEPLUGINFUNC (HAP);
END;
End.
List II. DLLEXPORT.PASUNIT DLLEXPORT;
Interface
Uses Windows, Classes, Forms, Dllutils, BaseFrm
Type
// inherited from tinterfacedObject to be reference-counted.
TPLUGIN = Class (TinterFaceDObject, IPLUGIN)
Private
FFORM: TBASEFORM;
public
DESTRUCTOR DESTROY; OVERRIDE;
Function Createform (Hmainform: Thandle): thandle;
Procedure destroyform;
Function ShowModalform: Integer;
END;
Function CreatePlugin (HAPP: THANDLE): IPLUGIN; EXPORT; stdcall;
Exports
CreatePlugin;
VAR
G_Concreteclass: = nil; g_pluginintf: iplugin = nil;
g_dllapphandle: thandle;
IMPLEMentation
Uses dialogs, sysutils;
Function CreatePlugin (HAPP: THANDLE): IPLUGIN;
Begin
IF Happ <> 0 THEN
Application.handle: = happ; // sync application handle.
IF g_pluginintf = nil dam
g_pluginintf: = TPLUGIN.CREATE;
RESULT: = g_pluginintf;
END;
{TPLUGIN}
DESTRUCTOR TPLUGIN.DESTROY;
Begin
DESTROYFORM;
Application.handle: = g_dllapphandle;
Inherited destroy;
END;
Function TPLUGIN.CREATEFORM (Hmainform: Thandle): Thandle;
Begin
IF fform = nil dam
Begin
Assert (g_concreteclass <> nil, 'does not set the form name of the FORM class!'); ');
FFORM: = g_concreteclass.create (application);
Fform.mainformHandle: = hmainform;
END;
RESULT: = fform.handle;
END;
Procedure tplugin.destroyform;
Begin
IF fform <> nil dam
Begin
Fform.release;
FFORM: = NIL;
END;
Application.ProcessMESSAGES;
END;
Function TPLUGIN.SHOWMODALFORM: Integer;
Begin
IF fform = nil dam
Raise Exception.create ('DLLEXopRT: The window has not been established!');
Result: = fform.ShowModal;
END;
INITIALIZATION
g_dllapphandle: = application.handle;
End.
Paradigm
The paradigm can be downloaded here: plugindll.zip Download the compressed file and unwave first, read the readme.txt.
Improving
You can try to modify the model and enhance it, so that it can be used as the basic framework of the actual development project, the following lists are listed below:
Give TBaseform basic data processing power, like new, modified, deleted ..., etc. Modify make it suitable for Modeless Form and MDI applications. This means that the time to release the DLL will also change, you may need a series of structures to record the loaded DLL, usually a TSTRINGLIST can do it. Let a Plugin object can establish and maintain multiple different types of FORM objects.
You may want a DLL to provide a variety of FORM objects for main programs, and these FORM objects may have some degree of similar or dependent relationships. According to this requirement we can organize the plugin objects with the following two features:
Plugin objects can build a variety of different types of FORM objects, and they are all inherited from the basic form category TBASEFORM. There is only one plugin object in a DLL. According to the definition in [GHJV95], Abstract Factory is:
"Provide a interface to establish the same trier or dependent object, without indicating the CONCRETE CLASS"
Factory is often made into Singleton, which clearly tells us that the Plugin object is very suitable for a Factory. You may need to provide a RegisterClass method in the TPLugIN class. This method replaces the original category reference state, which originally sets the narrative of g_concreteclass in the unit of TBASEForm subcategory.
PlugInfactory.registerClass (TFORM1);
The registered category will be recorded in a series. The main program can specify the FORM category name to be established by the FORM object, like this:
Aplugin.createform ('tcustomerform');
The Createform method of the PLUGIN object will go to the skewers to search for a registered category, obtain the corresponding category reference and establish its entity (is it a bit like COM?).
Well, I think this prompt should be enough. The most important thing is to go to write and remove the wrong program to get a more profound experience. This Design Pattern will fully integrate into your knowledge system, not Enemy can be used.
Conclusion
In this document, it is mainly introduced to Delphi to design the Plugin module's actual process, which uses the techniques of the interfacial design (including the reference count of the interface, and the control of the life cycle of the item life cycle) and the Design Patterns to solve the design. Question, this is also one of the focus of learning.
In a multi-person development, if your responsibility is the design mainframe, what will you do when you want to cut an application with a DLL? This article shows a possible design method. If you have different ideas or any suggestions for this article, you are welcome to believe in your mind.
Delphi DLL Memory Vulnerability
Finally, although not the theme of this article, it is also worth noting that dynamic loaded DLL will have 4K memory vulnerabilities when released, and Delphi 5 and 6 have this problem, you can read the following two files, There is a detailed description and the solution is provided:
Memory Lost And Found ... and realase by roy nelson.http://www. -.thedelphimagazine.com/samples/1328/1328.htm vcl Leak fix for dynamic dlls by dejan maksimovic.http: //codecentral.borland.com/ CodeCentral / ccWeb.exe / Listing? ID = 16380
Note 1. Because the update of the DLL version can make the original use of its program unable to function properly, distinguish between different files (for example: mfcxx.dll), so that the same DLL must be saved in the hard disk, Even if the user removes the application, it does not dare to remove the relevant DLL files, so as to avoid other applications, because of the lack of this file, the problem is not working, and the problem formed is called DLL Hell. The appearance of COM has an attempt to solve this problem (through the intervene of the component support through the Performer), but it seems not ideal until .NET has finally had a better solution. Note 2. You can find related articles in http://www.geocities.com/huanlin_tsai/ [Experience Sharing]. It is inevitable that the above is inevitably doped with personal factors, perhaps other people have not happen to use Package, and there are many advantages in the way of using package, only the status and sensation of personal applications when personal applications. Come out, if there is a fallacy, please ask all parties. Note 3. Singleton Style: Provide a single window to create an entity of the category to ensure that there is only one entity that exists. Refer to [GHJV95] book. Reference
Delphi learning notes. Author: Chanda wisdom. Acer Information, 1998. [Cantu2001] MASTERING Delphi 6. Sybex, 2001. [Harmon2000] Eric Harmon. Delphi Com Programming. MTP, 2000. [GHJV95] E. Gamma, R. Helm, R. Johnson, J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995. 中文E: Parts Guide Design Mode, Ye Bingzhe. Pasheng, 2001.