VpPuZzle. Observe. Part 1
Written by Victor Yukhtenko. Email: email@example.com
DLL-based component technology (The Visual Prolog Puzzle or VPPZL or just PZL) is the set of agreements to build VIP-based applications on the basis of the standard VIP packages placed into DLLs. The VIP package fitted to PZL-technology agreements is named pzl-component. The VIP project (executable or DLL), which contains pzl-components, organized in a special way, is named pzl-container. The short description of the pzl-technology and it's basic consepts may be found below.
Talking about components we consider component as the peace of software, which can be reused in applications without the change of the source code.
The component-based programming is one of the ways to make software development efficient. The usual ways to represent components in the component-based technologies today are:
- Source code
- Statically linked libraries
- Dynamically linked libraries (DLLs)
As to Visual Prolog the VIP package is the example of the component, which is represented via the source code.
The use of statically linked libraries as components in VIP is possible but is not clearly supported.
Compare to source code the use of statically linked libraries gives the only opportunity to hide the internal algorithms of the component. But components based on both source code and static libraries have the negative features:
- The size of the application is the sum of the size of used components
- Developers do not have the opportunity to extend the application’s functionality without the rebuilding the whole application.
The best way of the component representation is the DLL. With the use of DLLs almost all goals of the efficient development are reached. Applications become expandable and easily modifiable. Microsoft Component Model technology (MSCOM) is the one of the possible sets of agreements regarding the use of DLLs. The Microsoft agreements, which make DLLs as the COM components are widely used. Visual Prolog currently supports the use of the MS COM components. But to make the use of MSCOM in the VIP-style takes time and still has some problems. Placing the code to DLLs we (being programmers) meet problems which we must have in mind. We must: · take care for the loading and unloading the DLLs · organize the general error handling · think about the common output stream
The goal was to support the use of DLLs with no knowledge about DLLs, using the current VIP IDE.
The usual way to use the DLL
Let’s assume we have two interacting dynamic classes shown in the table
|Class A||Class B|
inrerface a predicates pA:(). end interface a class a:a end class a implement a clauses ... Ob=b::new(), Ob:pB(). clauses pA():- ... end implement a
inrerface b predicates pB:(). end interface b class b:b end class b implement b clauses ... Oa=a::new(), Oa:pA(). clauses pB():- ... end implement b
Let’s assume now that on some reasons we wish to place the implementation of the class B into DLL. The standard MS Windows methods are supported in VIP6 well enough by the IDE and the PFC class pfc\application\useDLL.
It is the easy procedure: using the IDE it is needed to create project with the target type DLL. Then the package, which includes the class B, must be included to the appropriate DLL project. The exported predicate must be created and then it must be linked to the predicates of the class B.
The part of the application, which uses class placed to the DLL, must be also modified. Say for the calling the predicate pB the code in the class A will look like
... ObjDll = useDll::load(DLLFileName), pB_Ref=ObjDll:getPredicateHandle(pB_Exp), pB_Ref(…), ...
It is not important how much the code above has practical sense. The main point is that the code of the class A, which calls the predicate pB, must be changed dramatically and it becomes hardly dependant on the procedure of the interaction with the class placed to DLL.
Moreover, the creation of the instance of the class A and the call of the predicate pA from the class B side will need to create some special code.
VPPuZzle: Basic Idea
The PZL technology makes it possible to split the application on parts, which are placed to DLL so the codes of the classes are not dramatically changed. The called and calling classes are indifferent in that case to the place where they are placed. It means, that all the communication way work well for calling and called classes:
- Main applicaion - DLL
- DLL - Main applicaion
- DLL - DLL
The two features of VIP system are used to make this possible:
- The main application and DLLs created by VIP have the same memory space;
- All instances of classes belong to the domain Object.
The PZL technology is built around the basic idea: When some peace of code needs to call the predicate of some class placed to DLL, then the call new() is transported to DLL, the new instance of the needed class is created and then the pointer to the created object is transported back to the caller. Between main application and DLL this pointer is transported belonging to the Object domain.
But on the back way to the caller it is converted to the domain of the called class.
So in the reality the chain of substitutions and conversions is made, when we use the call:
... MyClassInstance=myClass::new(), ...
Let’s assume the DLL is already loaded.
- myClass is the static class with the predicate (not constructor) new() at the calling side.
- The predicate call new() transported to DLL and real constructor is invoked MyClassInstance=myClass ::new()
- MyClassInstance converted by MyClassObject=convert(object,MyClassInstance)
- MyClassObject delivered to calling side
- MyClassObject converted to MyClass domain by MyClassInstance=convert(myClass,MyClassObject)
Thus from the pragmatic point of view the call
will take place, and then it is used as usual
So the source code of the calling class is not changed.
The mechanizms of the Pzl-system
The Pzl-system is represented by the class pzl and appropriate library.
- makes automatic loading of DLLs with the needed class
- transports the constructor calls and object pointers back
- gives the opportunity to interact classes placed to different DLLs (not only to the application and DLL)
- unloads automatically the DLLs, when all classes are finalized by garbage collector
- leads to minimal changes in the source codes of classes
- doesn’t change dramatically the structure of the main application
- includes all nessesery libraries and classes, which support the running of the pzl-system
- gives the possibility to update the existing application projects so it can use the PZL technology
- gives the possibility to generate the code of DLLs automatically
- contains all needed tools to use the pzl-system practically
The technology means several entities to be described:
- VIP Pack as the pzl-component
- Component Delegate (proxy)
- VIP Project as the pzlContainers
- Main Application as the pzlPort
PzlComponent is the usual Visual Prolog class. The only difference is that:
- this class inherits from the class pzlComponent
- the base interface of this class supports interface pzlComponent
- the base interface of this class contains the special constant componentDescriptor_C
- the interface name and the class name must be different
- The constructor of the class declared as new:(object AnyUserDefinedObject)<br\>The argument has no special meaning in the context of the Pzl System.
The constant componentDescriptor_C belongs to the special domain pzlComponentInfo_D. One of the arguments of the structured domain pzlComponentInfo_D defines the unique component identifier. The iniquity has the sense only in the given application. The identifier may belong to the domain string or it can be a Universal ID, the same as it used in the MSCOM technology. Component has also the alias, which can be used to create the instance in some cases. The Component Alias is the part of the component descriptor also. The source code of the class A after the conversion to the pzlComponent is shown below.
inrerface iA supports pzlComponent constants componentDescriptor_C:…componentInfo ( componentAlias_C, componentID_C, ... ). predicates pA:(). end interface iA class a:iA constructors new:(Object). end class a implement a inherits pzlComponent clauses new(_Object):- ... clauses ... Ob=b::new(This), Ob:pB(…). clauses pA():- ... end implement a
Class B converted to the pzlComponent looks absolutely the same except the name of the class. You can see that there are changes only in declarations and not in the meaningful code of the implementation. You can see that the pzl-component doesn't know nor about the place where it is placed, nor about the place where the component, which it comminicates with, placed.
The registration of the component
Pzl-system must know where the pzl-component is placed at the computer. To provide this the registration mechanism is used. The information regarding the component registration may be stored in the user-defined file or it may be stored in the Windows Registry. The same component may be registerede in more then one place.
The pzl-technology tools give the possibility to register or to deregister any component from the any place of registration one by one or using the group mode.
Three ways to create an instance of the component
The initial and the most important process of the communication of two classes is that one class must get the pointer to the instance of the other created class.
There are three ways to invoke the creation of the instance of the PzlComponent:
- By calling the class constructor new()
- By calling the predicate newByName() of the pzl class.
- By calling the predicate newByID() of the pzl class.
The first way was already described and looks like
... MyClassInstance =myClass::new(SomeObject), MyClassInstance:callNeededPredicate(…) ...
It corresponds to the usual VIP rules.
The second way uses PzlSystem explicitly and looks like
... MyClassObj=pzl::newByName(“MyClass”,...), MyClassInstance=tryConvert(iMyClass, MyClassObj), MyClassInstance:сallNeededPredicate(…),
Here iMyClass is the base interface of the class myClass and “MyClass” is the string name (alias) of the class myClass.
The third way also uses PzlSystem explicitly and looks like
... MyClassObj=pzl::newByID(str(“MyClass”),...), MyClassInstance=tryConvert(iMyClass, MyClassObj), MyClassInstance:сallNeededPredicate(…),
Here iMyClass is the interface of the class myClass, and str(“MyClass”) is the string form of the class identifier.
The string form of the class identifier str(...) is the convenient form, but it doesn't provide the uniqueness. To provide the uniqueness it is recommended to use the numeric identifier, which must look like (numbers depend on the user's wishes):
The user is free to choose the way to represent the identifier. For instance while learning the programming with the pzl-system use it is convenient to use the string representation of the identifier str(...). In the case of the real commercial programming it is preferable to use the numeric identifier uid(...).
Class delegate (proxy)
To use the usual VIP style
... MyClassInstance =myClass::new(SomeObject), MyClassInstance:CallNeededPredicate(…) ...
in the case, when called and calling classes placed in different entities (in the executable and DLL or in two different DLLs), the process of creation of the instance of the class uses the delegate (proxy) class.
Thus the calling class calls the predicate new(…) of the proxy class and then proxy class communicates with the Pzl-system and Pzl-system returns the pointer to the instance of the called class to proxy class. Proxy class then converts the object domain to the domain of the called class and the creation of the pointer to the instance is complete.
The text of the proxy class is extremely simple and it is possible to generate the text of the proxy class automatically for each PzlComponent. As an example the text of the class implementation of the class-proxy is shown
implement aboutDialog open core constants className = "AboutDialogProxy". version = "1.0". clauses classInfo(className, version). clauses new(ObjIn)=convert(iAboutDialog,ObjOut):- ObjOut=pzl::new(iAboutDialog::componentID_C, ObjIn).
The package file AboutDialog.pack has the text
Pzl-technology tools can generate the content of files needed to have a class-proxy automatically.
The combination of the Original and Proxy classes
In the case, when called and calling classes placed in one entity (in the executable or in one DLL), then the communication of the classes in the form
... MyClassInstance =myClass::new(SomeObject), MyClassInstance:CallNeededPredicate(…) ...
happens explicitly, and PZL system doesn’t participate in the interaction. The usual VIP rules work here. The package for this case looks like
#include@"AboutDialog\AboutDialog.ph" % privately used packages #include @"pfc\string\string.ph" % private interfaces #include @"resourceIdentifiers.i" % implementations #include @"AboutDialog\AboutDialog.pro"
It was found convenient to have all files, related to the pzlComponent - the Original and Proxy classes and packages, mentioned in one package.
The conditional compilation gives the possibility to use the appropriate declarations and implementations. Depending on the meaning of the conditional compilation parameter, the original or proxy code is used.
The text of the combined package file is shown below.
#include @"AboutDialog\AboutDialog.ph" #include @"pfc\string\string.ph" #if iPzlConfig::useAboutDialogOriginal_C=true #then % privately used packages % private classes % private interfaces #include @"resourceIdentifiers.i" % implementations #include @"AboutDialog\AboutDialog.pro" #else #include @"AboutDialog\AboutDialogProxy.pro" #endif
The same principle is used to combine the contents of the header file AboutDialog.ph for the use of the Original and the Proxy related files
So if we need to use the original classes, then the constant useAboutDialogOriginal_C must have the value true, and if we need to use the proxy-related files, then the constant useAboutDialogOriginal_C must have the value false.
The constant, which defines the way of communication, is defined in the file PzlConfig.i of the package PzlConfig.pack , which is the collection of the pzlComponents of the given entity (executable of DLL).
The PzlContainer is the usual Visual Prolog project (one container - one project). It can be the project, which generates the executable application or it can be the project, which generates the DLL. Unlike the pzl-component, the PzlContainer “knows” which pzl-components it contains. All pzl-components, which are placed to the given pzl-container, must be included to the special package PzlConfig. The implementation of the class pzlConfig deals with the pzl-components also. The picture below shows the structure of the PzlConfig package as it can be seen on the Project window of the IDE.
The file of the interface type PzlConfig.i, implementation type PzlConfig.pro and package type PzlConfig.pack are the only entities, related to the pzl-components, included to the given project.
Files PzlConfig.cl and PzlConfig.ph has no deal with pzlComponents and thus are included to the set of files of the PzlSystem. Because of the information regarding the pzlComponents included to the given pzlContainer is concentrated only in files of the pzlConfig package. This feature gives the possibility to move pzlComponents from one pzlContainer to another pzlContainer with no changes in other parts of the projects.
Pzl-technology tools may update the content of the pzlConfig files automatically, when pzlComponent is added (removed) to (from) the given pzlContainer.
Being the usual project, PzlContainer may contain not only pzlComponents, but it may contain usual Visual Prolog packs also according to the user needs.
The feature of the PZL-technology, when two pzlComponents placed in one entity communicate explicitly with no use of PzlSystem and communicate via PzlSystem, when they are placed in different entities, gives the possibility to split the application on parts whenever user finds it convenient to him. No change in code is needed. Pzl-technology tools can help in this.