Let's say I have a large data array updated 1000+ times per second.
Another application wants to access and read the array in a short interval. Both applications are on the same machine.
I have tried using WCF for interprocess communication, but serializing and sending the whole array (or a large object) thousands of times per second is unfeasible performance wise.
Is there a way to directly access objects from different applications in c#?
There are a few IPC technologies you can use that though pre-date WCF are still relevant today.
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 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 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:
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 otherwords, 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.
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.
NOTE: Contrary to popular belief, .NET Remoting is not entirely obsolete. One contemory use for it is communication between objects in different AppDomains
within the same process, something you typically do in plug-in systems
.