Sunday, August 24, 2008

Building Server And Client

Creating the Server

Building a component in ATL is a four-step process:
(a) Creating a module
(b) Adding a component to the module
(c) Adding methods to the component
(d) Adding properties to the component We would create a module
(usually a DLL) called CalcServer.
Then we will create a component called Calculator within this server. Then we would add 15 methods and one property to it. Let us carry out these steps now.
Creation Of Module
To create a module the Developer Studio provides an ATL COM AppWizard. This is the easiest of jobs. Carry out the following steps:
(a) Select ‘New’ from the ‘File menu. From the dialog that pops up select ‘ATL COM AppWizard’ as the project. Type ‘CalcServer’ as the project name and click ‘OK’ to continue.
(b) Select type of module as ‘Dynamic Link Library’ and click ‘Finish’.
(c) Now a ‘New Project Information’ dialog is displayed which lists the name of files that the wizard would create. Click on ‘OK’.
Now that the module stands created let us add a component to it.
Adding Component To The Module
To add component to the module we can use ‘ATL Object Wizard’. Carry out the following steps for adding the component named ‘Calculator’ using this wizard:
(a) Select ‘Insert | New ATL Object’ menu item. This would display the ‘ATL Object Wizard’ dialog. Select ‘Simple Object’ from the various object categories and click on ‘Next’.
(b) A ‘ATL Object Wizard Properties’ dialog is displayed.
(c) The property sheet contains two tabs-Names and Attributes.
The Names tab is divided into two sections. The first section displays the ‘C++ names’ and the second section the ‘COM names’. Enter the ‘Short Name’ as 'Calculator'. As soon as you do this all other edit controls would be filled automatically. The names filled under the edit controls of ‘C++ names’ indicate that the class CCalculator will implement the object Calculator in the files ‘Calculator.h’ and ‘Calculator.cpp’. The names filled under the edit controls of ‘COM names’ indicate that the CoClass name (component class) remains the same as the short name. The Interface name will be ICalculator. The type is a description for the class. The ProgID will be.. When the ‘OK’ button is clicked, the class CCalculator and the interface ICalculator are created. They can be viewed from the class view tab.
Adding Methods To The Component
The component that has been added does not contain any functionality. To provide functionality we should add several methods to it. Here we would show addition of one method. On similar lines you should add the rest.

(a) Switch to class view tab. Select the interface ICalculator and click the right mouse button. From the menu that pops up select ‘Add Method’. Note that the interface ICalculator appears twice in the class view. Any one can be used to add methods.
(b) In the ‘Add Method to Interface’ dialog specify the method name as Add and parameters as [in] double n1, [in] double n2, [out, retval] double *n3. Click on ‘OK’. Here [in] specifies the value to be passed to the method and [out, retval] specifies the return value.
(c) Follow a similar procedure for adding the following methods:

Subtract ( [in]double n1, [in]double n2, [out, retval] double *n3 ) ;
Multiply ( [in] double n1, [in]double n2, [out, retval] double *n3 ) ;
XpowY ( [in] double n1, [in]double n2, [out, retval] double *n3 ) ;
Divide ( [in] double n1, [in]double n2, [out, retval] double *n3 ) ;
onebyx ( [in] double n1, [out, retval] double *n2 ) ;
Percent ( [in] double n1, [in]double n2, [out, retval] double *n3 ) ;
sqrroot ( [in]double n1, [out, retval] double * n2 ) ;
Sineval ( [in]double n1,[out, retval]double *n2 ) ;
CosVal ( [in]double n1,[out, retval]double *n2 ) ;
tanvalue ( [in]double n1,[out, retval] double * n2 ) ;
Xpow3 ( [in]double n1, [out, retval]double *n2 ) ;
Xpow2 ( [in]double n1, [out, retval]double *n2 ) ;
Nfact ( [in]int n1,[out, retval]double *n2 ) ;
Log10 ( [in]double n1, [out, retval]double *n2 ) ;

Adding the Add method creates a function definition in ‘Calculator.cpp’ as shown below.

STDMETHODIMP CCalculator::Add ( double n1, double n2, double *n3 )

{

// TODO: Add your implementation code here

return S_OK ;

}

We can add the following line to the Add( ) method by double clicking it from the Class view tab:

*n3 = n1 + n2 ;

On similar lines complete the definitions of the other 14 methods.
Adding Properties To The Component
A property helps expose a member variable of the server. The client can use this variable. For every property that we expose there are two member functions in the server: one that puts the value in the variable and another that gets the value of the variable. Carry out the following steps to add the property.
(a) Switch to class view tab. Select the interface ICalculator and click the right mouse button. From the menu that pops up select ‘Add Properties’.
(b) In the ‘Add Property to Interface’ dialog specify the property type as BSTR and property name as Memval. Check the ‘Get Function’ and the ‘Put function’ from the dialog and click on OK.
This adds the following methods to the server in ‘Calculator.cpp’ as shown below:

STDMETHODIMP CCaluculator::get_Memval ( double *pVal )

{

AFX_MANAGE_STATE(AfxGetStaticModuleState( ) )

// TODO: Add your implementation code here

return S_OK;

}

STDMETHODIMP CCaluculator::put_Memval ( double newVal )

{

AFX_MANAGE_STATE ( AfxGetStaticModuleState( ) )

// TODO: Add your implementation code here

return S_OK ;

}

Now add the following line to the get_Memval( ) method:

*pVal = memory ;
Similarly add the following line to the put_Memval( ) method.

memory = newVal ;
Note that the variable memory must be added as a private member variable of the CCalculator class using ClassWizard. Now if we build the project, a DLL file would be created containing the component having fifteen methods and one property. Now what remains to be done is building a client that would use these methods and property.
Creating the Client
After knowing how to create a component (server) using ATL COM. now we would see how to develop a client that makes use of this component. We would develop the client as a simple dialog-based application. But before we do that let me state a few facts about the server that we developed while building the server. While building the component we first created a module called CalcServerr. Then we created a component called Calculator within this server. Lastly we added methods and a property to it.
Creating A COM Client
The steps to create a COM Client using MFC are:
Step 1: Create a dialog-based project using AppWizard (EXE). The dialog acts as an interface between the client can feed the numbers that interface with the server through the client.
Step 2: Add buttons to the dialog.
Step 3: Import the server’s Type Library into the client. This is done by adding the following two statement to the file ‘StdAfx.h’. # import “..\CalcServer\CalcServer.tlb” using namespace CALCSERVERLib ; When we compile the client project # import keyword creates the header for the wrapper class in a file with .tlh extension and the implementation in another file with extension .til. These files are present in the Debug directory of the client’s project workspace. The name of the wrapper class is the server workspace name plus the keyword Lib. In our case it turns out to be CALCSERVERLib. The wrapper class contains information about the server that is used while accessing the methods defined in the server from the client. This wrapper class hides the details of reference counting and permits us to throw errors.
Step 4: Initialize the COM Library so that the application can call COM functions. The client can interact with the server through the COM Runtime Library. To initialize the COM Library, call CoInitialize( ) function from InitInstance( ) as shown below:
CoInitialize (NULL ) ;
Step 5: Retrieve the CLSID of the server to be able to instantiate the server. As it is difficult to remember the CLSID it can be retrieved using the ProgID through the function CLSIDFromProgID( ) as shown below:

BOOL CCalcClientDlg::OnInitDialog( )

{

// AppWizard generated code

// TODO: Add extra initialization here

CoInitialize ( NULL ) ;

CLSID clsid ;

HRESULT hr ;

hr = CLSIDFromProgID( OLESTR ( "CalcServer.Caluculator" ), &clsid ) ;

if ( FAILED ( hr ) )

MessageBox ( "Unable to access server" ) ;

}

Here OLESTR( ) function has been called to converts the character string to the ProgID format.
Step 6: Using the CLSID of the component create an instance of the COM Server component. The CoCreateInstance( ) function is used for this purpose as shown below:

BOOL CCalcClientDlg::OnInitDialog( )

{

// AppWizard generated code

// TODO: Add extra initialization here

// calls to CoInitialize and CLSIDFromProgID( )

hr = CoCreateInstance ( clsid, NULL, CLSCTX_ALL, __uuidof ( ICaluculator ), ( void ** ) &p ) ;

if ( FAILED ( hr ) )

MessageBox ( "cocreate failed" ) ;

}

Call to CoCreateInstance( ) creates an instance of the component IMyMath and returns the interface address in the pointer math. The parameter CLSCTX_INPROC_SERVER indicates that our server is a DLL. The __uuidof( ) function has been used to obtain the id of the interface.
Step 7: Use the COM Object. Now that we have obtained the interface pointer, the client application can call the methods of the COM server object as shown below:

void CCalclientDlg::OnEqual( )

{

if ( m_oper == ' ' )

return ;

double num3 ;

GetDlgItemText ( IDC_DISPLAY, m_str ) ;

m_num2 = atof ( m_str ) ;

switch ( m_oper )

{

case '+' :

num3 = p -> Add ( m_num1, m_num2 ) ;

break ;

case '-' :

num3 = p -> Subtract ( m_num1, m_num2 ) ;

break ;

case '*' :

num3 = p -> Multiply ( m_num1, m_num2 ) ;

break ;

case '^' :

num3 = p -> XpowY ( m_num1, m_num2 ) ;

break ;

case '/' :

if ( m_num2 == 0 )

{

m_over = true ;

m_decipoint = false ;SetDlgItemText ( IDC_DISPLAY, "Cannot divide by zero." ) ;

return ;

}

else

num3 = p -> Divide ( m_num1, m_num2 ) ;

}

m_str.Format ( "%.8g", num3 ) ;

if ( m_str == "0" )

m_str = "0." ;

m_over = true ;

m_decipoint = false ;

m_oper = ' ' ;

SetDlgItemText ( IDC_DISPLAY, m_str ) ;

}

Note that the methods of the COM server object Add( ) and Subtract( ) have been called using the interface pointer math obtained in the call to CoCreateInstance( ).
Step 8: Uninitialize the COM Object. This is possibly the simplest job. Simply call the function CoUninitialize( ) at the end of InitInstance( ) function as shown below:

BOOL CAtlClientApp::InitInstance( )

{

// AppWizard generated code

CoUninitialize( ) ;

}

With that we are through with the creation of the client. Now you can compile and execute the client and check out whether it is able to interact with the methods in the server.

No comments: