During the lightweight container based on dynamic agents, dynamically loaded external customer custom interface / class / component functions is a necessary component. The implementation that should be used as a custom component should not be determined for the implementation of the DLL or BPL to be used as a custom component. During the repeated test, some of the technical details are found, especially when using a string type as a parameter or return value.
If you have developed DLL with Delphi, you know that Delphi's DLL wizard is generated, and there is a long "important note on DLL memory management at the beginning of DLL Project Source", which is roughly: if in DLL In the exports function, use the string type as a parameter or return value, you must add ShareMem in the DLL's USES segment and your application's USES segment, which will make the two use borlndmm.dll to manage memory management, can guarantee String Type Memory Allocation / Release is correct.
This is because the string type is inside Delphi, which provides dynamic memory allocation / release mechanism and reference counting mechanism, which allows String types to be used as in the simple type, not like in C / C . Troublely considering memory management and prevents memory leakage. But this also brings such a problem like DLL: If you do not use ShareMem, it is possible to happen in one of the memory that is incorrectly released, and eventually leads to an annoying Access Violation.
For simple function calls, you can implement it with DLL ShareMem, but if you involve interfaces and classes, you can't.
Consider the simple interface and implementation below.
/ / -------------------------------------------------------------------------------------------- -------- // Define in the interface unit {$ m } ideMointf = interface ['{5F3C4D61-B885-41B6-B43B-C4725DF5D901}'] Function GetHello (NID: Integer): string; stdcall; end; ; {$ M -} // ------------------------------------------ --------------- // Define in Implementation Unit TYPE TDEMOIMPL = Class (TinterFaceDObject, Idemointf) protected {protected declarations} Function GetHello (NID: Integer): string; stdcall; end; procedure CompRegister (aIntfReg: TMRegisterIntfEvent); Cdecl; implementationProcedure CompRegister (aIntfReg: TMRegisterIntfEvent); Begin aIntfReg (IDemoIntf, TypeInfo (IDemoIntf), TDemoImpl); End; {TDemoImpl} function TDemoImpl.GetHello (nID: Integer): String; begin Result: = 'Hello' INTOSTR (NID); end;
First come to see the implementation of the DLL version. Create a DLL Project and add the above two units and come out of the Compregister function exports. For this compregister function, make a brief explanation:
This compregister is a registered entry that performs the DLL call into this registration function, and the user component package must implement this registration function and register the interface / class to the container to register the user. TmRegisterintFevent is a method type. When the container calls the registration function, pass the registration method reference to this registration function via the parameter to call it. Below is a unit test program implemented with DUnit.
procedure TTestCaseDLLPackageLoader.Setup; Var funcInit: TMFuncCompRegister; begin hPkg: = LoadLibrary ( 'demopkg.dll'); funcInit: = TMFuncCompRegister (GetProcAddress (hPkg, 'CompRegister')); funcInit (GMIntfReg.RegisterIntf); end; procedure TTestCaseDLLPackageLoader. TearDown; begin GMIntfReg.UnregisterIntf (IDemoIntf); FreeLibrary (hPkg); end; procedure TTestCaseDLLPackageLoader.TestLoader; Var f: IDemoIntf; begin f: = GMIntfReg.GetIntfInstance (IDemoIntf) As IDemoIntf; Check (f.GetHello (10) = ' Hello 10 '); END;
This test is simple: first in initialization (Setup method [1]), then call the COMPREGISTER registration interface and implement the class. In the Test Function TestLoader, the interface instance is obtained by the interface registration manager (GMINTFREG, supplied by the container). The internal implementation principle of this method is to find the corresponding class type through the GUID of the interface, then create an instance, and then transfer to an interface reference through the Supports function. Call the interface method getHello and check it with DUnit's Check function. Finally, the interface registration information is removed in the cleaning process (Teardown method [1]) and releases the DLL.
But the result is very unfortunate, the test has not passed. The problem is that f.gethello () returns a string, and the memory used by this String is allocated in the DLL, but because Delphi's compilation is automatically cleared before the TestLoader returns, the return value String reference is automatically cleared. After debugging, you can find that the problem is here, first can be sure, the Check function check is passed, but open the CPU debug window tracking can find an abnormality when compiling the generated cleaning code.
Why is it clear that ShareMem is used but will it be wrong? Because the interface method that calls this call is not a DLL EXPORTS function, it is not managed by Sharemem in the parameter or return value. It is not to be managed by ShareMem, and an error is Of course.
Let's take a look at the implementation of the BPL mode. Also create a BPL Project, then add the front of the interface unit and the implementation unit, but it is not necessary to exports like a DLL, but you need to join a Hack:
Procedure Hackregister; ASM PUSH EDX PUSH EAX CALL Compregister Pop ECX POP ECXEND;
Why has to be this way? Although BPL is essentially a DLL, Delphi is not the same. One of the biggest features is that the content in the BPL does not require explicit export, but the contents of all Interface portions are automatically automatically exports at a certain rules. This law is roughly like this: The export name of the function in a unit is composed of its cell name and function name, such as the above-mentioned Hackregister's export name is @ unit1 @ Hackregister $ qrv, where $ qrv is Compile additional strings. If you are a class method, you have to add a class name. If you have a parameter or return value, this information is also compiled into an additional string for distinguishing overload et al. Because the Compregister has a complex parameter, it will form a complex export name like this: @ unit1 @ compregister $ @ unit1 @ Compregister $ qPQQRRX5_GUIDPX17TYPINFO @ TTYPEINFOPX17SYSTEM @ TMetAclass $ V. In order to avoid such trouble, Delphi's standard BPL uses a parameter-free register function, but in this way, it needs to rely on a global variable to be shared by the main program and BPL, and to truly implement sharing, this global variable must In a public BPL, such as standard BPL is two packages that depend on Vcl.bpl and RTL.bpl [2]. I don't want to make another separate BPL, so use the above-free Hack method to turn it, the parameter is implied by EDX: EAX. Here is the beta test code.
procedure TTestCaseBPLPackageLoader.Setup; Var funcInit: TProcedure; begin hPkg: = LoadPackage ( 'demobpl.bpl'); funcInit: = TProcedure (GetProcAddress (hPkg, '@ Unit1 @ HackRegister $ qqrv')); CompRegisterHack (funcInit, GMIntfReg.RegisterIntf ); end; procedure TTestCaseBPLPackageLoader.TearDown; begin GMIntfReg.UnregisterIntf (IDemoIntf); UnloadPackage (hPkg); end; procedure TTestCaseBPLPackageLoader.TestLoader; Var f: IDemoIntf; begin f: = GMIntfReg.GetIntfInstance (IDemoIntf) As IDemoIntf; Check (f .Gethello (10) = 'Hello 10'); END;
Basically, like the DLL version, just call the registration entry to indirectly through the CompregisterHack, store the parameter into the EDX: EAX, adjust the Hackregister function pointer. In addition, the DLL version is exactly the same.
In theory, there should be no problem, but for the sake of insurance, I used the debug mode to run this test, track the code of the CPU window until the test function returns, there is no problem. But continue to run, but an unexpected exception occurred in Dunit's test framework. This makes me a few days, I can't understand, because Delphi itself uses BPL, using String, it is also very common, never I heard that there is a problem. In order to find the problem, I even tried to change the implementation class to Tcomponent, but it still exists. Finally, I noticed here, although it is a TDemoIMPL instance in GetInetFinStance, but the GetHello method that is returned is the member of the interface type Idemointf. Theoretically call the interface method in nature, mapping the virtual function mechanism of OOP to class instance, there should be no difference, but here involve problems across Module calls (call code between EXE and BPL) will not be here. After mapping, the conditions for destroying ShareMem are destroyed?
In order to confirm this guess, I contain class definition code in the test program, and change the interface method to class method calls, and the test is really successful. In order to understand the nature of the problem, I will again view the CPU window in both cases of the class method call and the interface method. Sure enough, after the interface method is called, after the jump of the virtual function, the target address after the jump is different from the address called by the class method! But the code at the different code addresses is exactly the same in both cases!
This makes me fight. I even implemented an interface-based String class instead of String type, although this can be passed through parameters, but the return value is still not. And this method is really difficult to see and it is difficult to use.
Today, I thought that my test program was compiled by static bag, ie without BPL mode, and Delphi itself is compiled into a BPL mode. So I hold the mentality of trying, change the test program to the way VCL.BPL and RTL.bpl.
Sure enough test passed.
Reference [1] Cai Huanlin "Dunit Delphi's Ultimate Test Tool" [2] Wang Rui "Dynamic Loading and Dynamic Registration Techniques In - depth Exploration"
[Mental Studio] Raptor APR.24-05