[RESOLVED] Sharing objects between application instances (using .NET Remoting)
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 7 of 7

Thread: [RESOLVED] Sharing objects between application instances (using .NET Remoting)

Threaded View

  1. #1
    Join Date
    Jun 2010

    [RESOLVED] Sharing objects between application instances (using .NET Remoting)

    Well, my last few "requests for comments" didn't raise much resonance. Perhaps because there simply was nothing to complain about the code? At least the code from the last one (with only a few minor changes) in the meantime runs without problems in two of my real-life apps. So I'll try it another time. New month, new "request"...

    And this one is about techniques I'm really new to as of now.

    The scenario: I have written a small command line mailer. The way it is used, I can't reliably eliminate the possibility of two (or more) instances of that program attempting to access the same user account on the same SMTP server simultaneously. At least my mail provider's server doesn't really like (attempts to) simultaneous logins (like probably at least most of the serious servers).

    Currently I'm guarding the server/user name pair with a mutex which works just fine (and therefore this inquiry is anything but urgent) and even does allow simutaneous logins to different accounts or different servers. However, as I understood the MSDN documentation, server connections used by the SmtpClient class are cached and potentially reused, but only with application instance scope. Therefore I suspected that a "phalanx" of app instances quasi-simultaneously accessing the same mail account would log out and then back in. And using Wireshark I could actually confirm this: I observerd them logging out and back in within about 300 ms.

    The servers I tested that with apparently tolerate this procedure, but I somehow want to further reduce the "stress" imposed on the server by my program. My plan is to share SMTP server connections between the app instances by sharing objects wrapping SmtpClient objects, one for each active connection.

    My first idea was that the first instance of the mailer app that's started (for a particular mail account) assumes the role of a server (after sending the mail it's responsible for itself) and other instances would then delegate mail sendig requests to it via a named pipe. However, while reading up about the building blocks I'd need for that, this approach sometime began to look overcomplicated and I thought making a clearer separation between client and server responsibilities would be a good idea.

    My first idea how to construct a server for this scenario was using a DLL. But that quickly turned out to not work. A DLL always gets mapped into the client's address space and the instances don't share their data. Making the DLL a COM server doesn't gain anything either: COM servers implemented as DLLs always are in-process servers and thus behave just the same.

    So my next idea was an out-of-process COM server in an .exe. I tried to make this work over days without success: The .exe produced that way always got mapped straight away into the client's address space, just like a DLL. (Actually, it turned out later that all my COM attempts worked, or rather didn't work, just the same when I simply removed the COM exposure... )

    I was on the verge of desparately posting here when I found this great Code Project article: http://www.codeproject.com/KB/COM/Bu...sInDotNet.aspx. I read the (quite long) article in search of a way to implement an out-of-process COM server in .NET, which is exactly what the article is about, but it turned out that something used in that article as a sub-technology was what I actually was looking for: .NET Remoting. I had just been looking in the wrong places...

    So I started to model an object-sharing server using Remoting and the following thest code is what I have so far. These are the header and implementation of the core server class and a tiny helper class:

    // Server.h (Test Server)
    #pragma once
    #using <System.Runtime.Remoting.dll>
    namespace TestServer
    using namespace System;
    using namespace System::Runtime::Remoting::Channels::Tcp;
    using namespace System::Threading;
    public ref class Server : public MarshalByRefObject
      Server() : m_nClientInstance(0), m_strSharedData(String::Empty)
      int GetClientInstanceNumber();
      bool IsDebugBuild();
      String ^GetSharedData();
      void SetSharedData(String ^strData);
      static Server ^GetInstance();
      static bool Startup(Action<int> ^dltStatusReportCallback);
      static void Shutdown();
      static Server ^s_instance = nullptr;
      static Mutex ^s_mtxInstanceMutex = nullptr;
      static TcpClientChannel ^s_clientchannel = nullptr;
      static Action<int> ^s_dltStatusReportCallback = nullptr;
      int m_nClientInstance;
      String ^m_strSharedData;
    public ref class Factory : public MarshalByRefObject
      static Server ^GetServer();
      Server ^GetRemoteServer();
    // Server.cpp (Test Server)
    #include "StdAfx.h"
    #include "Server.h"
    using namespace System::Runtime::Remoting;
    using namespace System::Runtime::Remoting::Channels;
    using namespace System::Runtime::Remoting::Activation;
    using namespace System::Runtime::CompilerServices;
    using namespace System::Diagnostics;
    using namespace System::Reflection;
    using namespace TestServer;
    // Server class members
    int Server::GetClientInstanceNumber()
      return Interlocked::Increment(m_nClientInstance);
    bool Server::IsDebugBuild()
    #ifdef _DEBUG
      return true;
      return false;
    String ^Server::GetSharedData()
      if (s_dltStatusReportCallback) s_dltStatusReportCallback(Thread::CurrentThread->ManagedThreadId);
      return m_strSharedData;
    void Server::SetSharedData(String ^strData)
      if (s_dltStatusReportCallback) s_dltStatusReportCallback(Thread::CurrentThread->ManagedThreadId);
      m_strSharedData = strData;
    Server ^Server::GetInstance()
      if (!s_mtxInstanceMutex) {
        // This means we're not the primary .exe instance
        try {
          Mutex ^mtx = Mutex::OpenExisting("TestServerInstanceMutex");
        catch (WaitHandleCannotBeOpenedException ^) {
          // No primary instance is running yet, so start one up
          Process ^proc = Process::Start(Assembly::GetExecutingAssembly()->Location);
          Trace::Assert(proc != nullptr, "TestServer: Failed to launch primary .exe instance");
          // Trace::Assert(proc->WaitForInputIdle(10000), "TestServer: Wait for idle state of launched primary .exe instance timed out");
        if (!s_clientchannel) {
          s_clientchannel = gcnew TcpClientChannel;
          ChannelServices::RegisterChannel(s_clientchannel, false);
        Factory ^facRemoteFactory = safe_cast<Factory ^>(Activator::CreateInstance(nullptr, "TestServer.Factory",
          gcnew array<Object ^>{gcnew UrlAttribute("tcp://localhost:55300/TestServer")})->Unwrap());
        return facRemoteFactory->GetRemoteServer();
      if (!s_instance) s_instance = gcnew Server;
      return s_instance;
    // To be called upon start-up of the supposedly singular primary server .exe instance,
    // returns true if no other instance is running yet and the instance mutex was acquired:
    bool Server::Startup(Action<int> ^dltStatusReportCallback)
      bool bIMTheOne;
      Mutex ^mtx = gcnew Mutex(true, "TestServerInstanceMutex", bIMTheOne);
      if (!bIMTheOne) {
        return false;  // Start-up failed: primary instance already running
      s_mtxInstanceMutex = mtx;
      s_dltStatusReportCallback = dltStatusReportCallback;
      ChannelServices::RegisterChannel(gcnew TcpServerChannel(55300), false);
      RemotingConfiguration::ApplicationName = "TestServer";
      return true;
    // To be called upon shutdown of the singular server .exe instance:
    void Server::Shutdown()
      if (s_mtxInstanceMutex) {
    // Factory class members
    Server ^Factory::GetServer()
      return Server::GetInstance();
    Server ^Factory::GetRemoteServer()
      return GetServer();
    And this is how the server is started up in the main form class's constructor of the server app:

        Form1(void)  // Test Server
          //TODO: Konstruktorcode hier hinzuf&#252;gen.
          if (!Server::Startup(gcnew Action<int>(this, &Form1::ServerStatusReportCallback))) {
            Text += " (non-primary)";
            MessageBox::Show("There's already an instance of the server running. Please use that.",
              Text, MessageBoxButtons::OK, MessageBoxIcon::Exclamation);
          lblGuiThreadId->Text = String::Format("GUI thread ID: {0}", Thread::CurrentThread->ManagedThreadId);
    The form class' destructor then simply calls Server::Shutdown() later.

    The server has a GUI which I find quite useful for diagnostic purposes, at least at the current stage. Also, I know how to effectively hide the GUI later (and perhaps let it reappear in case I want to), while I have no idea how to do that with a console (though this is certainly possible).

    Finally, this is how the client attaches to the server:

        Form1(void)  // Test Client
          //TODO: Konstruktorcode hier hinzuf&#252;gen.
          m_svr = TestServer::Factory::GetServer();
          Text += String::Format(" #{0}", m_svr->GetClientInstanceNumber());
          lblGuiThreadId->Text = String::Format("GUI thread ID: {0}", Threading::Thread::CurrentThread->ManagedThreadId);
          lblIsServerDebugBuild->Text = String::Format("Server is debug build: {0}", m_svr->IsDebugBuild());
    The rest of the code IMO is somewhere between rather unspectacular and trivial. To illustrate the toy functionality implemented by the code I've attached a screen shot of the server and two client instances "in action" which probably tells more than 1k words. I also have attached the test solution for those who want to play with it (and perhaps improve it a bit ).

    The toy code works AFAICT, yet there are some open questions remaining. For instance, the tight entanglement of client and server aspects in the Server class, in particular the GetInstance() member, might be a fundamental design issue. OTOH it felt quite natural to write it that way and I'm afraid that separating them would bloat the code a bit without necessarily making it really easier to read/maintain.

    Did I miss anything about the synchronization? I took the "quick and dirty" approach to synchronization here and might use something more refined in the real-life server.

    While the test server here is a singleton, the real-life server is planned to be sort of a "multi-singleton", or rather a hybrid of the Singleton pattern and the Named Constructor idiom. The singular server app instance shall be able to host a number of server objects, identified by the server/user name pair (and perhaps the password). The server objects are reference-counted, reused on demand and self-destroy after some idle time determined by a timeout. (I'm uncertain whether I can conveniently implement that based on the .NET framework's Lifetime Service concept or should better roll my own.) The server app in turn waits for a timeout span after the last server object has been destroyed and then shuts itself down. Regarding this, I'm concerned about a potential race condition in the shutdown phase, for instance when the server isn't ready to accept remoting messages anymore but the mutex indicating that it's running hasn't been released yet.

    Is the currently commented-out (asserted) call to Process::WaitForInputIdle() in Server::GetInstance() really unnecessary? I have the impression that the call to Activator::CreateInstance() takes care of waiting until the server actually is up, but I didn't see that mentioned anywhere on MSDN. (Moreover, WaitForInputIdle() of course only works for a server with a GUI, so I would need an alternative approach for one implemented as a console app.)

    Also, what if I forcefully shut down the server while there are still clients running that are attached to it? (Didn't dare to simply try that yet... )

    I also have tried to migrate the above code to use IPC channels since it's not intended to be accessed over a network anyway, but I couldn't get that to work. Chances are I messed up the addressing with IPC port name, app name and type name, but OTOH I've practically seen no sample code using IPC channels and there probably is a reason for that.
    Attached Images Attached Images  
    Attached Files Attached Files
    Last edited by Eri523; September 1st, 2011 at 08:54 PM.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts

Azure Activities Information Page

Windows Mobile Development Center

Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


HTML5 Development Center