Wrapping MFC Objects For Use With .NET Remoting

Welcome to this week’s installment of .NET Tips & Techniques! Each week, award-winning Architect and Lead Programmer Tom Archer from the Archer Consulting Group demonstrates how to perform a practical .NET programming task using either C# or Managed C++ Extensions. This article assumes a basic understanding of combining MFC and Managed Extensions as well as .NET remoting.

Combining the powers of MFC and .NET (via Managed Extensions) enables you to leverage the much more robust UI capabilities of MFC while taking advantage of the many features available in .NET that are not in MFC. One example of the latter is Remoting—the .NET equivalent of DCOM (except much more functional and easier to use). However, if you do attempt to use Remoting in an MFC application, you’ll quickly run into the limitation that you cannot remote MFC objects across application boundaries. To illustrate this, consider the following example:

namespace RemotingMFCEx
{
  public __gc class StringArrayWrapper : public MarshalByRefObject
  {
  public:
    StringArrayWrapper(CStringArray* books) { m_books = books; }
    CStringArray* m_books;
  };

  public __gc class BookList : public MarshalByRefObject
  {
  private:
    CStringArray* m_books;
  public:
    BookList()
    {
      m_books = new CStringArray();
      m_books->Add(_T("Inside C#, Second Edition"));
      m_books->Add(_T("Extending MFC Applications with the .NET Framework"));
      m_books->Add(_T("Visual C++ .NET Bible"));
      m_books->Add(_T("The Microsoft Outlook 2003 Answer Book"));
    }
    ~BookList() { delete m_books; }
  public:
    CStringArray* GetList() { return m_books; }
    StringArrayWrapper* GetList2() { return new StringArrayWrapper(m_books); }
  };
}

Looking at the second—BookList—class, you can see that it exposes two public methods:

  • GetList—returns a pointer to a CStringArray object (an MFC type)
  • GetList2—returns a pointer to a StringArrayWrapper object (a managed type)

Now, assume that you had the following server code to register the BookList object:

TcpChannel* channel = new TcpChannel(9988);

ChannelServices::RegisterChannel(channel);

RemotingConfiguration::RegisterWellKnownServiceType(
  Type::GetType(S"RemotingMFCEx.BookList,RemotingMFCEx"),
                S"MFCRemotingDemo",
                WellKnownObjectMode::SingleCall);

Console::WriteLine(S"Press any key to stop server");
getch();

Finally, here’s some example client code to access the BookList object:

TcpChannel* channel = new TcpChannel();
ChannelServices::RegisterChannel(channel);

#undef GetObject

BookList* RemoteObject = static_cast<BookList*>
  (Activator::GetObject(Type::GetType(S"RemotingMFCEx.BookList,RemotingMFCEx"),
                                      S"tcp://jeanvaljean:9988/MFCRemotingDemo"));

//CStringArray* sa = RemoteObject->GetList();
StringArrayWrapper* sw = RemoteObject->GetList2();
//CString s = sw->m_books->GetAt(0);

Console::WriteLine(S"Press any key to quit");
getch();

If you were to uncomment the call to GetList2, a System.NotSupportedException exception would be thrown due to the fact that remoting a CStringArray* (or any MFC type) across a remoting boundary is not supported. However, if you were to run the code as is, you’d find that the call to GetList2 works—or at least appears to work.

The problem is that the successful call to the GetList method would have you believe that you have successfully remoted a managed object that wraps a CStringArray pointer. However, what you really have is a reference object (StringArrayWrapper). When you attempt to access its CStringArray pointer member (m_books), the remoting framework would then attempt to transfer the requested object across the application domain boundaries, which would result in an exception of type System.Runtime.Serialization.SerializationException. You can see this by uncommenting the GetAt method call on the StringArrayWrapper::m_books member.

As of this writing, the only way to circumvent this limitation is to write a wrapper class that not only wraps the MFC object you wish to remote, but also provides access to each member function of the MFC object that will be accessed from the remote client. Using the same example, suppose that the remote client needs to access the CStringArray::GetAt member function. A simple wrapper for that would look like the following:

public __gc class StringArrayWrapper : public MarshalByRefObject
{
public:
  StringArrayWrapper(CStringArray* books) { m_books = books; }
  String* GetAt(int index) { return m_books->GetAt(index); }
private:
  CStringArray* m_books;
};

As you can see, the process of wrapping the MFC object and its needed member functions is a matter of making an internal call to the object’s member function and returning a type that is convertible to the corresponding native type. In this example, I’m returning a String type that will be implicitly converted to a CString object on the client side.

The client code would now look like the following:

#undef GetObject

BookList* RemoteObject = static_cast<BookList*>
  (Activator::GetObject(Type::GetType(S"RemotingMFCEx.BookList,RemotingMFCEx"),
                        S"tcp://jeanvaljean:9988/MFCRemotingDemo"));

StringArrayWrapper* sw = RemoteObject->GetList2();
CString str = sw->GetAt(1);
_tprintf(_T("%srn"), str);

The MFC object exists only on the server. As a result, any invocation of its member functions will execute on the server and not on the client side. That might seem obvious, but it’s very easy to sometimes forget what is getting executed where—especially if you’re also remoting managed types, which are executing on the client side.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read