[Abstract] this paper attempts to introduce interface oriented variables into LCOM programming model to provide programmers with a more flexible programming model.
1. Cause of the problem
Previous articles LCOM: lightweight component object model A programming mode similar to COM is introduced, which can be understood as a simplified version of COM. Among them, the interface is the core of programming. All functions are defined by the interface. A software module implements one or more objects, and each object implements one or more interfaces to provide services. Each interface is defined as a set of functions. In COM, this point is relatively dead. Variables are not allowed in the interface, so that VaR appears when you want to access the internal variables of an object_ Get, var_ Interface functions such as set (which can be automatically generated in Visual Basic).
Using functions to control variable access inside objects has many advantages. For example, it can control the modification of variables to conform to the logic of variables, maintain the consistency of data inside objects, and ensure that there is no illegal access during access, such as array access. If the subscript exceeds the access, it will be a disaster. Many software bug s are caused, and variable read-only can be provided, Write only and other functions. When reading variables, you can also provide some special services, such as virtualized services. In fact, there is no corresponding variable in the object. You can provide a virtual variable Get, which can keep the internal implementation flexible and keep the external interface consistent with the user logic. After all, the user sees and the internal implementation are two different things.
However, not allowing variables in the interface also brings a lot of inconvenience. One feeling is that it is too dogmatic. For example, the classic two-way linked list data structure is to add next and last pointers to each data object, and then manipulate these two pointers to form a circular two-way linked list. Of course, a two-way linked list interface can be designed to provide access to next and last, but it's a little strange to do so, which limits the freedom of programmers. Originally, data - > pnext - > plast = data; This usage is not easy to use. The key for programmers to adapt to a new set of things is that they do not bring substantive benefits. Of course, you can argue that accessing variables through interface functions can increase code security, such as verifying the legitimacy of linked list nodes, but you can't code smoothly, and accessing a variable requires calling a function, which will cause a great psychological burden for some programmers. In any case, the operation efficiency of function call is certainly not as efficient as direct variable access. I also want to use LCOM under C51. As a result, CPU is consumed in these red tape. Programmers need freedom and have the right to make a balance between security and efficiency. They should allow experts to write code that is equally safe but more efficient. Professional photographers should not only provide fool cameras. Therefore, this paper attempts to introduce a method of defining variables in the interface in LCOM, and takes the two-way linked list as an example to demonstrate the feasibility of this method.
2 where does the interface data exist
The first problem we face is how to represent the data related to the interface and where to store it? In the previous LCOM interface, the interface function is declared in a static variable in the object implementation file. Let's review the IObject interface and IGLApp interface implemented in the MandelBrotApp object implemented in the previous article:
The functions of the public interface and the functions required for object management are declared below, which is necessary for each object implementation*/ static int mandelbrotappQueryInterface(HOBJECT object, IIDTYPE iid, const void **pInterface); static int mandelbrotappAddRef(HOBJECT object); static int mandelbrotappRelease(HOBJECT object); static int mandelbrotappIsValid(HOBJECT object); static void mandelbrotappDestroy(HOBJECT object); static int mandelbrotappCreate(const PARAMITEM * pParams, int paramcount, HOBJECT* pObject); static int mandelbrotappValid(HOBJECT object); /*Define an IObject object that implements the IObject interface, MandelBrotApp The first member of the object must be a pointer to the data*/ static const IObject mandelbrotapp_object_interface = { 0, /*IObject The pointer to must be the first member of the object implementation, so the offset is 0*/ mandelbrotappQueryInterface, mandelbrotappAddRef, mandelbrotappRelease, mandelbrotappIsValid };
The above code provides the implementation of IObject interface in mandelbrotapp object. In the object, these functions only store a pointer to mandelbrotapp_object_interface is a pointer to this variable. Similarly:
/*The functions of the IGLApp interface are declared below*/ static int mandelbrotapp_glapp_Render(HOBJECT object, int x, int y, int width, int height); static int mandelbrotapp_glapp_SwitchIn(HOBJECT object); static int mandelbrotapp_glapp_SwitchOut(HOBJECT object); static int mandelbrotapp_glapp_GetOption(HOBJECT object, int option); static int mandelbrotapp_glapp_MouseLeftDoubleClick(HOBJECT object, int x, int y, int ctrlkey); static int mandelbrotapp_glapp_MouseLeftDown(HOBJECT object, int x, int y, int ctrlkey); static int mandelbrotapp_glapp_MouseLeftUp(HOBJECT object, int x, int y, int ctrlkey); static int mandelbrotapp_glapp_MouseRightDoubleClick(HOBJECT object, int x, int y, int ctrlkey); static int mandelbrotapp_glapp_MouseRightDown(HOBJECT object, int x, int y, int ctrlkey); static int mandelbrotapp_glapp_MouseRightUp(HOBJECT object, int x, int y, int ctrlkey); static int mandelbrotapp_glapp_MouseMove(HOBJECT object, int x, int y, int ctrlkey); static int mandelbrotapp_glapp_KeyDown(HOBJECT object, int key, int ctrlkey); static int mandelbrotapp_glapp_KeyUp(HOBJECT object, int key, int ctrlkey); /*The following defines a pointer to IGLApp. In the object, a pointer member should point to this structure*/ static const IGLApp mandelbrotapp_glapp_interface = { (int)&(((const sMandelBrotApp*)0)->__IGLApp_ptr), /*The offset of this interface is the offset of the pointer to this structure in the object*/ mandelbrotappQueryInterface, mandelbrotappAddRef, mandelbrotappRelease, mandelbrotappIsValid, mandelbrotapp_glapp_Render, mandelbrotapp_glapp_SwitchIn, mandelbrotapp_glapp_SwitchOut, mandelbrotapp_glapp_GetOption, mandelbrotapp_glapp_MouseLeftDoubleClick, mandelbrotapp_glapp_MouseLeftDown, mandelbrotapp_glapp_MouseLeftUp, mandelbrotapp_glapp_MouseRightDoubleClick, mandelbrotapp_glapp_MouseRightDown, mandelbrotapp_glapp_MouseRightUp, mandelbrotapp_glapp_MouseMove, mandelbrotapp_glapp_KeyDown, mandelbrotapp_glapp_KeyUp, };
The implementation function of iglapp interface is defined, and only mandelbrotapp is stored in the object_ glapp_ Interface is the pointer to this variable__ IGLApp_ptr, we rely on the relative position of the pointer in the object to calculate the address of the object, so we can normally access each data object in the object. When the QueryInterface interface function of the object is implemented, it returns the address of this pointer. However, if you want to implement the variables related to the interface, you must not put them in this static data structure, so you can't write the variables directly to the interface, because this static data structure will be shared by all object instances, and it's impossible to provide an independent variable storage space for each object instance.
Let's take a closer look at the implementation of IObject. Last time, when we used macros to help each object implement IObject functions, the implementation of AddRef and Release relies on a reference count. There is also a variable in the object that can judge whether it is the CLSID of a class and provide users with support for whether it is an object class. This is also implemented with the CLSID stored in the object. These two variables are used to implement the IObject interface. In fact, they can be regarded as variables related to the IObject interface. We stipulate that they should be declared in each object:
typedef struct _sMandelBrotApp { /*Object Interface pointer*/ const IObject * __IObject_ptr; /*Reference count*/ int __object_refcount; /*Class ID pointer*/ IIDTYPE __object_clsid; /*IGLApp Interface pointer*/ const IGLApp* __IGLApp_ptr; ......
Later, macros are supported to assist implementation. Such macros are defined:
/*This macro is used to declare the basic members of each object, including pointers to iobjects The reference count and CLSID can be placed directly at the front of the structure implemented by the object */ #define OBJECT_HEADER \ const IObject * __IObject_ptr; \ int __object_refcount; \ IIDTYPE __object_clsid; \
Well, rules are often broken by rule makers. A famous programmer at Microsoft has formulated the first programming rule: any rule can be violated as long as there are sufficient reasons. Programmers should respect rules on the one hand and despise them on the other. I don't know how Microsoft's c language version of COM is implemented from time to time. Anyway, LCOM is the most smooth way to do so, so I don't care about the disclosure of variables.
From this, we can see that the variables related to the interface can be implemented. The simplest way is to declare them behind the interface pointer. The IObject * * variable of such an interface can also be used as a pointer
struct sObject { const IObject * __IObject_ptr; int __object_refcount; IIDTYPE __object_clsid; };
The pointer of this structure is used, so the IObject interface has its own variables. Of course, this only applies when we use the macros we provide to implement the IObject interface. In fact, object implementers can choose to implement their own AddRef and Release, not through the above macros or similar data structures. Therefore, the above statement of relevant variables of IObject interface is actually an additional effect of macro assisted implementation and cannot be used as a normative provision in LCOM, When using the IObject interface of LCOM, we can't assume that IObject * * is a pointer to an sObject. What if a programmer doesn't use the auxiliary macro we provide to implement his own object.
Anyway, we have a way to implement object related variables, that is to declare the interface pointer variable and interface variable together. In this way, the pointer of the interface pointer variable obtained through QueryInterface is also the structure pointer of the interface related variables (of course, this structure must start with an interface definition variable pointer one by one).
3 bidirectional linked list interface
At the beginning of this section, we take the bidirectional linked list as an example to demonstrate the implementation method of interface related variables. In fact, we go to the extreme. This interface has no functions at all (of course, IObject related functions must be implemented. LCOM stipulates that each interface must implement each function of IObject, so that AddRef, Release, QueryInterface and other functions can be called through any interface). There are only two interface related variables, pNext and pLast. We use a two-way circular linked list to describe it. Users remember the header, and then start from the header. You can traverse the linked list in two directions through pNext and pLast. Of course, the header is generally not an item in the linked list. Let's define the two-way linked list interface. We provide auxiliary macros:
DEFINE_GUID(IID_DLIST, 0xcdad8509, 0xb8, 0x4c3e, 0xa3, 0xcc, 0xbe, 0x7c, 0xf0, 0xc, 0x20, 0x8e); struct sIDList; struct sIDListVar; typedef struct sIDList IDList; typedef struct sIDListVar IDListVar, *IDListVarPtr; struct sIDList { OBJECT_INTERFACE }; #define DLIST_VARDECLARE \ INTERFACE_DECLARE(IDList) \ IDListVar* __dlist_pLast; \ IDListVar* __dlist_pNext; struct sIDListVar { DLIST_VARDECLARE }; #define DLIST_VARINIT(_objptr, _obj) \ INTERFACE_INIT(IDList, _objptr, _obj, dlist); \ _objptr->__dlist_pLast = \ _objptr->__dlist_pNext = (IDListVarPtr)&_objptr->INTERFACE_VAR(IDList); #define DLIST_FUNCIMPL(_obj, _clsid, _localstruct) \ static const IDList _obj##_dlist_interface = { \ INTERFACE_HEADER(_obj, IDList, _localstruct) \ };
As you can see, the interface of the bidirectional linked list does not have any functions, but we have defined DLIST_VARDECLARE macro requires objects that support bidirectional linked lists. In the object data structure, use this macro to declare variables related to bidirectional linked lists. In this way, use IID_ The interface pointer from the DLIST interface name QueryInterface can be used as a pointer to IDListVar. Conversely, IDListVar can also be used as an interface or hobjet. It can also get other interfaces through QueryInterface, or use the objectThis macro to get the pointer of the object implementing the two-way linked list. In short, it is a normal LCOM interface, However, there is no DLIST related interface function, which can directly access interface variables__ dlist_pNext and__ dlist_ Last, these two variables are pointers to IDListVar, which can be used as hobjet or IDList * *. So we achieved our goal and implemented a two-way linked list interface, in which there are no DLIST related functions, but only two variables. It seems that it is more convenient for us to put these variables in front of the traditional implementation of plcom than the traditional implementation of plcom. The possible advantage of this method is that an object can be placed in different data structures, for example, in two different linked lists (the interface may need to be redefined), or in a two-way linked list on the one hand and a binary tree on the other, There is no need to consider that the two data structures compete to occupy the front of the object data structure (in fact, the advantage of the front is that the offset is fixed to zero, and there is no need to store an additional offset. The LCOM interface has its own offset, which can solve this problem).
In this way, if you want to support two-way linked list in an object, you can simply declare the two-way linked list interface in the object implementation structure. For example:
typedef struct _sMandelBrotApp { OBJECT_HEADER INTERFACE_DECLARE(IGLApp) GLAPP_VARDECLARE DLIST_VARDECLARE GLuint m_program; double psize; double pfromx; double pfromy; int psizeLoc; int pfromLoc; int vertexLoc; int inited; int x, y, w, h; }sMandelBrotApp; OBJECT_FUNCDECLARE(mandelbrotapp, CLSID_MANDELBROT); GLAPP_FUNCDECLARE(mandelbrotapp, CLSID_MANDELBROT, sMandelBrotApp); DLIST_FUNCIMPL(mandelbrotapp, CLSID_MANDELBROT, sMandelBrotApp}; OBJECT_FUNCIMPL(mandelbrotapp, sMandelBrotApp, CLSID_MANDELBROT); QUERYINTERFACE_BEGIN(mandelbrotapp, CLSID_MANDELBROT) QUERYINTERFACE_ITEM(IID_GLAPP, IGLApp, sMandelBrotApp) QUERYINTERFACE_ITEM(IID_DLIST, IDList, sMandelBrotApp) QUERYINTERFACE_END
By adding three lines of code, the two-way linked list interface of LCOM is realized, and our MandelBrotApp can be maintained in a two-way linked list.
4 bidirectional linked list interface operation function
Is this achieved? Of course, the traditional two-way linked list adds two pointers to the front of the object. There are three ways to operate this two-way linked list. One is to still define the interface function to operate pNext and pLast variables to complete the linked list operation. The advantage of this method is that it can be the same as the traditional method of LCOM, such as using safe objectCall macros, but the lack is code redundancy. Each object class implements a set of exactly the same code, A master should feel unhappy. One is implemented with macros. The general support of two-way linked list is provided through macros. Of course, we can also support it here. The other is supported by a function library, so that the code can not be redundant. At the same time, some security verification can be done to ensure the security of the system. There are three methods that users can choose by themselves. The third method is recommended here. The operation functions for defining the two-way linked list are as follows. Of course, if it is not enough, it can still be realized through macros or other functions:
typedef int (*dlist_traversan_func)(IDListVarPtr item, void *param); /*Initialize a header*/ int dlistInit(IDListVarPtr list); /*Append an item to the last side of the table*/ int dlistAppendItem(IDListVarPtr list, HOBJECT item); /*Insert an entry at the beginning of the table*/ int dlistInsertItem(IDListVarPtr list, HOBJECT item); /*Connect the necklace in list2 table to the list, and list2 will be cleared*/ int dlistCancat(IDListVarPtr list, IDListVarPtr list2); /*Traverse the table list, call func for each item in the table list, and pass param to func as a parameter*/ int dlistTraversal(IDListVarPtr list, dlist_traversan_func func, void * param); /*Insert the item item before the item before*/ int dlistInsertBefore(HOBJECT before, HOBJECT item); /*Insert item item after item after*/ int dlistInsertAfter(HOBJECT after, HOBJECT item); /*Remove the item from the list of its table*/ int dlistDetach(HOBJECT item); /*Get the number of items in the table*/ int dlistItemCount(IDListVarPtr list); /*Delete each item in the table (each item will call Release)*/ int dlistRemoveAll(IDListVarPtr list);
The following is the implementation of these functions. It can be seen that although they are defined by the LCOM mode, the implementation method seems to return to the classic mode. This feeling is much better and smooth:
#include "object.h" #define IMPLEMENT_GUID #include "../include/dlist.h" #undef IMPLEMENT_GUID /*Initialize a header*/ int dlistInit(IDListVarPtr plist) { if (plist==NULL) return -1; plist->__dlist_pNext = plist; plist->__dlist_pLast = plist; return 0; } /*Append an item to the last side of the table*/ int dlistAppendItem(IDListVarPtr plist, HOBJECT item) { IDListVarPtr pitem = NULL; if (plist == NULL) return -1; objectQueryInterface(item, IID_DLIST, &pitem); if (pitem == NULL) { return -1; } pitem->__dlist_pNext = plist; pitem->__dlist_pLast = plist->__dlist_pLast; pitem->__dlist_pNext->__dlist_pLast = pitem; pitem->__dlist_pLast->__dlist_pNext = pitem; objectRelease(pitem); return 0; } /*Append an entry to the top of the table*/ int dlistInsertItem(IDListVarPtr plist, HOBJECT item) { IDListVarPtr pitem = NULL; if (plist == NULL) return -1; objectQueryInterface(item, IID_DLIST, &pitem); if (pitem == NULL) { return -1; } pitem->__dlist_pLast = plist; pitem->__dlist_pNext = plist->__dlist_pNext; pitem->__dlist_pLast->__dlist_pNext = pitem; pitem->__dlist_pNext->__dlist_pLast = pitem; objectRelease(pitem); return 0; } /*Connect the necklace in list2 table to the list, and list2 will be cleared*/ int dlistCancat(IDListVarPtr plist, IDListVarPtr plist2) { if ( (plist == NULL) || (plist2 == NULL) ) { return -1; } if (plist2->__dlist_pNext != plist2) { plist ->__dlist_pLast->__dlist_pNext = plist2->__dlist_pNext; plist2->__dlist_pNext->__dlist_pLast = plist ->__dlist_pLast; plist2->__dlist_pLast->__dlist_pNext = plist; plist ->__dlist_pLast = plist2->__dlist_pLast; plist2->__dlist_pNext = plist2; plist2->__dlist_pLast = plist2; } return 0; } /*Traverse the table list, call func for each item in the table list, and pass param to func as a parameter*/ int dlistTraversal(IDListVarPtr plist, dlist_traversan_func func, void * param) { IDListVarPtr pitem, pitemtemp; if (plist == NULL) return -1; pitem = plist->__dlist_pNext; while (pitem != plist) { pitemtemp = pitem->__dlist_pNext; if (func(pitem, param) != 0) break; pitem = pitemtemp; } return 0; } /*Insert the item item before the item before*/ int dlistInsertBefore(HOBJECT before, HOBJECT item) { IDListVarPtr pitem = NULL; IDListVarPtr pbefore = NULL; objectQueryInterface(item, IID_DLIST, &pitem); objectQueryInterface(before, IID_DLIST, &pbefore); if ( (pitem == NULL) || (pbefore==NULL) ) { objectRelease(item); objectRelease(before); return -1; } pitem->__dlist_pNext = pbefore; pitem->__dlist_pLast = pbefore->__dlist_pLast; pitem->__dlist_pLast->__dlist_pNext = pitem; pitem->__dlist_pNext->__dlist_pLast = pitem; objectRelease(item); objectRelease(before); return 0; } /*Insert item item after item after*/ int dlistInsertAfter(HOBJECT after, HOBJECT item) { IDListVarPtr pitem = NULL; IDListVarPtr pafter = NULL; objectQueryInterface(item, IID_DLIST, &pitem); objectQueryInterface(after, IID_DLIST, &pafter); if ( (pitem == NULL) || (pafter==NULL) ) { objectRelease(item); objectRelease(after); return -1; } pitem->__dlist_pLast = pafter; pitem->__dlist_pNext = pafter->__dlist_pNext; pitem->__dlist_pLast->__dlist_pNext = pitem; pitem->__dlist_pNext->__dlist_pLast = pitem; objectRelease(item); objectRelease(after); return 0; } /*Remove the item from the list of its table*/ int dlistDetach(HOBJECT item) { IDListVarPtr pitem = NULL; objectQueryInterface(item, IID_DLIST, &pitem); if (pitem == NULL) { return -1; } pitem->__dlist_pLast->__dlist_pNext = pitem->__dlist_pNext; pitem->__dlist_pNext->__dlist_pLast = pitem->__dlist_pLast; pitem->__dlist_pLast = pitem; pitem->__dlist_pNext = pitem; objectRelease(item); return 0; } /*Get the number of items in the table*/ int dlistItemCount(IDListVarPtr plist) { int count; IDListVarPtr pitem; if (plist==NULL) return 0; count = 0; while (pitem != plist) { pitem = pitem->__dlist_pNext; count++; } return count; } /*Delete each item in the table (each item will call Release)*/ int dlistRemoveAll(IDListVarPtr plist) { IDListVarPtr pitem, pnextitem; if (plist==NULL) return -1; pitem = plist->__dlist_pNext; while (pitem != plist) { pnextitem = pitem->__dlist_pNext; objectRelease((const IDList **)pitem); pitem = pnextitem; } plist->__dlist_pNext = plist->__dlist_pLast = plist; return 0; }
5 linked list header object of bidirectional linked list
As mentioned earlier, to use a circular two-way linked list, you must remember a header. The header can directly declare an IDListVar object. The advantage is that there is no need to dynamically allocate memory, so there is less maintenance of a pointer. The disadvantage is that this header cannot be used as an LCOM object. Here is an option to directly implement a header class object. When in use, you can directly declare the header class object and then manage it according to the LCOM object. You can also use isClass macro to query whether it is a header, which can reduce a lot of maintenance work.
Here is the definition:
DEFINE_GUID(CLSID_DLISTHEADER, 0x7ba10549, 0x8c42, 0x4301, 0xbb, 0xc7, 0x75, 0x5a, 0x7f, 0x8, 0x9, 0xae); IDListVar * dlistheaderCreate(); #define IsDListHeader(obj) objectIsClass(obj, CLSID_DLISTHEADER)
The implementation is as follows:
/*Header object*/ typedef struct _sDListHeader { OBJECT_HEADER DLIST_VARDECLARE }sDListHeader; OBJECT_FUNCDECLARE(dlistheader, CLSID_DLISTHEADER); DLIST_FUNCIMPL(dlistheader, CLSID_DLISTHEADER, sDListHeader); OBJECT_FUNCIMPL(dlistheader, sDListHeader, CLSID_DLISTHEADER); QUERYINTERFACE_BEGIN(dlistheader, CLSID_DLISTHEADER) QUERYINTERFACE_ITEM(IID_DLIST, IDList, sDListHeader) QUERYINTERFACE_END static const char* dlistheaderModuleInfo() { return "1.0.0-20210503.1247 Dual List Header"; } static int dlistheaderCreate(const PARAMITEM* pParams, int paramcount, HOBJECT* pObject) { sDListHeader* pobj; pobj = (sDListHeader*)malloc(sizeof(sDListHeader)); if (pobj == NULL) return -1; memset(pobj, 0, sizeof(sDListHeader)); *pObject = 0; DLIST_VARINIT(pobj, dlistheader); /*Returns the generated object*/ OBJECT_RETURN_GEN(dlistheader, pobj, pObject, CLSID_DLISTHEADER); return EIID_OK; } static void dlistheaderDestroy(HOBJECT object) { sDListHeader* pobj; pobj = (sDListHeader*)objectThis(object); free(pobj); } /* Function: judge whether the object is a valid object Parameters: object -- Object data pointer Return value: 0 -- The object is invalid 1 -- Object is valid */ static int dlistheaderValid(HOBJECT object) { return 1; } IDListVar* dlistheaderCreate() { int ret; IDListVar* pListHeader; A_u_t_o_registor_dlistheader(); ret = objectCreateEx(CLSID_DLISTHEADER, NULL, 0, IID_DLIST, (const void**)&pListHeader); if (ret == 0) return pListHeader; else return NULL; }