Handling Interdependent Objects in Automation - An Example of a Bank Server Object
Bank : This application explains component creatability with a Client application perspective. I got most of my inputs by using Microsoft Schedule+ Application . It supports lots of objects like Appointments, Alarm, Schedule, etc,. One important thing in that application is that, most of these components, any Client application can't create as it is. What I mean by that, in VC++, Client can't creat these compnents by calling CoCreateInstance function, as these classes don't have coclass property attached to them. Similary in VB Client, you can't just say " Dim AlarmObj as New Alarm ". That means, there are objects which the Microsoft Schedule+ server exposes, which the Client applications can't directly create. So, to creat them, user uses some other objects (whcih can be created directly). For example, assume a case where a SingleAppointment object is there with you. You can create a Alarm Object by calling one of its method i.e. " Set AlarmObject = SingleAppointments.Alarm ". This is very nice feature, which enable lots of inherent security in your object design.
I have developed this server, keeping all these things in mind. It has been developed using ATL and VC++ 5.0. I have also written two sample Test applications to demonstrate my point of view. One test application is written using VB 5.0 and other is written using VC++ 5.0.
For simplicity sake, I have considered only three objects. viz. Bank , IAccount , IAccountManager. One more thing that I have done in this server application is that, it implements COM Collection class. It also shows how to use STL vector library. The server is a inproc server and has MFC support also.
There is very well defined clear heirarchy existing in creating the three components the server exposes. In fact initially Client application can creat only one (i.e Bank Object) of these three components. Other components (AccountManager and Account) can't be created. Of course in real life, an AccountManager and an Account can't exist, without being related to particular bank.
As mentioned earlier, the Client will creat first the Bank object. Once he is throug with this, he will ask the created Bank object, to get the AccountManager using one of its methods (i.e. GetAccountManager()). Once he gets the AccountManager, he will go for creating an account. The AccountManager object, acts like a collection class. He maintains a list of Account that are there in the Bank. The client calls Add method of AccountManager to create a new Account. If the AccountManager feel it is ok to create a new account, it will return a Account object. Thus in turn, Bank object is managing the whole show. The whole control is with the Bank Object.
One main thing to observe is the IDL file. In this you can clearly make out that we have AccountManager and Account objects.
------------------- // BankAccount.idl : IDL source for BankAccount.dll // // This file will be processed by the MIDL tool to // produce the type library (BankAccount.tlb) and marshalling code. import "oaidl.idl"; import "ocidl.idl"; [ uuid(68B00060-801D-11D2-A7DE-00C04F79FED8), version(1.0), helpstring("BankAccount 1.0 Type Library") ] library BANKACCOUNTLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); typedef enum { ACCOUNT_SAVINGS, ACCOUNT_CURRENT, ACCOUNT_MISC }Enum_Account_Type; [ object, uuid(68B00071-801D-11D2-A7DE-00C04F79FED8), dual, helpstring("IAccountManager Interface"), pointer_default(unique) ] interface IAccountManager : IDispatch { [id(1), helpstring("Add an account")] HRESULT Add([out, retval] IDispatch** ppAccount); [id(2), helpstring("Remove the current selected item (if any)")] HRESULT Remove(); [id(3), helpstring("Item get the One based Indexed Account")] HRESULT Item([in] short int nIndex,[out, retval] IDispatch** ppAccount); [id(4), helpstring("Returns Number of Accounts ")] HRESULT Count([out, retval] short int* nNoOfAccounts); [id(-4), helpstring("method _Enum")] HRESULT _Enum([out, restricted] short int* pSafeArray); }; [ object, uuid(68B00073-801D-11D2-A7DE-00C04F79FED8), dual, helpstring("IAccount Interface"), pointer_default(unique) ] interface IAccount : IDispatch { [propget, id(1), helpstring("property Type")] HRESULT Type([out, retval] Enum_Account_Type *pVal); [propput, id(1), helpstring("property Type")] HRESULT Type([in] Enum_Account_Type newVal); [propget, id(2), helpstring("Account Holder's Name")] HRESULT Name([out, retval] BSTR *pVal); [propput, id(2), helpstring("Account Holder's Name")] HRESULT Name([in] BSTR newVal); }; [ object, uuid(68B0006F-801D-11D2-A7DE-00C04F79FED8), dual, helpstring("IBank Interface"), pointer_default(unique) ] interface IBank : IDispatch { [id(1), helpstring("Method to get AccountManager object of the Bank")] HRESULT GetMeAccountManager([out, retval] IDispatch** ppAccountManager); }; [ uuid(68B00070-801D-11D2-A7DE-00C04F79FED8), helpstring("Bank Class") ] coclass Bank { [default] interface IBank; }; /* [ uuid(68B00072-801D-11D2-A7DE-00C04F79FED8), helpstring("AccountManager Class") ] coclass AccountManager { [default] interface IAccountManager; }; [ uuid(68B00074-801D-11D2-A7DE-00C04F79FED8), helpstring("Account Class") ] coclass Account { [default] interface IAccount; }; */ };Probably there might be some more articles about Collection class objects.
VB Test project sources zip file name here - 18 KB
Bank Server Download source - 53 KB
Comments
Usefull to me
Posted by Legacy on 03/27/2003 12:00amOriginally posted by: swordingliang
I have succeedfully compiled and run the client app
Replyand server app.But Is this means it is possible
for them to transfer large datas without errors!
Error while executing VCBankTest file
Posted by Legacy on 04/05/2002 12:00amOriginally posted by: anandkumar
Excellent idea but one small bug found...
Posted by Legacy on 01/01/2000 12:00amOriginally posted by: Craig Green
This was an excellent article but I did find one small (but signicant) bug. When you queried the Bank to get an IDispatch of an AccountManager an extra QueryInterface was added into your method that should not have occurred:
hr = pAccountManagerObj->QueryInterface(IID_IAccountManager,reinterpret_cast<void**>(&pIAccountManager;));
Since you are attempting to retrieve the IDispatch there is no reason to get a pointer to IAccountManager (this is done on the client side).
Although a small bug it did cause an automation server (out of process) to stay in memory since some of the objects still had references to them even though the main object (the Bank in your case) was released correctly.
A correct version of the method should be:
STDMETHODIMP CBank::GetMeAccountManager(IDispatch** ppAccountManager)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
HRESULT hr;
IAccountManager* pIAccountManager = NULL;
CComObject<CAccountManager>* pAccountManagerObj = NULL;
LPDISPATCH pDispatch = NULL;
hr = CComObject<CAccountManager>::CreateInstance(&pAccountManagerObj;);
if(FAILED(hr))
return hr;
hr = pIAccountManager->QueryInterface(IID_IDispatch,reinterpret_cast<void**>(ppAccountManager));
if(FAILED(hr))
{
delete pAccountManagerObj;
pAccountManagerObj = NULL;
return hr;
}
return hr;
}
This same problem holds true in the AccountManager class when getting an IDispatch to an Account.
Keep up the good articles - there should be more articles like this about object design as opposed to the abundance of utility classes. Thanks for the ideas...
ReplyQuestion
Posted by Legacy on 01/24/1999 12:00amOriginally posted by: Hai NGuyen
Hi Shashi Kumar M. Kolavi , how do you do!
Im Hai Nguyen, a Vietnamese and i am living in VietNam. Now i am studying MSMQ(Microsoft Message Queue), I want to send message between two site, so I don't know how to do it. Can you help me! If you know informaion about it, please send it for me ASAP following email address hainguyen@psv.com.vn. thank you very must.
Best regards,
HaiNguyen.