|
TYX Corporation
MFC Client with ActiveX control 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. Note: This document differs from the document ref TYX_0051_1 in that it uses an ActiveX control for the edit window rather than a simple edit box. With this ActiveX control, the display will take in consideration the ASCII characters and interpret them correctly. 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 ActiveX 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,
|