[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)

  1. #1
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,585

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

    Code:
    // 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
    {
    public:
      Server() : m_nClientInstance(0), m_strSharedData(String::Empty)
      {}
    
      int GetClientInstanceNumber();
      bool IsDebugBuild();
      String ^GetSharedData();
      void SetSharedData(String ^strData);
    
    internal:
      static Server ^GetInstance();
      static bool Startup(Action<int> ^dltStatusReportCallback);
      static void Shutdown();
    
    private:
      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
    {
    public:
      static Server ^GetServer();
    
      Server ^GetRemoteServer();
    
    };
    
    }
    Code:
    // 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;
    #else
      return false;
    #endif
    }
    
    [MethodImpl(MethodImplOptions::Synchronized)]
    String ^Server::GetSharedData()
    {
      if (s_dltStatusReportCallback) s_dltStatusReportCallback(Thread::CurrentThread->ManagedThreadId);
      return m_strSharedData;
    }
    
    [MethodImpl(MethodImplOptions::Synchronized)]
    void Server::SetSharedData(String ^strData)
    {
      if (s_dltStatusReportCallback) s_dltStatusReportCallback(Thread::CurrentThread->ManagedThreadId);
      m_strSharedData = strData;
    }
    
    [MethodImpl(MethodImplOptions::Synchronized)]
    Server ^Server::GetInstance()
    {
      if (!s_mtxInstanceMutex) {
        // This means we're not the primary .exe instance
        try {
          Mutex ^mtx = Mutex::OpenExisting("TestServerInstanceMutex");
          mtx->Close();
        }
        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");
          proc->Close();
        }
        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) {
        mtx->Close();
        return false;  // Start-up failed: primary instance already running
      }
      s_mtxInstanceMutex = mtx;
      s_dltStatusReportCallback = dltStatusReportCallback;
      ChannelServices::RegisterChannel(gcnew TcpServerChannel(55300), false);
      RemotingConfiguration::ApplicationName = "TestServer";
      RemotingConfiguration::RegisterActivatedServiceType(Factory::typeid);
      return true;
    }
    
    // To be called upon shutdown of the singular server .exe instance:
    
    void Server::Shutdown()
    {
      if (s_mtxInstanceMutex) {
        s_mtxInstanceMutex->ReleaseMutex();
        s_mtxInstanceMutex->Close();
      }
    }
    
    // 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:

    Code:
        Form1(void)  // Test Server
        {
          InitializeComponent();
          //
          //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:

    Code:
        Form1(void)  // Test Client
        {
          InitializeComponent();
          //
          //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.

  2. #2
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,309

    Re: Sharing objects between application instances (using .NET Remoting)

    One question:

    Why did you use .Net remoting?

  3. #3
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,585

    Re: Sharing objects between application instances (using .NET Remoting)

    Quote Originally Posted by Arjay View Post
    One question:

    Why did you use .Net remoting?
    It took me almost one week to get to what I described in the OP and I was quite confident .NET Remoting was the way to go. Do you know of any much simpler alternative? (You know, even getting to Remoting took me a long detour, due to looking in the wrong places.)

    Curiously looking forward to your reply...
    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.

  4. #4
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,309

    Re: Sharing objects between application instances (using .NET Remoting)

    I didn't read your post completely (I admit), but .Net Remoting is so 2003.

  5. #5
    Join Date
    Jul 2002
    Posts
    2,513

    Re: Sharing objects between application instances (using .NET Remoting)

    Take a look at WCF.
    Last edited by Alex F; September 4th, 2011 at 12:55 AM.

  6. #6
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,309

    Re: Sharing objects between application instances (using .NET Remoting)

    Quote Originally Posted by Alex F View Post
    Take a look at WCF.
    Well, I was getting to that, but I wanted to keep the suspense up.

  7. #7
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,585

    Re: Sharing objects between application instances (using .NET Remoting)

    Quote Originally Posted by Alex F View Post
    Take a look at WCF.
    Thanks.

    Actually, of course I've already heard of that, but I failed to consider it because TLAs matching the regex W.?F always look to me like stuff that can't be (reasonably) used from C++/CLI. (You know, there's some well-known example... ) Probably more important, though, was my habit not really to consider web technologies when I'm looking for something that's meant to be confined to the local machine. For the same reason it took me so long to get to .NET Remoting; I had no idea it's a candidate until I read the Code Project article linked to in the OP. Most likely this habit is a remnant from times when I was learning programming on systems where resources where so limited, most of today's developers don't even rember it, if they had ever seen systems like that at all...

    Since it looks like this will be exclusively about WCF from now on, I've started a new thread about that. I invite you both to have a look and perhaps contribute. And of course that pertains to anyone who feels like that as well.

    Quote Originally Posted by Arjay View Post
    Well, I was getting to that, but I wanted to keep the suspense up.
    Hitchcock fan?
    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
  •  


Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center