Click here to Skip to main content
15,881,757 members
Articles / Programming Languages / C#
Article

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

Rate me:
Please Sign up or sign in to vote.
4.61/5 (9 votes)
9 Mar 2007CPOL5 min read 135.6K   1.9K   42   30
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.

C#
[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:

C#
[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:

C#
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.

C#
[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.

C#
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.

C#
//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.
VB
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Ireland Ireland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Harry von Borstel26-Apr-12 6:26
Harry von Borstel26-Apr-12 6:26 
Questiontroubleshooting Pin
bjdodo27-Mar-12 8:09
bjdodo27-Mar-12 8:09 
GeneralThank you Pin
gareth williams229-Sep-09 4:38
gareth williams229-Sep-09 4:38 
QuestionRegister as single-instance server. Pin
thorben50011-May-09 6:05
thorben50011-May-09 6:05 
AnswerRe: Register as single-instance server. Pin
bjdodo20-Jul-09 7:21
bjdodo20-Jul-09 7:21 
QuestionVB6 Consumer Pin
bbelliveau449-Oct-07 8:46
bbelliveau449-Oct-07 8:46 
AnswerRe: VB6 Consumer Pin
bjdodo10-Oct-07 0:31
bjdodo10-Oct-07 0:31 
AnswerRe: VB6 Consumer Pin
bjdodo10-Oct-07 3:31
bjdodo10-Oct-07 3:31 
GeneralRe: VB6 Consumer Pin
bbelliveau4411-Oct-07 7:05
bbelliveau4411-Oct-07 7:05 
GeneralRe: VB6 Consumer Pin
bjdodo12-Oct-07 0:17
bjdodo12-Oct-07 0:17 
Generalindividual projects in separate solution Pin
Kabirdas Jaunjare11-Jun-07 0:34
Kabirdas Jaunjare11-Jun-07 0:34 
GeneralRe: individual projects in separate solution [modified] Pin
bjdodo11-Jun-07 1:25
bjdodo11-Jun-07 1:25 
Generali dont understand how did u derived "CCOMROTVictimWrapper " class Pin
Kabirdas Jaunjare11-Jun-07 0:00
Kabirdas Jaunjare11-Jun-07 0:00 
GeneralRe: i dont understand how did u derived "CCOMROTVictimWrapper " class Pin
bjdodo11-Jun-07 1:43
bjdodo11-Jun-07 1:43 
Hi
Have you ever:

- used COM in an MFC application before?
- used COM event sinks in an MFC application before?
- exported a COM interface from a .net (C#) project?

If any, or both of your answers is/are no, you should maybe begin with something more elementary in these fields, to learn the basics. Or choose other inter-process communication methods! (Like registered windows messages)

What you might be missing now:

In the .NET application you can export functionality via a COM interface for non managed applications, this step will produce a file with the extension of tlb. This tlb file describes the .net interface in the COM way. You can import this tlb file in your MFC application (check stdafx.h in the MFC project, in the demo zip), and once you do that, the types in that tlb will be available in your MFC app. Another pitfall might be for you that you'll need to add ATL support to your MFC application for the events to work. I think the rest of the steps are quite ok if you understand what you are doing.

Good Luck!

Jozsi
Generalc# consumer Pin
vchang123410-Apr-07 7:04
vchang123410-Apr-07 7:04 
GeneralRe: c# consumer Pin
bjdodo15-Apr-07 22:25
bjdodo15-Apr-07 22:25 
GeneralRe: c# consumer Pin
jian258715-Dec-09 23:23
jian258715-Dec-09 23:23 
GeneralRe: c# consumer Pin
antovictor11-Mar-10 7:45
antovictor11-Mar-10 7:45 
GeneralRe: c# consumer Pin
einfrankfurter17-Oct-13 2:58
einfrankfurter17-Oct-13 2:58 
GeneralRe: c# consumer Pin
bjdodo17-Oct-13 3:59
bjdodo17-Oct-13 3:59 
GeneralRe: c# consumer Pin
einfrankfurter17-Oct-13 5:38
einfrankfurter17-Oct-13 5:38 
SuggestionRe: c# consumer Pin
bjdodo17-Oct-13 6:10
bjdodo17-Oct-13 6:10 
QuestionHow to find Path of a given file in C# Pin
Member 37839969-Mar-07 18:43
Member 37839969-Mar-07 18:43 
Questionis it possible to use this mechanisme with a VB6 onsumer? Pin
fredobedo0075-Feb-07 8:49
fredobedo0075-Feb-07 8:49 
AnswerRe: is it possible to use this mechanisme with a VB6 onsumer? Pin
bjdodo6-Feb-07 0:03
bjdodo6-Feb-07 0:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.