Discussion on MIDAS Application Development with C ++ Builder

zhaozj2021-02-08  274

On the day of the 914 incident, Ben_Ladan (Lan Penguin) brother a post in the 9CBS BCB version and asked a question about Midas development with BCB. Just this problem is, because more than a year ago (accurate September 4, 2001) Luhongjun (over Jiang Bo Yu) brother once in the BCB version of the post about Midas developed, there is similar The problem, the two questions that solve the brother in the same year are also the high people of the BCB version: Holyfire (^ @ l @ ^) and alng (?). I am very pleased that this discussion has joined several new high people: Kingcaiyao (AKING), PPOWER (), etc., the study of this problem is deeper, and it is believed that people who have seen this offer are not shallow. I put these contents and made some further analysis.

To facilitate discussion, write an example program, different from the Ben_Ladan brother's procedure and Luhongjun brother, but the principle is the same: the server program is shown in the figure:

This is an ordinary remoteDataModule MiDAs intermediate server. Use an Adoconnection, an adodataset, a DataSetProvider. The CommandText property of the adotest (adodataset) is not set. Add a method in the IrdmTest interface: SetQuery, with a parameter with a BSTR type: Atablename, as shown below:

After refreshing, generate the implementation of the setQuery method, complete its implementation as follows (where M_DataModule marked is red is one of the problems to be discussed later):

STDMETHODIMP TRDMTESTIMPL :: SetQuery (BSTR Atablename)

{

Try

{

M_DataModule-> adotest-> commandtext = ansistring ("select * from") atablename;

}

Catch (Exception & E)

{

Return Error (E.Message.c_str (), IID_IRDMTEST;

}

Return S_OK;

}

Compile the server server to register. Then write a client program, as shown below:

Generate two buttons of the event response and implementation, the code is as follows (the part marked as red below is another problem to be discussed):

/ / -------------------------------------------------------------------------------------------- ---------------------------

// setQuery

Void __fastcall tform2 :: Button1click (TOBJECT * SENDER)

{

CONN-> open ();

IrdmtestDisp V;

v.bind (iDispatch *) conn-> appserver);

Try {

V-> setQuery (WideString (edit1-> text);

cdstest-> open ();

}

Catch (...) {

}

v.unbind ();

// You can operate data here.

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

// Closeall

Void __fastcall tform2 :: button3click (TOBJECT * Sender)

{

CDSTEST-> Close (); conn-> close ();

}

/ / -------------------------------------------------------------------------------------------- ---------------------------

The above program can run normally. Now you can start discussing the two problems mentioned earlier:

Why do you have to use m_datamodule to access a remote data module in the server, instead of using RDMTest this global pointer variable? Why do you have bind the server's scheduling interface (IDSPATCH *) Conn> AppServer to bind the server's scheduling interface () instead of binding to bindDefault ()?

First of all, see the first question: Familiar with delphi will be very strange why setQuery is a member of the Trdmtestimpl class, not a member of the Rdmtest class, and in Delphi, there is no trdmtestimpl class. This is because Delphi and C Builder have different technologies in COM. In Delphi, the FrameWork: Dax developed by Borland is used in Delphi, and in C Builder is implemented with ATL. Look at the RemoteDataModule and C Builder's RemoteDataModule and C Builder in the DataBRK.Pas unit, the following is a class diagram:

The TCREMOTAMODULE in the figure is the REMOTEDATAMODULE class prepared for C Builder, which is the biggest difference between the TremoteDataModule class used by Delphi: it does not implement the Iappserver interface, and TremoteDataModule is there. The trdmtest is RemoteDataModule in the foregoing example program, while iDrdmtest / TDRDMTEST is to compare Delphi corresponding interfaces and classes. It can be seen that Delphi can implement the core function of MIDAS as long as these interfaces / classes, but C Builder can't do it, it hasn't implemented the Iappserver interface. So how is C Builder implement? Look at the following class diagram, it is based on the contents of the Rdmtestimpl.h file (Note: Stereotype in the picture below shows the module board parameters):

Of course, in the rdmtestimpl.h file, the macro is used to realize the derived of the TrDMTestImpl class. The above figure is to expand and go up to several hierarchies. Obviously, this is a multi-derived, Trdmtestimpl class from ATL template CComObjectRoTex and CCOMCOCLASS and Iappserver interface implementation class IappserverImpl, and in the previous diagram: Trdmtest is derived from VCL TcRemoteDataModule. We know that the VCL class does not support multiple derivation, so there is no additional processing in C Builder. First, you can see that the IrdmTest interface is derived from Iappserver, so SetQuery is a method of Irdmtest, of course, a member of trdmtestimpl, not a member of Trdmtest. The three classes of IdispatchImpl, IappServerImpl, TrDMTestImpl, Idispatch, IAPServer, IAPSERVER, IRDMTEST interface are implemented from the IrdmTest interface, which is implemented by templates, respectively. Because in Midas, the client calls the server's Iappserver interface by creating a Remote COM object, i.e., when the client is connected to the server, the server will start an instance of an IAPServerImpl. In order to connect RemoteDataModule, the IappServerImpl class implements an instance of a Trdmtest class through the template: M_DataModule, because each client connection corresponds to an instance of IappserverImpl, that is, each client has a separate RemoteDataModule instance. The following code comes from the atlvcl.h file, which is the code of bufanxiong (Buffanxiong), which means how IappserverImpl is connected to RemoteDataModule via m_datamodule. (Note: The DM class is actually the type incoming passage of the template parameter, in this example, Trdmtest) DM * m_datamodule; // the core.

// Note: this data module _must_ descend from tcremoteDataModule.

IappserverImpl ()

{

m_datamodule = New DM (NULL);

}

~ Ketpserverimpl ()

{

m_datamodule-> free ();

}

And the global RDMTest object pointer is definitely not a RemoteDataModule instance associated with the current IappserverImpl instance, so in the problem one, M_DataModule must be used without using rdmtest, otherwise call SetQuery Settings the Adodataset, which is set, set, will definitely opens with ClientDataSet Open The adodataset is not the same, of course, will be wrong. Basic PPOWER () brother understanding is relatively correct, but it is important to note that IAPPServerImpl is not from ATL, it is part of the Midas of BCB, as for the Midas implemented with ATLs to be more than Midas implemented with DAX It's hard to say. But it is certain that Dax is sure to be simpler than using ATL. Let's see the second question, why can't you use binddefault. First look at how BindDefault is implemented in the IrdMTestDISP class in the server_tlb.h file:

// ******************************************************** ******************** //

// DISPINTF: Irdmtest

// Flags: (4416) Dual Oleautomation Dispatchable

// GUID: {1BB59F63-93D0-4FC2-9D5C-5DAE096C38D2}

// ******************************************************** ******************** //

Template

Class IrdmtestDispt: Public Tautodriver

{

PUBLIC:

...

HRESULT BINDDEFAULT ()

{

Return Olecheck (Bind (CLSID_RDMTEST));

}

Typedef IrdmTestDispt IrdmTestDisp;

It calls an overload version of the BIND function in the Tautodriver template class, see Utilcls.h

// Bind Via Guid

//

Template HRESULT

Tautodriver :: Bind (const guid & clsid)

{

LPunknown punk = 0;

HRESULT HR = CoClassCreator :: CoCreateInstance (CLSID, IID_IUNKNOWN, (LPVOID *) & PUNK);

En (ac))

{

// We Should Have a Valid Interface Pointer

//

_Asserte (Punk / * Must Have Valid iUnknown Pointer * /);

// Run Object - Just In Case

//

HR = :: Olerun (punk);

// bind to Running iUnknown

//

En (ac))

HR = bind (punk);

// Release IUNKNOWN

//

punk-> release ();

}

Return HR;

}

Note that the code marked as a red portion is called here, call CocreateInstance here to create a new server instance, that is, the server creates a new IappserverImpl instance, which is created with the client program through the DCOMCONNECTION connection to the IAPPServerImpl Examples are independent of each other. This means that if the client is connected to the server via binddefault, then call SetQuery to operate, this IappserverImpl instance will be released when the client calls the IrdmTestDISP class, that is, the operation just used by SetQuery is all Improval, there is no impact on the IappserverImpl instance connected to the DCOMConnection, because they are not the same, of course it will be wrong. Look again what if you use Bind (iDispatch *) conn-> appserver)? At this time, another overload version of the BIND function in the Tautodriver template class will be called, see utilcls.h // bind via iUnknown

//

Template HRESULT

Tautodriver :: Bind (lpunknown punk)

{

_Asserte (punk / * must bind to non-null interface pointer * /);

HRESULT HR = E_POINTER;

IF (punk)

{

Dispintf * DISP;

HR = punk-> queryinterface (__ uuidof (dispintf), (lpvoid *) & data;

En (ac))

Bind (DISP, FALSE / * DON't AddRef * /);

}

Return HR;

}

At this time, you will not create a new instance, but through QueryInterface to achieve the corresponding instance pointer. As for why the conn-> appserver is converted to idispatch * because: appserver is a Variant type (for convenient to make a Late Binding mode call), and the Variant type is the compatibility type of iDispatch *, and idispatch is derived from iUnknown, so you need first Convert, otherwise the type is incompatible with compatibility.

Finally, I said that Ben_Ladan (Lan Penguin) brother mentioned another question, in Server.cpp:

Tcommodule _ProjectModule (0 / * initATlserver * /);

Tcommodule & _Module = _ProjectModule;

// The ATL Object Map Holds An Array of _tl_Objmap_ENTRY STRUCTURES THAT

// Described The Objects of Your Ole Server. The map is handed to your

// Project's ccommodule-deerived _module object via the init method.

//

Begin_Object_map (ObjectMap)

Object_entry (CLSID_RDMTEST, TRDMTESTIMPL)

END_OBJECT_MAP ()

What _module is doing? The explanation of PPOWER () brothers is wrong. Because the following paragraph is not used between the MAP code is not used between the VCL class and the C class, it is not related to _Module. First, see the definition of these three macros in ATLCOM.H (because the code is too long, make a break process): # define begin_object_map (x) static _tl_objmap_entry x [] = {

#define end_object_map () {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

#define Object_ENTRY (CLSID, CLASS) {& CLSID, CLSS :: UpdateRegistry, /

Class :: _ ClassFactoryCreatorClass :: CreateInstance, /

Class :: _ Creatorclass :: CreateInstance, Null, 0, Class :: getObjectDescription, /

Class :: getcategorymap, class :: objectmain},

It can be seen that this MAP is only used to build an array of _ATL_OBJMAP_ENTRY classes. The following is the definition of this class in ATLBASE.H:

Struct _tl_objmap_ENTRY

{

Const CLSID * PCLSID;

HRESULT (BOOL BREGOSTER)

_TL_CREATORFUNC * PFNGETCLASSOBJECT;

_TL_CREATORFUNC * PFNCREATEINSTANCE;

IUNKNOWN * PCF;

DWORD DWREGOSTER;

_TL_DESCRIPTIONFUNC * PFNGETOBJECTDESCRIPTION;

_TL_catmapfunc * pfNgetcategoryMap;

HRESULT WINAPI REVOKECLASSOBJECT ()

{

Return CorevokeClassObject (dwregister);

}

HRESULT WINAPI RegisterClassObject (DWORD DWCLSCONTEXT, DWORD DWFLAGS)

{

IUNKNOWN * P = NULL;

IF (pfNgetClassObject == null)

Return S_OK;

HRESULT HRES = PFNGETCLASSOBJECT (PfncreateInstance, IID_IUNKNOWN, (LPVOID *) & P);

"succeeded (hres))

HRES = CoregisterClassObject (* PCLSID, P, DWCLSCONText, DWFLAGS, & DWREGISTER);

IF (p! = null)

P-> Release ();

Return HRES;

}

// Added in ATL 3.0

Void (WinApi * PfnObjectmain) (BOOL BSTARTIN);

}

This is clear, _ATL_OBJMAP_ENTRY class is initialized for COM objects, which provides functions of the object's registration / anti-registration / createInstance / getClassObject (used to get classfactory). When a program contains multiple COM classes, you need to use this data to maintain initialization implementation of each class. So what is the relationship between these initialization achievements? In fact, _Module is very like Comserver in Dax. The following code comes from atlmod.h, _Module and Objectmap (an array of front-macro defined) is used here. // _Module is assoced to be a reason to a tcommodule

// User May Define _Module to be a ref. To an instance of a class derived

// from tcommodule.

//

Typedef Tatlmodule Tcommodule;

EXTERN TCOMMODULE & _MODULE;

...

// to be defined in The Project's Source

//

EXTERN _ATL_OBJMAP_ENTRY ObjectMap [];

Carefully study AtLmod.h's implementation of TatlModule types, it can be found that its constructor calls member functions initATlserver, which is why there will be: tcommodule _projectModule in server.cpp: Tcommodule _ProjectModule; In initATlserver, _module calls the ccommodule member init for initialization, where one of the parameters used is ObjectMap. To this _Module's use is completely clear: it is an implementation object of the initialization class of the COM application, and it is initialized by the ObjectMap array for different COM class objects in the COM application. The following code comes from the COM of the DLL mode written by BCB. Four initial letters are called to _Module's corresponding function calls.

// entry point of your server invoked to inquire WHETHER THE DLL IS NO

// Longer in Use and shop be unloaded.

//

StDAPI __Export DllcanunloadNow (Void)

{

Return (_Module.GetLockCount () == 0)? s_ok: s_false;

}

// Entry Point of Your Server ALLOWING OLE TO RETRIEVE A CLASS OBJECT

// Your Server

//

StDAPI __EXPORT DLLGETCLASSOBJECT (Refclsid Rclsid, Refiid Riid, LPVOID * PPV)

{

Return_Module.getClassObject (RCLSID, RIID, PPV);

}

// entry point of your server invoked to instruct the server to create

// registry entries for all classes supported by the module

//

Stdapi __export dllregisterServer (void)

{

Return_Module.RegisterServer (True);

// entry point of your server invoked to instruct the server to remove, INSTRUCT

// All Registry Entries Created THROUGH DLLREGISTERSERVER.

//

Stdapi __export dllunregisterServer (void)

{

Return_Module.unregisterServer ();

}

This technical discussion is very helpful. In order to write this article, I read a lot of source code in BCB in this week, I feel that there is a great harvest, I hope this article also has a certain help to readers.

[Mental Studio] Raptor Sep.22-02

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

New Post(0)