|
|
Title |
Creating a Simple callback function in VC and VB.
|
Summary |
Callbacks are quite simple when done within the same process, however when calling across process boundrys or machines another system must be used. This is done using COM in a simular way to ActiveX events. |
Contributor |
John McTainsh
|
Published |
21-Dec-2000 |
Last updated |
21-Dec-2000 |
|
|
Download
Client and Server files - 22 Kb
Introduction.
Calling back from a server into a client is a very useful tool to resolve problems
such as catching remote events without suffering the overhead of high polling rates.
In COM calling back is not simple process because we may be crossing process boundaries,
or even machines and networks. One standard method to do this is be using connection
points. These are easily generated using the wizard and are derived from the IDispatch
interface. For a faster callback it is possible to pass an interface directly back
to the client with is much easier to implement in C++.
In this tutorial we will create a server that can display a message box and call
back into the client. We will also add a function in the server which when called
will fire the callback into the client..
Creating the server.
These steps will create a COM server with a callback interface and two functions.
- Open visual studio and create a new ATL project called
DemoCallback .
Choose the defaults and Finish.
- Select, Insert a new ATL object and choose the Simple Object. Use a short name
of
CallbackServer and under the attributes tab select the Custom interface
radio button.
- Build the project.
- Open the
DemoCallback.idl and just above helpstring("ICallbackServer
Interface"), insert oleautomation, .
- Just above this interface between the second
import line and the[
insert the following code.
//
//Callback interface
//
[
object,
uuid(D072EB42-D7BD-46f2-85C0-A7B285061859),
oleautomation,
helpstring("ICallbackEntry Interface"),
pointer_default(unique)
]
interface ICallbackEntry : IUnknown
{
HRESULT JrmEvent1();
};
- Close the idl file, compile and add two methods the
ICallbackServer
by right clicking on the interface in the class viewer. SimulateCallback
and JrmAdvise([in] ICallbackEntry* pEntry) ;. The interface should look
like this with a different uuid ;
//
//Main server interface
//
[
object,
uuid(F73886CA-1871-423D-9611-565095314BCF),
oleautomation,
helpstring("ICallbackServer Interface"),
pointer_default(unique)
]
interface ICallbackServer : IUnknown
{
[helpstring("method JrmAdvise")]
HRESULT JrmAdvise([in] ICallbackEntry* pEntry);
[helpstring("method SimulateCallback")]
HRESULT SimulateCallback();
};
- Now add an attribute to the
CCallbackServer class of ICallbackEntry*
m_pCallback; and set it to NULL in the constructor. Don't for get to free it
in FinalRelease .
- Modify the
CallbackServer.cpp as follows;
// ***************************************************************************
//DESCRIPTION:
// Pass the interface pointer the client has exposed to be
// called back on. Call this once at startup.
//CREATED:
// 11-12-2000, 1:30:45 AM by john@mctainsh.com
// ***************************************************************************
STDMETHODIMP CCallbackServer::JrmAdvise(ICallbackEntry *pEntry)
{
if( m_pCallback )
return MAKE_HRESULT( 3, FACILITY_RPC, ERROR_REQ_NOT_ACCEP );
m_pCallback = pEntry;
m_pCallback->AddRef();
return S_OK;
}
// ***************************************************************************
//DESCRIPTION:
// Calls back into the client using a client provided interface
//CREATED:
// 11-12-2000, 1:30:42 AM by john@mctainsh.com
// ***************************************************************************
STDMETHODIMP CCallbackServer::SimulateCallback()
{
if( m_pCallback == NULL )
return MAKE_HRESULT( 3, FACILITY_RPC, ERROR_CONNECTION_UNAVAIL );
m_pCallback->JrmEvent1();
return S_OK;
}
That's it! Rebuild all and check it performs build registration in the output
window.
Creating a C++ Client.
Visual C++ is an excellent language to develop COM clients but does require a little
more work and VB. As a trade of you are given much greater control such the option
to control who, where and how the server is instanciated at run time.
- Create an MFC Dialog based application using the wizard.
- Add the following to the top of the Dialogs header file.
#import "..\DemoCallback\debug\DemoCallback.dll" named_guids raw_interfaces_only
using namespace DEMOCALLBACKLib;
/////////////////////////////////////////////////////////////////////////////
// Connected interface called from Server
//WARNING: It is assumed this object is heap based. Always create it
// using new.
class CCallbackEntry : public ICallbackEntry
{
//Operators
public:
STDMETHODIMP JrmEvent1()
{
MessageBox( NULL, "Event Recieved at JrmEvent1()", "Recieved from Server!", MB_OK );
return S_OK;
}
//
//The following is general fluff that should be moved off into a template
//
public:
CCallbackEntry() : m_nRefCount(0) {}
STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
if (riid == IID_IUnknown || riid == IID_ICallbackEntry)
*ppv=this;
else
return *ppv=0,E_NOINTERFACE;
AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) AddRef()
{
return ++m_nRefCount;
}
STDMETHODIMP_(ULONG) Release()
{
if (!--m_nRefCount)
{
delete this;
return 0;
}
return m_nRefCount;
}
//Attributes
private:
ULONG m_nRefCount;
};
This defines the object to be called back into.
- In the class definition of the same file add attributes to point to the server
object and our interface object to call back into.
//Attributes
private:
ICallbackServer* m_pServer; //Connection to server
CCallbackEntry* m_pCallBack; //Callback interface
- At the top of the Dialog class CPP file add the following helper.
// NOTE: CheckHRESULT is a helper function that takes an HRESULT
// and a function name as parameters and displays an error
// message if the HRESULT's severity bit indicates an error
// has happened. This function never returns if an error
// has occurred.
void CheckHRESULT(HRESULT hr, const char *pszCause)
{
if (FAILED(hr))
{
char sz[1024];
char szCaption[1024];
strcpy(szCaption, pszCause);
strcat(szCaption, " failed!");
if (!FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, hr, 0, sz, 1024, 0))
strcpy(sz, "Unknown error");
MessageBox(0, sz, szCaption, MB_SETFOREGROUND);
DebugBreak();
}
}
- In the CPP file of the Dialog, just before the return, initialise COM and create
the server object.
//
//Setup for COM communications
//
HRESULT hr = CoInitialize( NULL );
CheckHRESULT( hr, "CoInitialize" );
hr = CoCreateInstance(
CLSID_CallbackServer,
NULL, CLSCTX_ALL,
__uuidof( m_pServer ),
(void**)&m_pServer );
CheckHRESULT( hr, "CoCreateInstance of CallbackServer" );
//
//Expose the callback interface
//
m_pCallBack = new CCallbackEntry;
m_pCallBack->AddRef();
hr = m_pServer->JrmAdvise( m_pCallBack );
CheckHRESULT( hr, "JrmAdvise" );
- Now finally call the server to stimulate the callback into our app with the OK
button event handler.
// ***************************************************************************
//DESCRIPTION:
// When OK is pressed call a method on the control.
//CREATED:
// 20-12-2000, 8:48:56 PM by john@mctainsh.com
// ***************************************************************************
void CDemoMFCDlg::OnOK()
{
TRACE( _T("CDemoMFCDlg::OnOK() \n") );
HRESULT hr = m_pServer->SimulateCallback();
CheckHRESULT( hr, "SimulateCallback" );
}
Creating a Visual Basic Client.
Using Visual Basic 6 is probably the easiest way to use the Callback server. I
know very little about VB so use this code with caution.
- Start a new standard exe project in VB.
- Add a large button which can be pressed with our forehead.
- Under Project->References tick the
DemoCallback
box.
- Open the source code by double clicking on the button.
- Add the following to the file.
Dim oCall as DEMOCALLBACKLib.CallbackServer
Dim cls1 As Class1
Private Sub Form_Load()
Set oCall = New CallbackServer
Set cls1 = New Class1
oCall.JrmAdvise cls1
End Sub
Private Sub Command1_Click()
oCall.SimulateCallback
End Sub
- Under Project select Add Class Module and accept the default.
- Add
Implements ICallbackEntry to the top of the file and select it
from the left combo box.
- Modify the code to look as follows
Private Sub ICallbackEntry_JrmEvent1()
MsgBox "CallBack occured"
End Sub
- Close this file and run.
The application should display a dialog and with a fat button. Pressing the button
will call a method in the server that will use the Callback interface to run a function
in the VB client.
|