Home Search Contact us About us
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
Page rating   97% for 15 votes Useless Brilliant
 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.

  1. Open visual studio and create a new ATL project called DemoCallback. Choose the defaults and Finish.
  2. 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.
  3. Build the project.
  4. Open the DemoCallback.idl and just above helpstring("ICallbackServer Interface"),insert oleautomation,.
  5. 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();
        };
    
  6. 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();
        };
    
  7. 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.
  8. 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.

  1. Create an MFC Dialog based application using the wizard.
  2. 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.
  3. 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
    
  4. 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();
        }
    }
    
  5. 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" );
    
  6. 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.

  1. Start a new standard exe project in VB.
  2. Add a large button which can be pressed with our forehead.
  3. Under Project->References tick the DemoCallback box.
  4. Open the source code by double clicking on the button.
  5. 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
  6. Under Project select Add Class Module and accept the default.
  7. Add Implements ICallbackEntry to the top of the file and select it from the left combo box.
  8. Modify the code to look as follows
    Private Sub ICallbackEntry_JrmEvent1()
        MsgBox "CallBack occured"
    End Sub
  9. 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.

Comments Date
so nice 2-Dec-2003 cduret
thanks very useful !
How can I make it work with ATL NT Service ? 25-Mar-2004 Aviram Gilboa
I get a error message 10-Jun-2004 JoeValenz
Private Sub Form_Load()
Set oCall = New CallbackServer
Set cls1 = New Class1
==>> oCall.JrmAdvise cls1 `automation error --2147418041 in runtime

End Sub
How to trap errors 20-Nov-2004 Clive
I have been looking at your demo app code and have added a thread which does some work while the vb app can carry on, then calls back as before. The problem I have is that the user is able to close down the application before the com server raises the callback event and this causes VB to exit !

Is there any way to determin if the client app is still connected before calling it back ?

Thanks

Clive
visual basic samole code 14-Jun-2006 biniyam
how to work visual basic 6
r 4-Nov-2008 r
Home Search Contact us About us