Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Running Object Table: Provider in .NET, consumer in MFC

0.00/5 (No votes)
9 Mar 2007 1  
Two example classes: one in C# that registers itself to the ROT, the other one in MFC/C++ that is using that object

Introduction

Using ROT (Running Object Table) is a great way to establish interprocess communication between two windows applications. From a purely logical aspect, one application registers a pointer to an instance of a class in the ROT, the other one gets a pointer pointing to the same instance of the registered class and therefore can use the same instance of the class via this pointer. The class that is registered has to be a COM class, otherwise it can be written in any language. The application that will retrieve the pointer from the ROT can be written in any language that can use COM, as ROT gives a pointer to a COM interface. I have had the need that a C# application registers a class to the ROT and an MFC (native C++) application retrieves it and uses it. I have created a small sample application for testing the system, and I would like to present it here.

Using the code

The C# side

The first thing to show is the .NET class that registers itself in the ROT when created. Since this class has to be a COM class, it has to have an interface. My class is called COMROTVictim, so I named the interface ICOMROTVictim.

[ComVisible(true),
GuidAttribute("14C09983-FA4B-44e2-9910-6461728F7883"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICOMROTVictim
{
    [DispId(1)]
    void MFCStart();
    [DispId(2)]
    void MFCStop();
    [DispId(3)]
    void MFCCallsCSharp(string s);
}

I also needed callbacks (functions that get invoked in the MFC application as a result of some processing in the C# application) so I have declared an event interface like this:

[Guid("E00FA736-8C24-467a-BEA0-F0AC8E528207"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
ComVisible(true)]
public interface ICOMROTVictimEvents
{
    [DispId(1)]
    void CEClose(string s);
    [DispId(2)]
    void CECSharpCallsMfc(string s);
}

I needed some Ole32 calls, I created a class for those functions:

class Ole32
{
    [ DllImport ( "Ole32.Dll" ) ]
    public static extern int CreateBindCtx ( int reserved, 
    out UCOMIBindCtx bindCtx );
        [DllImport("oleaut32.dll")]
    public static extern int RegisterActiveObject
        ([MarshalAs(UnmanagedType.IUnknown)] object punk,
            ref Guid rclsid, uint dwFlags, out int pdwRegister);
}

And finally the class itself. As I like encapsulation very much, my class registers itself to the ROT from the constructor and removes itself in the Dispose() method. For clear code I think this is the best. This is going to be the interface from my C# application to my MFC application, the way it solves the communication is its own responsibility.

[ComVisible(true),
GuidAttribute("5E9C1704-28BC-499c-A54F-A19F082D08B6"),
ComSourceInterfaces(typeof(ICOMROTVictimEvents)),
ClassInterface(ClassInterfaceType.None)]
public class COMROTVictim : ICOMROTVictim, IDisposable
{
    public delegate void ComEvent(string p);
    public event ComEvent CECSharpCallsMfc;
    public event ComEvent CEClose;

    int m_dwRegister;
    bool m_bMFCConnected;

    public COMROTVictim()
    {
        Guid guid = Marshal.GenerateGuidForType(typeof(COMROTVictim));
        Ole32.RegisterActiveObject(this, ref guid, 0, out m_dwRegister);
        MessageBox.Show("Registering: " + guid.ToString());
    }
    public void Dispose()
    {
        if (m_dwRegister != 0)
        {
            if (m_bMFCConnected )
                CSharpClose(0);

            UCOMIBindCtx bc;
            Ole32.CreateBindCtx ( 0, out bc );

            UCOMIRunningObjectTable rot;
            bc.GetRunningObjectTable ( out rot );
            rot.Revoke(m_dwRegister);
            m_dwRegister = 0;
        }

    }

    ~COMROTVictim()
    {
        Dispose();
    }


    public void MFCStart()
    {
        MessageBox.Show("MFCApp connected");
        m_bMFCConnected = true;
    }
    public void MFCStop()
    {
        MessageBox.Show("MFCApp disconnected");
        m_bMFCConnected = false;
    }
    public void MFCCallsCSharp(string s)
    {
        MessageBox.Show(string.Format("MFCCallsCSharp param: {0}", s));
    }

    public void CSharpCallsMfc(string s)
    {
        if (CECSharpCallsMfc != null)
            CECSharpCallsMfc(s);
    }
    public void CSharpClose(int i)
    {
        if (CEClose != null)
            CEClose("");

        m_bMFCConnected = false;
    }
}

This is the code of the C# class that is going to communicate with my MFC application. Functions beginning with MFC will be invoked from my MFC application. The CSharp application calls CSharpCallsMfc() for sending a string to the MFC application, and calls CSharpClose()for notifying the MFC application that it is closing down the pointers that are not ok anymore.

The MFC side

My MFC application also has a class for managing this communication, its name is CCOMROTVictimWrapper. Since I needed the COM event interface (for backward communication), this class is derived from the appropriate specialization of IDispEventImpl. The class declaration can be seen below.

class CCOMROTVictimWrapper : public IDispEventImpl<0, CCOMROTVictimWrapper,
              &DIID_ICOMROTVictimEvents,&LIBID_ROTProviderDotNetDlg, 1, 0>
{
public:
    CCOMROTVictimWrapper(void);
    virtual ~CCOMROTVictimWrapper(void);

    BEGIN_SINK_MAP(CCOMROTVictimWrapper)
        SINK_ENTRY_EX(0, DIID_ICOMROTVictimEvents, 1, OnClose)
        SINK_ENTRY_EX(0, DIID_ICOMROTVictimEvents, 2, OnCSharpCallsMfc)
    END_SINK_MAP()

    // incoming calls from the C# app

    STDMETHOD_(void, OnClose)(BSTR param);
    STDMETHOD_(void, OnCSharpCallsMfc)(BSTR param);

    // outgoing calls to the C# app


    //connect and call start()

    bool Start();
    // an example function call

    bool MfcCallsCSharp(CString s);
    //call stop and disconnect

    bool Stop();

protected:
    ICOMROTVictim* m_pIF;

};

Start() will connect to the C# application, Stop() will disconnect from it. The function MfcCallsCSharp() sends a string to the CSharp application that displays a messagebox with this string. The implementation of the most relevant functions are shown below.

//connect and call start()

bool CCOMROTVictimWrapper::Start()
{
    try
    {
        USES_CONVERSION;

        if (m_pIF != NULL)
            return false;

        IRunningObjectTable* pRunningObjectTable = NULL;
        IMoniker* pMoniker = NULL;

        HRESULT hr = GetRunningObjectTable(0, &pRunningObjectTable);

        LPOLESTR strClsid;
        StringFromCLSID(CLSID_COMROTVictim, &strClsid);
        CreateItemMoniker(T2W("!"), strClsid, &pMoniker);

        IUnknown* punk = NULL;

        IBindCtx* pctx;
        hr = CreateBindCtx(0, &pctx);
        if (pRunningObjectTable->GetObject( pMoniker, &punk) != S_OK)
        {
            pctx->Release();
            throw 1; //object not running

        }

        hr = punk->QueryInterface(__uuidof(ICOMROTVictim), (((void**)&m_pIF)));

        hr = DispEventAdvise(m_pIF);
        pctx->Release();
        punk->Release();

        m_pIF->MFCStart();
    }
catch(...)
{
    return false;
}
return true;
}

//How to disconnect

bool CCOMROTVictimWrapper::Stop()
{
    try
    {
        if (!m_pIF)
            return false;

        m_pIF->MFCStop();
        DispEventUnadvise(m_pIF);
        m_pIF->Release();
        m_pIF = NULL;
    }
    catch(...)
    {
        return false;
    }
    return true;
}

// an example function-call

bool CCOMROTVictimWrapper::MfcCallsCSharp(CString s)
{
    try
    {
        m_pIF->MFCCallsCSharp(s.AllocSysString());
    }
    catch(...)
    {
        return false;
    }
    return true;
}

//called by the C# app

void CCOMROTVictimWrapper::OnClose(BSTR)
{
    AfxMessageBox("CSharpApp closed");
    DispEventUnadvise(m_pIF);
    m_pIF->Release();
    m_pIF = NULL;
}

//called by the C# app

void CCOMROTVictimWrapper::OnCSharpCallsMfc(BSTR str)
{
    AfxMessageBox("OnCSharpCallsMfc param: " + CString(str));
}

Using VB6 as a consumer

With the help of fredobedo007 (see the FAQ below this article) I was able to use the same C# object in the ROT from a VB6 client as well (if you have read an earlier version of this article, I have changed the C# code so that it can cooperate with the VB6 client, and also changed the C++ code so that it can cooperate with the new version of the C# code). The VB6 class that is using the C# object is shown below.
Private WithEvents m_rv As ROTProviderDotNetDlg.COMROTVictim

Private Sub Class_Initialize()
    Set m_rv = GetObject(, "ROTProviderDotNetDlg.COMROTVictim")
    m_rv.MFCStart
End Sub

Public Sub SendMsg(ByVal s As String)
    m_rv.MFCCallsCSharp ("")
End Sub

Public Sub Disconnect(ByVal s As String)
    m_rv.MFCStop
End Sub
Private Sub m_rv_CEClose(ByVal s As String)
    MsgBox "a"
End Sub

Private Sub m_rv_CECSharpCallsMfc(ByVal s As String)
    MsgBox "b"
End Sub
Of course you will need to add a reference to the tlb file generated by the C# project.

Conclusions

If you understand all the above, you can copy and paste the code and modify it so that it suits your needs (change the GUID numbers!!!). But before programming, please read the following:
  • The C# exe has to be registered as a COM application using regasm.exe
  • Event interface programming can be very annoying. With several kinds of errors DispEventAdvise returns S_OK, but the events are simply not called. Such errors can include versioning (the version of the IDispEventImpl specialization has to match the major and minor versions of the C# assembly). Also, unmatching function signature in the MFC code for implementing the event interface can cause the code to build, but functions not to be called. This is just a warning that the tool is great, but you have to be careful with it.
  • Visual Studio 6 has a tool called ROT Viewer. This can be a useful application, but any other application displaying the contents of the ROT might be ok for checking if registration went alright.
  • Again, the pure fact that your class registered into the ROT and it appears in ROT viewer does not mean that it is ok. If you experience problems, check not only the caller side, but also the code that has registered the pointer.
  • There is a new namespace in .NET 2.0 for COM classes: System.Runtime.InteropServices.ComTypes. If you use .NET 2.0 you should use the classes in this namespace, but the basic theories should be the same (I do not have a chance to try it).
  • VB6 cannot handle function names in COM event interfaces that contain any underscore characters. If you are planing to have a VB6 client, you should avoid using that.
  • In VB6, when you specify WithEvents, the event functions should be added by the debugger automatically (when you select the variable in the left combobox on the top of the code area). For me, only the first function was added. I added the second one manually.

What's in the downloads:

  • The source code link downloads the 4 files (cs, cpp, h, vb) that have been explained above.
  • The Demo link downloads the C# and the Visual C++ projects that I created.

To use: extract it, build it, register the .NET application (regasm.exe ROTProviderDotNetDlg.exe /codebase /tlb), run the two exe files, push the buttons and watch out for the message boxes.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here