Implement dynamic agents with Delphi (1): Overview

xiaoxiao2021-03-06  41

Implement dynamic agents with Delphi (1): Overview

[Mental Studio] Raptor [blog]

First, problem

The so-called dynamic proxy, first start from the Proxy mode of GOF.

Suppose there is an IFO interface:

{$ M } ifoo = interface (ie upterface) ['{3A85E46D-F3D4-4D9C-A06C-4E7C1BAC9361}'] Function Dosth (Dummy: Integer): string; stdcall; procedure bar; stdcall; end; {$ m-}

The interface provider has implemented it, and provides a factory method (Factory Method) to provide users with the creation of the instance, as follows:

TFooImpl = class (TInterfacedObject, IFoo) Protected Function doSth (dummy: Integer): String; StdCall; Procedure bar; StdCall; end; (* TFooImpl implementation code, slightly *) // create an instance factory method Function GetFooObject (): Begin Result: = TfooImpl.create as ifoo;

As a user of this interface, only the definition of the IFOO interface, and can be created instances that implement the IFO interface, but does not implement the definition and implementation of class TFOOIMPL. What if the user needs to increase transaction function for ifoo.dosth (assuming that dosth is implemented as an update to the database), what should I do?

Second, static agent solution

GOF's Proxy mode is one of the solutions:

As shown in the figure, first define a new IFOO interface to implement - TSTATICPROXY. Among them, an attribute FIMPL records an instance of TFOOIMPL. Dosth and bar are then implemented in TStaticProxy, and the BAR function that does not need to be changed directly to the FIMPL process, and transaction can be added in Dosth implementation. TSTATICPROXY code is approximately as follows:

TStaticProxy = class (TInterfacedObject, IFoo) Private FImpl: IFoo; Protected Function doSth (dummy: Integer): String; StdCall; Procedure bar; StdCall; public constructor Create (aImpl: IFoo); end; {TStaticProxy} constructor TStaticProxy.Create ( AIMPL: IFOO); Begin FIMPL: = AIMPL; END; // New Dosth, joined database transaction Function TSTATICPROXY.DOSTH (Dummy: Integer): string; begin dbconn.startTransAction; try fimpl.dosth (Dummy); dbconn .Commit; Except DBConn.Rollback; End; end; procedure TStaticProxy.bar; begin FImpl.bar; end; // new factory method Function NewGetFooObject (): IFoo; Begin Result: = TStaticProxy.Create (GetFooObject ()) As Ifoo; end; Now, users only need to use the new factory method NewGetfooObject instead of the original getfooObject, the instance returned by the new factory approach has already had the ability to add transaction processing for Dosth.

It can be seen that we pass a proxy class to proxy all the operations of the IFOO interface, which is equivalent to inserting additional processing code between the Client and TFOOIMPL, to some extent, this is the so-called "cross cut" of AOP.

Third, the problem of static agents

But the above static agent solution is still very troublesome:

First of all, if the member functions of the IFOO must be added to them by one by one; secondly, if there are many interfaces in the application that require a proxy, they must write such a dedicated agent class; third, When you need to change the agent function, you need to modify all proxy classes ...

Of course, these problems are not to "dynamic agents" cannot be.

For example, the first point. If the user holds the TFOOIMPL code, you can derive a TNEWFOOIMPL directly from TfooImpl, and then at Override's dosth in tfooImpl. Finally modify the factory method, change the instance of Creating and returning TNEWFOOIMPL. As shown below:

The problem is that you must use the tfooimpl code, and this is not done many times - unless it is not using Delphi, it is like a dynamic language such as Python. In some such as component containers, such as remote interface calls, there is also a "virtual agent" (that is, when you create a fiMPL cost, just create a proxy class when you create, then create an instance of fIMPL when you really need it) This Application, usually only the interface definition and corresponding instance can be obtained.

Because there is no TFOOIMPL code, we have to use more troublesome static agents. You can pay attention to the previous code, which is not used to use the TfooIMPL class.

As for the second third two problems, if a language that supports GP (generic programming) like C , you can be implemented via Template. Unfortunately, this feature is not supported before delphi.net.

Bes to say that the application of the component container or the universal remote interface is called to be determined by the agent, the static agent is not used by the static agent - because it must implement the interface of the desired agent, such as the above TSTATICPROXY implements the IFO interface. This GP is also impossible, because the template is only a compilation period dynamic characteristic. Fourth, dynamic agent

So we need "dynamic agents". This concept is proposed in Java in JDK1.3, which is in java.lang.Reflect, the proxy [1]. Because delphi is in all static compile languages, the most dynamic is stronger, so it is also possible, I have used Delphi to complete a dynamic proxy implementation similar to Java [2].

A typical dynamic agent is applied as follows:

// class instance because TMInterfaceInvoker required, so the original need to change the factory method returns the object Function GetFooObject: TObject; Begin Result: = TFooImpl.Create (); End; TFooInvHandler = class (TInterfacedObject, IMInvocationHandler) private FImpl: IFoo; FInvoker: IMMethodInterceptor; Protected Procedure Invoke (const aProxy: TMDynamicProxy; const aContext: TMMethodInvocation); StdCall; Public constructor Create; end; {TFooInvHandler} constructor TFooInvHandler.Create; Var tmp: TObject; begin tmp: = GetFooObject (); // tmp is Examples do not affect the reference count Finvoker: = TMINTERFACEINVOKER.CREATE (TMP); Supports (TMP, IFO, FIMPL); // convert object to an interface instance, // Mainly set to 1 reference count 1, so as not to be subject inadvertent release end; Procedure TFooInvHandler.Invoke (const aProxy: TMDynamicProxy; const aContext: TMMethodInvocation); begin If (aContext.MethMD.Name = 'doSth') Then Begin DBConn.StartTransaction; Try FInvoker.Invoke (aContext); DBConn. COMMIT; Except Dbconn.rollba Ck; end; end else finvoker.invoke (acontext); end; // new factory method function newgetfooObject (): ifoo; begin result: = tMDynamicProxy.create (typeInfo (iFoo), tfooinvhandler.create ()) AS iFoo; END;

The function of the above code is the same as the example of the static agent.

First look at the new factory method. Its implementation is similar to the static agent. The important difference is that this TMDynamicProxy is a universal proxy class, unlike TSTATICPROXY, must be customized according to the interface to be implemented. The TMDynamicProxy implementation of dynamic proxy function and additional functionality to interface calls is implemented by two parameters, depending on the incoming parameters of the runtime, it can "dynamically" to realize agents for different interfaces, and different additional features. Cut. So it is called "dynamic agent."

However, because Delphi is still a compilation language, the implementation of this dynamic agent is used in addition to a large number of strong RTTI functions such as Delphi itself, and it has also used techniques such as Thunk to some extent, in a certain extent, the compiler's "" Forces range, but this is no. Fortunately, these only in the implementation of the dynamic agent itself, for the application of the dynamic agent, basically can be different from Java.

TMDYNAMICPROXY TMDynamicProxy is used, TypeInfo (IFOO) is incoming interface type information for realizing dynamic interface implementation. The example of TFOOINVHANDLER is an additional function code that is cut.

So the next thing to pay attention is the implementation of this TfooInvHandler. TFOOINVHANDER is a class that implements the iMinvocationHandler. The definition of iMinvocationHandler is as follows:

IMInvocationHandler = Interface Procedure Invoke (const aProxy: TMDynamicProxy; const aContext: TMMethodInvocation); StdCall; End; TMMethodInvocation = class public Property IID: TGUID; Property CallID: Integer; Property MethMD: TIntfMethEntry; Property Params [aIndex: Integer]: Variant; Property RetVal: Variant;

This interface only defines an invoke method, TMDynamicProxy puts all the methods that are proxy interfaces to this method. The type of TMMETHODInvocation records the context of the method call, including interface ID, method ID, method Meta Data (RTTI metadata), parameter list, return value, etc.

In the invoke method implementation of the TfooInvHandler implemented in the example, it is determined whether the called method name is "dosth", if it is inserted into the transaction, otherwise IMMETHODITERCEPTOR interface instance is delegated. I design this interface is ready to implement a dynamic interceptor in the AOP, but in this example, this instance corresponds to a TMINTERFACEINVOKE class object. This class is also a common class like TMDYNAMICPROXY, which is used to implement the corresponding method call of invoke calls Dispatch to specific implementation class objects. Because it is implemented by some RTTI characteristics of TOBJECT, these functions cannot be obtained through the interface instance, so the interface object returned by the factory method is required to a general class object, and the return to the TOBJECT type does not lose general (still there is no TfooImpl) Implement code).

Note that in the implementation of TfooInvHandler, only the method name is only judged, and the interface ID is not determined. This is because in this example, it only processes the call of the IFOO interface, so it is not necessary. But if it is an AOP application, an interceptor can usually be used for multiple interfaces, and you must judge the IID here. The structure of the entire dynamic agent application is approximately as shown below:

With such a dynamic agent, in addition to the transaction processing like this example, it is convenient to cut, such as security check, log, etc. In this case, it is not a problem with Delphi to implement AOP.

(Endlessly)

Reference: [1] Transparent "Dynamic Agent's Once Past Today" ("Programmer" 2005 No. 1) [2] I use Delphi dynamic agent code to download here, still improved, for reference only.

[Mental Studio] Raptor Feb.03-05, Feb.27

转载请注明原文地址:https://www.9cbs.com/read-52754.html

New Post(0)