Introduction:
The purpose of this class is to allow exchange data between different processes on the same machine.
The idea is to use temporary files, in a common directory, that are shared to all processes (i.e. c:\windows\temp) and synchronize access to these files through System.Threading.Mutex objects.
Prerequisites:
- The processes must reside on the same machine;
- There must be a local or remote directory accessible to all processes
- Objects that can be exchanged must be serializable (at least in binary format).
Limitations:
It is not possible to exchange data on a cluster system (active-active) or on back-end machines behind a load balancer such as NLB.
Possible uses:
- Inter Process Communication and data exchange of primitive types and/or complex types (custom classes that are serializable);
- Data exchange between Two or more applications, COM <==> NET, ASP <==> ASP.NET, and so on. [Shared Storage class is COM visible]
Mutex and Temporary files:
When two or more threads/processes need to access a shared resource at the same time, the system needs a synchronization mechanism to ensure that only one thread/process at a time uses the resource.
Mutex is a synchronization primitive that grants exclusive access to the shared resource to only one thread. If a thread/process acquires a mutex, the second thread (in the same or another process) that wants to acquire that mutex is suspended until the first thread/process releases the mutex.
Exclusive file write:
using System.IO;
using System.Threading;
public bool AddOrUpdate(string objectName, objectobjectValue)
{
bool createdNew = false;
(Mutex mLocked = new Mutex(true,String.Format(SharedStorage.
MUTEX_LOCKED_FORMAT, this.storageName), out createdNew))
{
try
{
if (!createdNew)
mLocked.WaitOne(); //Another thread/process is using it
bool fileCreated = this.writeFile(objectName, objectValue);
//false if overwrite
if (fileCreated && this.destroyWhenDisposed)
this.createdFiles.Add(this.getStorageFileName(objectName));
return fileCreated;
}
finally
{
mLocked.ReleaseMutex();
}
}
}
Exclusive file read:
using System.IO;
using System.Threading;
public object this[string objectName]
{
get
{
bool createdNew = false;
using (Mutex mLocked = new Mutex(true,String.Format(SharedStorage.MUTEX_LOCKED_FORMAT, this.storageName), out createdNew))
{
try
{
if (!createdNew)mLocked.WaitOne(); //Another thread/process is using it
if (this.Exists(objectName))return this.readFile(objectName);
else throw new ArgumentOutOfRangeException ("objectName",
String.Format("'{0}' object not found.",objectName));
}
finally
{
mLocked.ReleaseMutex();
}
}
}
}
Using SharedStorage from .NET:
public partial class Form1 : Form
{
SharedStorage ss = new SharedStorage();
public Form1()
{
InitializeComponent();
}
private void btnWrite_Click(object sender, EventArgs e)
{
ss.StorageName = this.txtStorageName.Text;
ss.AddOrUpdate(this.txtObjectName.Text, this.txtObjectValue.Text);
}
private void btnRead_Click(object sender, EventArgs e)
{
ss.StorageName = this.txtStorageName.Text;
this.txtObjectValue.Text = (string)ss[this.txtObjectName.Text];
}
}
Using SharedStorage from VB6:
Option Explicit On
Private Sub cmdWrite_Click()
Dim ss As New SharedStorages.SharedStorage
ss.StorageName = Me.txtStorageName.Text
ss.AddOrUpdate_2(Me.txtObjectName.Text, Me.txtObjectValue.Text)
ss = Nothing
End Sub
Private Sub cmdRead_Click()
Dim ss As New SharedStorages.SharedStorage
ss.StorageName = Me.txtStorageName.Text
Me.txtObjectValue.Text = ss(Me.txtObjectName.Text)
ss = Nothing
End Sub
Using SharedStorage from ASP:
(Remember to give first IIS_WPG group "modifiy" permission to c:\windows\temp folder.)
<html>
<head></head>
<body>
<%
Dim ss
Dim storageName
Dim objectName
Dim objectValue
if Request("txtStorageName")="" Then
storageName = "My Storage Name"
Else
storageName = Request("txtStorageName")
End If
if Request("objectName")="" Then
objectName = "Who I am ?"
Else
objectName = Request("objectName")
End If
if Request("objectValue")="" Then
objectValue = "ASP"
Else
objectValue = Request("objectValue")
End If
Set ss = Server.CreateObject("SharedStorages.SharedStorage")
ss.StorageName="My Storage Name"
if Request("hAction")="Write" Then
ss.AddOrUpdate objectName, objectValue
End If
if Request("hAction")="Read" Then
objectValue = ss(objectName)
End If
Set ss = Nothing
%>
<p>
<b>ASP World !</b>
</p>
<form id="form1" method="post" action="asp.asp">
<p>Storage Name:<input id="txtStorageName" name="txtStorageName" type="text" value="<% = storageName %>" /></p>
<p>Storage Name:<input id="objectName" name="objectName" type="text" value="<% = objectName %>" /></p>
<p>Storage Name:<input id="objectValue" name="objectValue" type="text" value="<% = objectValue %>" /></p>
<br />
<p><input id="btnWrite" type="submit" value="Write" onclick="document.getElementById('hAction').value='Write';" />
<input id="btnRead" type="submit" value="Read" onclick="document.getElementById('hAction').value='Read';"/></p>
<input id="hAction" name="hAction" type="hidden" />
</form>
</body>
</html>
Testing Inter-process data exchange:
Step 1: Write from a .NET Windows Application into the Shared Storage "My Storage Name".
Step 2: Read this value from an ASP.NET Application and from an ASP Application.
Step 3: Write from ASP and read value from ASP.NET (press first Write below, then Read above).
Step 4: Read same value from a .NET Windows Application.
Following is SharedStorage.cs:
(When compile remember to register SharedStorages.dll assembly into the Win registry to expose this class to COM Project Properties Build Register for COM Interop)
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace SharedStorages
{
/// <summary>
/// SharedStorage object that use Mutex and Files to share data between processes in the same Machine.
/// </summary>
/// <remarks>COM visibile.</remarks>
[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.AutoDual)]
public sealed class SharedStorage : IDisposable
{
#region Constants
private const string MUTEX_LOCKED_FORMAT = "{0} locked";
#endregion Constants
#region Static Fields
private static volatile string sharedFolder;
private static volatile bool destroyWhenDisposedDefault;
#endregion Static Fields
#region Fields
private bool destroyWhenDisposed;
private List<string> createdFiles;
private string storageName;
#endregion Fields
#region Static Constructors
static SharedStorage()
{
SharedStorage.sharedFolder = Environment.GetEnvironmentVariable ("temp", EnvironmentVariableTarget.Machine);
SharedStorage.destroyWhenDisposedDefault = true;
}
#endregion Static Constructors
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SharedStorage"/> class.
/// </summary>
public SharedStorage()
: this(String.Empty)
{}
/// <summary>
/// Initializes a new instance of the <see cref="SharedStorage"/> class.
/// </summary>
/// <param name="storageName">Name of the storage.</param>
public SharedStorage(string storageName)
: this(storageName, SharedStorage.destroyWhenDisposedDefault)
{}
/// <summary>
/// Initializes a new instance of the <see cref="SharedStorage"/> class.
/// </summary>
/// <param name="storageName">Name of the storage.</param>
/// <param name="destroyWhenDisposed">if set to <c>true</c> [destroy when disposed].</param>
public SharedStorage(string storageName, bool destroyWhenDisposed)
{
this.createdFiles = new List<string>();
this.destroyWhenDisposed = destroyWhenDisposed;
this.storageName = storageName;
}
#endregion Constructors
#region Static Properties
/// <summary>
/// Gets or sets the shared folder.
/// </summary>
/// <value>The shared folder.</value>
public static string SharedFolder
{
get { return SharedStorage.sharedFolder; }
set { SharedStorage.sharedFolder = value; }
}
/// <summary>
/// Gets or sets a value indicating whether [destroy when disposed default].
/// </summary>
/// <value>
/// <c>true</c> if [destroy when disposed default]; otherwise, <c>false</c>.
/// </value>
public static bool DestroyWhenDisposedDefault
{
get { return SharedStorage.destroyWhenDisposedDefault; }
set { SharedStorage.destroyWhenDisposedDefault = value; }
}
#endregion Static Properties
#region Properties
/// <summary>
/// Gets or sets the name of the storage.
/// </summary>
/// <value>The name of the storage.</value>
public string StorageName
{
get { return this.storageName; }
set { this.storageName = value; }
}
#endregion Properties
#region Public Methods
/// <summary>
/// Adds the or update.
/// </summary>
/// <param name="objectName">Name of the object.</param>
/// <param name="objectValue">The object value.</param>
/// <returns></returns>
public bool AddOrUpdate(string objectName, object objectValue)
{
bool createdNew = false;
using (Mutex mLocked = new Mutex(true, String.Format(SharedStorage.MUTEX_LOCKED_FORMAT, this.storageName), out createdNew))
{
try
{
if (!createdNew)
mLocked.WaitOne(); //Another thread/process is using it
bool fileCreated = this.writeFile(objectName, objectValue); //false if overwrite
if (fileCreated && this.destroyWhenDisposed)
this.createdFiles.Add(this.getStorageFileName(objectName));
return fileCreated;
}
finally
{
mLocked.ReleaseMutex();
}
}
}
/// <summary>
/// Removes the specified object name.
/// </summary>
/// <param name="objectName">Name of the object.</param>
/// <returns></returns>
public bool Remove(string objectName)
{
bool createdNew = false;
using (Mutex mLocked = new Mutex(true,String.Format (SharedStorage.MUTEX_LOCKED_FORMAT, this.storageName), out createdNew))
{
try
{
if (!createdNew)mLocked.WaitOne(); //Another thread/process is using it
return this.deleteFile(objectName);
}
finally
{
mLocked.ReleaseMutex();
}
}
}
/// <summary>
/// Existses the specified object name.
/// </summary>
/// <param name="objectName">Name of the object.</param>
/// <returns></returns>
public bool Exists(string objectName)
{
bool createdNew = false;
using (Mutex mLocked = new Mutex(true, String.Format(SharedStorage.MUTEX_LOCKED_FORMAT, this.storageName), out createdNew))
{
try
{
if (!createdNew)
mLocked.WaitOne(); //Another thread/process is using it
return this.fileExists(objectName);
}
finally
{
mLocked.ReleaseMutex();
}
}
}
#endregion Public Methods
#region Indexers
/// <summary>
/// Gets the <see cref="System.Object"/> with the specified object name.
/// </summary>
/// <value></value>
public object this[string objectName]
{
get
{
bool createdNew = false;
using (Mutex mLocked = new Mutex(true, String.Format(SharedStorage.MUTEX_LOCKED_FORMAT, this.storageName), out createdNew))
{
try
{
if (!createdNew)
mLocked.WaitOne(); //Another thread/process is using it
if (this.Exists(objectName))
return this.readFile(objectName);
else
throw new ArgumentOutOfRangeException("objectName", String.Format("'{0}' object not found.", objectName));
}
finally
{
mLocked.ReleaseMutex();
}
}
}
}
#endregion Indexers
#region Private Methods
/// <summary>
/// Gets the name of the storage file.
/// </summary>
/// <param name="objectName">Name of the object.</param>
/// <returns></returns>
private string getStorageFileName(string objectName)
{
string fileName = Path.Combine(SharedStorage.sharedFolder, String.Format("ss{0}_obj{1}.tmp", this.storageName.GetHashCode(), objectName.GetHashCode()));
Debug.WriteLine("getStorageFileName: " + fileName);
return fileName;
}
/// <summary>
/// Writes the file.
/// </summary>
/// <param name="objectName">Name of the object.</param>
/// <param name="objectValue">The object value.</param>
/// <returns></returns>
private bool writeFile(string objectName, object objectValue)
{
string fileName = this.getStorageFileName(objectName);
bool exists = File.Exists(fileName);
using (FileStream fs = File.Create(fileName))
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, objectValue);
}
return !exists;
}
/// <summary>
/// Reads the file.
/// </summary>
/// <param name="objectName">Name of the object.</param>
/// <returns></returns>
private object readFile(string objectName)
{
using (FileStream fs = File.Open(this.getStorageFileName(objectName), FileMode.Open))
{
IFormatter formatter = new BinaryFormatter();
return formatter.Deserialize(fs);
}
}
/// <summary>
/// Deletes the file.
/// </summary>
/// <param name="objectName">Name of the object.</param>
/// <returns></returns>
private bool deleteFile(string objectName)
{
if (this.fileExists(objectName))
{
File.Delete(this.getStorageFileName(objectName));
return true;
}
else
{
return false;
}
}
/// <summary>
/// Files the exists.
/// </summary>
/// <param name="objectName">Name of the object.</param>
/// <returns></returns>
private bool fileExists(string objectName)
{
return File.Exists(this.getStorageFileName(objectName));
}
#endregion Private Methods
#region IDispose members
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
using (Mutex mLocked = new Mutex(true, String.Format(SharedStorage.MUTEX_LOCKED_FORMAT, this.storageName)))
{
try
{
if (this.destroyWhenDisposed)
{
foreach (string createdFile in this.createdFiles)
{
if (File.Exists(createdFile))
File.Delete(createdFile);
}
}
}
finally
{
mLocked.ReleaseMutex();
}
}
}
#endregion IDispose members
#region Destructor
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the /// <see cref="SharedStorage"/> is reclaimed by garbage collection.
/// </summary>
~SharedStorage()
{
this.Dispose();
GC.SuppressFinalize(this);
}
#endregion Destructor
}
}
Remarks:
This is the easiest way to share data between two processes without using Remoting or more complex techniques.
If you set the static property DestroyWhenDisposedDefault to false you can get that instance of classes SharedStorage does not destroy the objects added to storage when they cease to be refernziate. In this way, data added to the Storage can be permanent.
Source Code:
Attached source code contains Shared Storage project, test web project, test win project and VB6 project which use SharedStorage class.