Object Sharing between Applications?

There are a few IPC technologies you can use that though pre-date WCF are still relevant today.

Pipes

Pipes is one such technology. It’s binary, runs in Kernel mode and very fast! Though it’s quite low-level and does not give access to “objects”.

.NET Remoting

.NET Remoting will give access to objects but is perhaps not as fast as pipes.

Both pipes and .NET remoting are faster than serialization-technologies WCF which converts things to verbose XML/SOAP.

COM

COM is a binary protocol for IPC. COM is a client server model where the client requests data from the COM or OLE server. The beauty about COM is that you have direct access to objects in the server – they are not serialised. For example requesting an element in a SAFEARRAY.

A SAFEARRAY is an Automation-safe structure of arbitrary dimensions consisting of type-safe data. Luckily .NET will hide the SAFEARRAY gobble-de-gook for us.

In my example I have created a Manager class that will expose the array. To get to the Manager I’ve used a factory pattern so that Manager is essentially a singleton.

You should lay out your project as follows:

  • MyComLib.Contracts.dll – contains all the interfaces
  • MyComLib.dll – contains the implementation of Factory, Manager

First the contracts:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IArrayItem
{
    #region Properties

    string Name { get; set; }

    int Whatsit { get; set; }

    #endregion
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IFactory 
{
    #region Methods

    IManager CreateManager();

    #endregion
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IManager
{
    #region Properties

    IArrayItem[] Array { get; }

    #endregion
}

public static class MyComLibConstants
{
    public const string FactoryProgId = "MickyD.MyComLib.Factory.1";
    
}

Now for the factory pattern:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof (IFactory))]
[Guid("...")]
[ProgId(MyComLibConstants.FactoryProgId)]
public class Factory : MarshalByRefObject, IFactory
{
    #region IFactory Members

    /// <summary>
    /// Creates the manager.
    /// </summary>
    /// <returns></returns>
    public IManager CreateManager()
    {
        return Manager.Instance;
    }


    #endregion
}

The manager:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof (IManager))]
[Guid("...")]
internal sealed class Manager : MarshalByRefObject, IManager
{
    private static Manager _instance;

    #region Constructor

    /// <summary>
    /// Prevents a default instance of the <see cref="Manager"/> class from being created.
    /// </summary>
    private Manager()
    {
        const int n = 5000;
        Array = new IArrayItem[n];
        for (int i = 0; i < n; i++)
        {
            Array[i]=new ArrayItem();
        }
    }

    #endregion

    #region Properties

    
    /// <summary>
    /// Gets the instance.
    /// </summary>
    /// <value>
    /// The instance.
    /// </value>
    public static IManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Manager();
            }
            return _instance;
        }
    }

    #endregion

    #region IManager Members

    /// <summary>
    /// Gets the array.
    /// </summary>
    /// <value>
    /// The array.
    /// </value>
    public IArrayItem[] Array { get; private set; }

    #endregion
}

A test app. This should only reference the MyComLib.Contracts.dll and not MyComLib.dll.

class Program
{
    static void Main(string[] args)
    {
        var type = Type.GetTypeFromProgID(MyComLibConstants.FactoryProgId);
        var factory = Activator.CreateInstance(type) as IFactory;
        var manager = factory.CreateManager();
        var x = manager.Array[500].Whasit;
    }
}

One final step is to change this in-process COM server to an out-of-process COM server so that multiple processes each share the same Manager and don’t create their own singletons. In other words, a singleton that spans processes. When the Manager is running, it is essentially in it’s own process space separate from all the other client processes.

For that you’ll need to configure a COM surrogate which is explained in detail here.

File Mapping/Shared Memory

Lastly, File Mapping allows you to manipulate a file as if it were nothing more than a large block of memory in the process’s address space. No fiddly file seek; read/write operations. Just grab a pointer to the memory block and start reading/writing. The system will do the rest.

MSDN:

You can use a special case of file mapping to provide named shared memory between processes. If you specify the system swapping file when creating a file-mapping object, the file-mapping object is treated as a shared memory block. Other processes can access the same block of memory by opening the same file-mapping object. Tell me more

Sadly, it still does require you to write your data in the first place and for it to be most effective you would need to change your application to treat the memory block as the source of truth rather than your array in memory. Otherwise you’ll be serializing all the time.

However, shared memory via the swap file does technically allow you to eliminate any serialize-de-serialize between your client-server apps and duplication of data “on the heap”. Though as I said you may need to adjust your app to work with raw memory buffers rather than objects.

Tell me more

NOTE: Contrary to popular belief, .NET Remoting is not entirely obsolete. One contemporary use for it is communication between objects in different AppDomains within the same process, something you typically do in plug-in systems.

Leave a Comment