|
TYX Corporation
MFC Client for TpsServer The purpose of this document is to write a client for the TpsServer. Knowledge of MFC and COM is recommended, but not required. For those that have questions about MFC and/or COM, I would recommend that they refer to literature that addresses those issues since this document will not attempt to teach you either one even if it will provide a lot of details on how to make this client sample work. The usage of the TpsServer will be asynchronous. It will allow the client to access the TpsServer functionality, and it will also process the notifications from the TpsServer. The client will include an MFC GUI. The commands to the Wrts will go directly to the TpsServer. The notification that comes from the TpsServer will go through a COM Sink client. Notes:
We will first generate the dialog and allow the MFC GUI dialog buttons to control the TpsServer. As we generate the COM Sink object to deal with the notifications, we will add the functionality that will allow for the COM Sink object to be generated and for the connectivities to be made. 1 Creating the MFC GUI dialog1.1 Adding Buttons You will want to create a new MFC AppWizard (exe).
The name will be WrtsClient
Click on OK. Select Dialog based
Click on Next >. Unselect Active X Controls and About box.
And then press on Next >. Include MFC statically in order to be less dependent upon the OS environment. This would be a necessary step for usage of the client on an embedded system. For a complete OS, you may be able to use MFC dynamically in order to generate a smaller exe. In our case, we will select As a statically linked library.
We can press Next > and accept the default class name.
Press Finish. Click OK on the New Project Information window that follows. You should have access to the following window:
Our first step is to verify that the code builds and runs. The purpose at this point is to delete the unnecessary buttons and add the ones that we want to support.
Make sure that you can build without errors and run the exe. We will now add the functionality to the dialog box. 1.2 Connecting to the TpsServerThe purpose of what we want to do now is to load the TpsServer when you open the client. We will want to unload when we close the client. For this, we will add some code that you will want to add, no matter what your client looks like:
#include
<afxwin.h> // MFC core and
standard components #include
<afxext.h> // MFC
extensions #include
<afxdtctl.h> // MFC
support for Internet Explorer 4 Common Controls #ifndef
_AFX_NO_AFXCMN_SUPPORT #include
<afxcmn.h> // MFC
support for Windows Common Controls #endif //
_AFX_NO_AFXCMN_SUPPORT // New Lines added by TYX #import
"C:\usr\tyx\com\RtsAx.dll" raw_interfaces_only \ raw_native_types, named_guids
rename_namespace("RTSAX") #include <atlbase.h> void handleError(HRESULT
hr); //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. Please note that the path to the RtsAx.dll should be the one that you have on your computer. The #include and the #import are necessary. The handleError will contain a function that will handle errors. The existence of this function is not vital, but the function that we propose and it’s content are highly recommended.
#include
"stdafx.h" void handleError(HRESULT
hr) { if (SUCCEEDED(hr)) return; USES_CONVERSION; CComPtr<IErrorInfo> pErrorInfo; CString errMsg; if (::GetErrorInfo(0, &pErrorInfo) == S_OK) { CComBSTR bstrDscr; if
(SUCCEEDED(pErrorInfo->GetDescription(&bstrDscr))) errMsg.Format(_T("COM Error: 0x%08X %s"),
hr, OLE2T(bstrDscr)); else errMsg.Format(_T("COM Error: 0x%08X"),
hr); } else { // ::GetErrorInfo may return S_FALSE TCHAR* pszMsg = NULL; DWORD nChars = ::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&pszMsg, 0, NULL); if (nChars) errMsg.Format(_T("COM Error: 0x%08X %s"),
hr, pszMsg); else errMsg.Format(_T("COM Error: 0x%08X"),
hr); // Free the buffer. if (pszMsg != NULL) ::LocalFree((HLOCAL)pszMsg); } // Display the com error: ::MessageBox(NULL, errMsg, _T("Error"), MB_OK); }
// Generated message map functions //{{AFX_MSG(CWrtsClientDlg) virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); //}}AFX_MSG DECLARE_MESSAGE_MAP() CComPtr<RTSAX::ITpsServerEx> m_pTpsServerEx; };
BOOL
CWrtsClientDlg::OnInitDialog() { CDialog::OnInitDialog(); // Set the
icon for this dialog. The framework
does this automatically //
when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); //
Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here // create TpsServer HRESULT hr =
m_pTpsServerEx.CoCreateInstance(RTSAX::CLSID_TpsServer); if (FAILED(hr)) { handleError(hr); return FALSE; } // attach tpsserver to wrts hr = m_pTpsServerEx->Attach(); if (FAILED(hr)) { handleError(hr); return FALSE; } return TRUE; // return TRUE unless you
set the focus to a control } CoCreateInstance creates a TpsServer object. Attach() is a method that will create an instance of the Wrts locally. Note: If you wish to
create an instance of the Wrts on a remote computer, you will want to pass on
the IP address of that computer as an parameter for Attach. Here is an example
of such call: m_pTpsServerEx->Attach(CComVariant(“192.168.100.164”)); Please note that you will need to set the proper privileges on the computers in order to achieve this.
void
CWrtsClientDlg::OnDestroy() { CDialog::OnDestroy(); // TODO: Add your message handler code
here m_pTpsServerEx.Release(); }
Summary: Here, you have seen code associated to 2 separate issues:
2 The sink object:At this point, you will want to save your project and copy it. The wizard to insert an ATL/COM object may crash and corrupt your project. If this happens, you will want to delete the project and reuse a copy of the saved project and try it again. 2.1 Creating the object:
Note: Once build, we can run the project. You will see the dialog window:
If you go into the task manager, you will see that the Wrts is running in the background. Once you have press CLOSE, you will see the Wrts being removed from the list in the Task Manager. Summary: Adding CoInitialize and CoUninitialize can be done manually, but using the wizard to insert an ATL/Com object, you can make your life easier. Inserting this object will be needed later in this document anyway. 2.2 Adding functionality to the Buttons: Go to View and select Class Wizard. You will see the following window
void
CWrtsClientDlg::OnHalt() { // TODO: Add your control notification
handler code here HRESULT hr = m_pTpsServerEx->Halt(); if (FAILED(hr)) handleError(hr); }
void
CWrtsClientDlg::OnLoad() { // TODO: Add your control notification
handler code here HRESULT hr = m_pTpsServerEx->Load(CComBSTR("")); if (FAILED(hr)) handleError(hr); } void
CWrtsClientDlg::OnMi() { // TODO: Add your control notification
handler code here HRESULT hr = m_pTpsServerEx->ManualIntervention(); if (FAILED(hr)) handleError(hr); } void CWrtsClientDlg::OnReset()
{ // TODO: Add your control notification
handler code here HRESULT hr = m_pTpsServerEx->Reset(); if (FAILED(hr)) handleError(hr); } void
CWrtsClientDlg::OnRun() { // TODO: Add your control notification
handler code here HRESULT hr = m_pTpsServerEx->Run(); if (FAILED(hr)) handleError(hr); } void
CWrtsClientDlg::OnUnload() { // TODO: Add your control notification
handler code here HRESULT hr = m_pTpsServerEx->Unload(); if (FAILED(hr)) handleError(hr); } You will want to add those methods where it makes sense for you in your own GUI.
è At this point, we have demonstrated how to connect directly with the TpsServer. The next step will allow for the client to catch all notifications from the TpsServer. Notes:
m_pTpsServerEx->Load(CComBSTR("C:\\usr\\paws\\test.paw")); Please note how each backslash is replaced by 2 backslashes inside the string. Summary: At this point in time, we are done accessing the methods that controls the Wrts directly. If you do not care to catch the notifications sent by the TpsServer, you are done. The locations where the methods depend on the GUI and those methods can be moved. 3 Catching the notifications sent by the TpsServer:This is the second step. It has been initiated in step 2.1. Now we will do the following: Ø We will first worry about how the sink object is created by working on the sink class. This is something that you will need regardless of what your GUI looks like. Ø Then we will add the property and the methods that allow the sink object to communicate with the dialog class. This step is also necessary regardless of what your GUI looks like. The sink object simply passes on the notifications from the TpsServer to your dialog class. Ø We will finally handle the notifications in the dialog class. How those notifications are handled depend on your GUI. 3.1 The sink class and connecting the client to the TpsServer:
class
ATL_NO_VTABLE CTpsClientSink : public
CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CTpsClientSink,
&CLSID_TpsClientSink>, public ISupportErrorInfo, public IDispatchImpl<ITpsClientSink,
&IID_ITpsClientSink, &LIBID_WrtsClientLib>, public IDispEventImpl<1, CTpsClientSink, &RTSAX::DIID__IRtsBaseEvents, &RTSAX::LIBID_RTSAX, 1, 0> { Please note the “,” at the end of the line preceeding IDisEventImpl. I. The first parameter is the ID used. We start with 1. This ID will be used as a parameter later in the document. II. The second argument is the name of the CoClass. This depends on the name that you used. In our case, we will use CtpsClientSink. III. The third is the connection point to the interface. This parameter should be fixed. IV. The forth is associated to the library. This parameter should be fixed. V. The last two are associated to the version of the TpsServer. These parameters should remain 1 and 0 until further notice. § We will now add the information for the mapping in the same file below the above code: BEGIN_COM_MAP(CTpsClientSink) COM_INTERFACE_ENTRY(ITpsClientSink) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(ISupportErrorInfo) END_COM_MAP() BEGIN_SINK_MAP(CTpsClientSink) SINK_ENTRY_EX(1, RTSAX::DIID__IRtsBaseEvents, 0x1, OnRtsTps) SINK_ENTRY_EX(1, RTSAX::DIID__IRtsBaseEvents, 0x5, OnRtsState) SINK_ENTRY_EX(1, RTSAX::DIID__IRtsBaseEvents, 0x9,
OnRtsMiEnable) SINK_ENTRY_EX(1, RTSAX::DIID__IRtsBaseEvents, 0xa,
OnRtsOutput) SINK_ENTRY_EX(1, RTSAX::DIID__IRtsBaseEvents, 0xb,
OnRtsDisplay) SINK_ENTRY_EX(1, RTSAX::DIID__IRtsBaseEvents, 0xc, OnRtsInfo) SINK_ENTRY_EX(1, RTSAX::DIID__IRtsBaseEvents, 0xd,
OnRtsWarning) SINK_ENTRY_EX(1, RTSAX::DIID__IRtsBaseEvents, 0xe, OnRtsError) END_SINK_MAP() //
ISupportsErrorInfo STDMETHOD(InterfaceSupportsErrorInfo)(REFIID
riid); Please note that the first parameter is the same value as the first parameter for IdispEventImpl just above. § We need to copy the notification in the same file just below the code above. //
ISupportsErrorInfo STDMETHOD(InterfaceSupportsErrorInfo)(REFIID
riid); void __stdcall OnRtsState(long lState) { if (m_ClientHWnd) PostMessage((HWND)m_ClientHWnd, WM_USER + 1,
eOnRtsState, lState); } void __stdcall OnRtsTps(BSTR pTps) { // extract the TPS name m_Tps = pTps; if (m_ClientHWnd) PostMessage((HWND)m_ClientHWnd, WM_USER + 1, eOnRtsTps, (long)(BSTR)m_Tps); } void __stdcall OnRtsMiEnable(VARIANT_BOOL varBool) { if (m_ClientHWnd) PostMessage((HWND)m_ClientHWnd, WM_USER + 1, eOnRtsMiEnable, (long)(BOOL)(varBool
!= VARIANT_FALSE)); } void __stdcall OnRtsOutput(BSTR pMsg) { if (m_ClientHWnd) SendMessage((HWND)m_ClientHWnd, WM_USER + 1, eOnRtsOutput, (long)pMsg); } void __stdcall OnRtsDisplay(BSTR pMsg) { if (m_ClientHWnd) SendMessage((HWND)m_ClientHWnd, WM_USER + 1, eOnRtsDisplay, (long)pMsg); } void __stdcall OnRtsInfo(BSTR pMsg) { if (m_ClientHWnd) SendMessage((HWND)m_ClientHWnd, WM_USER + 1, eOnRtsInfo, (long)pMsg); } void __stdcall OnRtsWarning(BSTR pMsg) { if (m_ClientHWnd) SendMessage((HWND)m_ClientHWnd, WM_USER + 1, eOnRtsWarning, (long)pMsg); } void __stdcall OnRtsError(BSTR pMsg) { if (m_ClientHWnd) SendMessage((HWND)m_ClientHWnd, WM_USER + 1, eOnRtsError, (long)pMsg); } protected: HWND m_ClientHWnd; CComBSTR m_Tps; //
ITpsClientSink public: }; § Now we want to add the enum for the third parameter in the SendMessage above. We will do this in StdAfx.h: extern
CDemoSekasTpsClientModule _Module; #include
<atlcom.h> enum EnumOnRtsEvent { eOnRtsTps = 0, eOnRtsState, eOnRtsMiEnable, eOnRtsOutput, eOnRtsDisplay, eOnRtsInfo, eOnRtsWarning, eOnRtsError, }; //{{AFX_INSERT_LOCATION}} // Microsoft
Visual C++ will insert additional declarations immediately // before
the previous line. #endif //
!defined(AFX_STDAFX_H__203EBD2C_7A3A_42FE_B018_12F5404E61EE__INCLUDED_) § At this point, the project should build without errors. § We still need to add the property and the methods. The property will allow to set the member variable m_ClientHWnd of the sink object to the dialog handle. The member variable m_ClientHWnd will then allow the sink object to access the buttons and the edit windows that we will implement later. The methods will allow the sink object to establish and break the connection with the TpsServer. We will see this in the explanation below. 3.2 Adding the Property and the Methods:
STDMETHODIMP
CTpsClientSink::put_ClientHandle(long newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here m_ClientHWnd = (HWND)newVal; return S_OK; }
public: CTpsClientSink() { m_ClientHWnd = NULL; }
STDMETHODIMP
CTpsClientSink::SinkAdvise(IUnkown *newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here return DispEventAdvise(newVal); }
STDMETHODIMP
CTpsClientSink::SinkUnadvise(IUnknown *newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here return DispEventUnadvise(newVal); }
//}}AFX_MSG DECLARE_MESSAGE_MAP() CComPtr<RTSAX::ITpsServerEx>
m_pTpsServerEx; CComPtr<ITpsClientSink> m_pSink; };
BOOL
CWrtsClientDlg::OnInitDialog() { CDialog::OnInitDialog(); // Set the icon for this dialog. The framework does this automatically //
when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here // create tps server HRESULT hr =
m_pTpsServerEx.CoCreateInstance(RTSAX::CLSID_TpsServer); if (FAILED(hr)) { handleError(hr); return
FALSE; } // put tps server in asynchronous mode hr = m_pTpsServerEx->put_Synchronous(VARIANT_FALSE); if (FAILED(hr)) { handleError(hr); return FALSE; } // create the sink object hr = m_pSink.CoCreateInstance(CLSID_TpsClientSink); if (FAILED(hr)) { handleError(hr); return FALSE; } // pass the window handle to the sink object hr = m_pSink->put_ClientHandle((long)m_hWnd); if (FAILED(hr)) { handleError(hr); return FALSE; } // set up the connection point hr = m_pSink->SinkAdvise(m_pTpsServerEx); if (FAILED(hr)) { handleError(hr); return FALSE; } // attach tpsserver to wrts hr = m_pTpsServerEx->Attach(); if (FAILED(hr)) { handleError(hr); return
FALSE; } return TRUE;
// return TRUE unless you set
the focus to a control } The added code has the following purpose: I. put_Synchronous puts the tps in asynchronous mode. II. CoCreateInstance creates the sink object.
III.
put_ClientHandle
passes the dialog handle to the sink object.
IV. SinkAdvise connects the sink object to the TpsServer connection point..
void
CWrtsClientDlg::OnDestroy() { CDialog::OnDestroy(); // TODO: Add your message handler code
here if (m_pSink && m_pTpsServerEx) m_pSink->SinkUnadvise(m_pTpsServerEx); m_pSink.Release(); m_pTpsServerEx.Release(); }
3.3 Handling the notifications:
I. We will add a few Edit boxes in the dialog window II. Prepare the reception of notifications. III. We will add the code. IV. We will add the mapping. 3.3.1 Adding Edit boxes in the dialogThe purpose of doing this is to add three read-only edit boxes. I. One to display the messages intended for the Wrts display. We will not parse the string in order to handle the invisible characters intended to handle the ASCII codes for coloring the font, background… It will nevertheless still display the text that is intended to be displayed. II. The second Edit will display the project name. III. The last one will indicate the state of the Wrts. Add three edit boxes and two static boxes as defined below:
Change the Member variable name to m_EditDisplay.
§ Click on OK and OK on the MFC ClassWizard window.
3.3.2 Preparing the reception of notifications
// Generated message map functions //{{AFX_MSG(CWrtsClientDlg) virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnDestroy(); afx_msg void OnHalt(); afx_msg void OnLoad(); afx_msg void OnMi(); afx_msg void OnReset(); afx_msg void OnRun(); afx_msg void OnUnload(); //}}AFX_MSG LRESULT OnComMessage(WPARAM
wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() CComPtr<RTSAX::ITpsServerEx>
m_pTpsServerEx; CComPtr<ITpsClientSink> m_pSink; }; 3.3.3 Adding the code
LRESULT OnComMessage(WPARAM wParam, LPARAM
lParam); DECLARE_MESSAGE_MAP() CComPtr<RTSAX::ITpsServerEx>
m_pTpsServerEx; CComPtr<ITpsClientSink> m_pSink; BOOL m_bMI; };
// attach
tpsserver to wrts hr = m_pTpsServerEx->Attach(); if (FAILED(hr)) { handleError(hr); return
FALSE; } m_bMI = FALSE; return TRUE; // return TRUE unless you
set the focus to a control }
void
CWrtsClientDlg::OnMi() { // TODO: Add your control notification
handler code here m_bMI = FALSE; HRESULT hr =
m_pTpsServerEx->ManualIntervention(); if (FAILED(hr)) handleError(hr); }
//
WrtsClientDlg.cpp : implementation file // #include
"stdafx.h" #include
"WrtsClient.h" #include
"WrtsClientDlg.h" #include <limits.h> #ifdef
_DEBUG #define new
DEBUG_NEW #undef
THIS_FILE static char
THIS_FILE[] = __FILE__; #endif
LRESULT CWrtsClientDlg::OnComMessage(WPARAM wParam,
LPARAM lParam) { USES_CONVERSION; TRY { switch((EnumOnRtsEvent)wParam) { case
eOnRtsTps: GetDlgItem(IDC_PROJNAME)->SetWindowText(OLE2T((BSTR)lParam)); break; case
eOnRtsMiEnable: GetDlgItem(IDC_MI)->EnableWindow(lParam); break; case
eOnRtsState: //
control the state of buttons switch(lParam) { case
0:// RTS_STATE_CLOSED GetDlgItem(IDC_STATE)->SetWindowText(_T("CLOSED")); GetDlgItem(IDC_RUN)->EnableWindow(FALSE); GetDlgItem(IDC_RESET)->EnableWindow(FALSE); GetDlgItem(IDC_HALT)->EnableWindow(FALSE); GetDlgItem(IDC_MI)->EnableWindow(FALSE); GetDlgItem(IDC_LOAD)->EnableWindow(TRUE); GetDlgItem(IDC_UNLOAD)->EnableWindow(FALSE); break; case
1:// RTS_STATE_READY GetDlgItem(IDC_STATE)->SetWindowText(_T("READY")); GetDlgItem(IDC_RUN)->EnableWindow(TRUE); GetDlgItem(IDC_RESET)->EnableWindow(TRUE); GetDlgItem(IDC_HALT)->EnableWindow(FALSE); GetDlgItem(IDC_MI)->EnableWindow(FALSE); GetDlgItem(IDC_LOAD)->EnableWindow(FALSE); GetDlgItem(IDC_UNLOAD)->EnableWindow(TRUE); break; case
2:// RTS_STATE_RUNNING GetDlgItem(IDC_STATE)->SetWindowText(_T("RUNNING")); GetDlgItem(IDC_RUN)->EnableWindow(FALSE); GetDlgItem(IDC_RESET)->EnableWindow(TRUE); GetDlgItem(IDC_HALT)->EnableWindow(TRUE); GetDlgItem(IDC_MI)->EnableWindow(m_bMI); GetDlgItem(IDC_LOAD)->EnableWindow(FALSE); GetDlgItem(IDC_UNLOAD)->EnableWindow(TRUE); break; case
3:// RTS_STATE_HALTED { GetDlgItem(IDC_STATE)->SetWindowText(_T("HALTED")); GetDlgItem(IDC_RUN)->EnableWindow(TRUE); GetDlgItem(IDC_RESET)->EnableWindow(TRUE); GetDlgItem(IDC_HALT)->EnableWindow(FALSE); //
save the MI state CWnd*
pMI = GetDlgItem(IDC_MI); m_bMI
= pMI->IsWindowEnabled(); pMI->EnableWindow(FALSE); GetDlgItem(IDC_LOAD)->EnableWindow(FALSE); GetDlgItem(IDC_UNLOAD)->EnableWindow(TRUE); } break; case
4:// RTS_STATE_FINISH GetDlgItem(IDC_STATE)->SetWindowText(_T("FINISH")); GetDlgItem(IDC_RUN)->EnableWindow(TRUE); GetDlgItem(IDC_RESET)->EnableWindow(TRUE); GetDlgItem(IDC_HALT)->EnableWindow(FALSE); GetDlgItem(IDC_MI)->EnableWindow(FALSE); GetDlgItem(IDC_LOAD)->EnableWindow(FALSE); GetDlgItem(IDC_UNLOAD)->EnableWindow(TRUE); break; } break; case
eOnRtsOutput: case
eOnRtsDisplay: case
eOnRtsInfo: case
eOnRtsWarning: case
eOnRtsError: m_EditDisplay.SetSel(LONG_MAX,
LONG_MAX); m_EditDisplay.ReplaceSel(OLE2T((BSTR)lParam)); break; } return
0; } CATCH(COleDispatchException,
e) { if
(e->m_strDescription.IsEmpty()) handleError(e->m_scError); else { CString
errMsg; errMsg.Format("COM
Error from %s: %s", e->m_strSource, e->m_strDescription); //
Display the com error: ::MessageBox(NULL,
errMsg, _T("Error"), MB_OK); } return
0; } AND_CATCH_ALL(e) { handleError(E_FAIL); return
0; } END_CATCH_ALL } 3.3.4 Adding the map
BEGIN_MESSAGE_MAP(CWrtsClientDlg,
CDialog) //{{AFX_MSG_MAP(CWrtsClientDlg) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_DESTROY() ON_BN_CLICKED(IDC_HALT, OnHalt) ON_BN_CLICKED(IDC_LOAD, OnLoad) ON_BN_CLICKED(IDC_MI, OnMi) ON_BN_CLICKED(IDC_RESET, OnReset) ON_BN_CLICKED(IDC_RUN, OnRun) ON_BN_CLICKED(IDC_UNLOAD, OnUnload) //}}AFX_MSG_MAP ON_MESSAGE(WM_USER + 1, OnComMessage) END_MESSAGE_MAP() At this point, the project should build without errors and you should be able to run the client. This should conclude the making of the Wrts client. |