|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn the halcyon days of COM architecture, the most common way to share data between processes was the use of a COM EXE class. A COM object, which runs in its own memory space, enabled retention of the data with the code of the process. This data could be shared between all COM objects created from the COM EXE classes. The above procedure was adopted by many of my clients when they needed to cache unchanged data (for example, a list of cities) on the server. However, when .NET was released, my clients and I looked for a way to achieve caching of unchanged data as we did previously. Our intuition indicated that we look for a .Net EXE class, but unfortunately, this was not found. What we discovered was that, we can create an EXE class and connect it by remoting. However, when we tested this approach, we ran into a performance setback. Deeper investigation of this issue revealed that, opening a TCP port to and from the client site was extremely slow. In addition, there was a further problem of the remoting procedure, which needed to retain a constant listener (that listened to the server port) to receive client requests. This made the task even more complicated. Bearing this in mind, it is obvious that infrastructure work becomes essential. We need to create a mechanism that will enable us to share data between processes in a timely manner. There are some options for building this mechanism. We will discuss two of them:
It is obvious which option is the optimal. Using MMF, we will build a mechanism that will enable us to share data between processes with negligible performance decrease. The challenge will be to work with MMF even though the managed code framework of .NET does not have any classes to handle MMF. To do this, we will go back to working with unmanaged code (WIN 32 API) via P/Invoke (interop). This article will demonstrate using MMF in .NET, to create a global cache for a .NET application. We achieve this by creating set of classes, that encapsulates the P/Invoke code to use MMF functionality, and to encapsulate the logic behind using MMF. While writing and reading from the MMF, we will gather our data between managed and unmanaged memory. Defining the problemSharing data between processes is one of the common problems that we face when we write applications. There are two main ways that a programmer can use memory sharing between processes:
Sharing data between processes in a system that does not allow one process to access the memory of another process is a recognized problem for which Win32 has answers (pipes, mail slots, memory map files, etc.). COM presents a simpler solution. If we create a COM EXE server, all the instances that are created from it by other processes, exist in the same memory area that was allocated for the COM EXE process. In this situation if we save data in the process. This data can be shared among all the instances. This is easily achievable, as you will see in the code that demonstrates caching of typedef map<_bstr_t,_RecordsetPtr> DATAMAP;
typedef DATAMAP::value_type vtData;
typedef vector<DATA, allocator<Data> > DATAVECTOR;
{
public:
LONG Unlock();
DWORD dwThreadID;
HANDLE hEventShutdown;
void MonitorShutdown();
bool StartMonitor();
bool bActivity;
DATAMAP DataMap;
DATAMAP::iterator MapIterator;
DATAVECTOR CachDataVector;
DATAVECTOR::iterator CachDataIterator;
HANDLE hMutex;
};
extern CExeModule _Module;
As you have probably guessed, if we leave out all the necessary code to prevent multi threading access to the shared data, we will encounter problems. While programming with COM, I use the COM EXE approach to cache rarely changed recordsets from databases as well as a mechanism to maintain state data between application calls in stateless applications on the client, or on the server side. When I started to write the new infrastructure for .NET, I looked for a solution for those problems. At first glance it seemed that, writing an EXE class with the same machinery would be acceptable. However, I soon discovered that there was a performance problem when I tried to put the machine under stress. The remote connection between the aspnet_wp.exe and the EXE which held the cached data, was not fast enough. What was needed was, a way to share data between the processes with minimum impact on the system performance. Defining the solutionThe solution to the problems is obtainable in the problem description. The data that I put in the code of the COM EXE is shared among all the instances of the COM EXE classes, because Win32 is actually holding one copy of the COM EXE code in the physical memory. Every process created from the EXE is mapped into this physical memory space. By using this characteristic from managed code, we can develop a satisfactory solution. Share memory is one of the options supplied with Win32, to handle inter-process communications (IPC), but there are also other options. Initially, we should therefore examine the IPC types to see if there are any other options that can used to obtain a solution. To craft working assemblies that will supply the request functionality, we first need to divide the work into three main blocks:
We will place these three code blocks into a DLL. In this way, every process that will attach the DLL into its memory area will be able to use its functionality to store and retrieve data from memory map files. At first I thought to show in this article, how to share the cached data across all the web farm servers. This functionality is necessary for maintaining state across web farms servers. As this aspect requires a very lengthy discussion, it could be the subject of a future article. As a result of the complexity of this solution, initially we will examine all the classes to be created and it’s the main task of each class in this solution.
Component design and codingWhat are MMF and how do they helps us?In the COM solution we are using one of the inter-process communications (IPC) available in Win32, the memory map file. As was indicated above, MMF will be used in our solution. We need to find a way to share data between processes and it appears that MMF is suitable. However, we first examine all the IPC types to determine if this is the right decision and if there are any other IPC types that can be used in this solution. IPC typesThe IPC types can be split into two main categories: local and network. The local IPC is designed for communication within the machine. The network is used for cross machine IPC. The local IPC consists of:
Network IPCs can be:
Examining all the IPC options, it becomes obvious that, the memory map files are the best choice to store data that can be shared among several processes on the machine. This is the only option that allows us to share large amounts of data. In addition to using MMF, we will use mutex to synchronize the write access of processes to the memory in this solution. Our next step will be to understand how MMF is working and how we use it from unmanaged code. This knowledge will be used to build a class, that will hold all the P/Invoke code needed to activate the unmanaged code from managed code. Converting Win32 API to .NETIn this article we will use the MMF only to map files. However, the Win32 API will also allow us mapping of the system page file. On reaching the declaration of creating file mapping, the mapping of system page file will be shown. The main reason for mapping files is that, Windows actually updates the underline file every time we write to the MMF. This is performed by the operating system, which means we are not aware of the update or of our performance damage. At the end of the process, we receive a file with our data. We can use this file to maintain the state even if the computer shuts down. The first step here is to create a file that eventually will be mapped into the memory so we can read and write to it. To create this file, we are going to use the Win32 API HANDLE CreateFile(
LPCTSTR lpFileName, // file name
DWORD dwDesiredAccess, // access mode
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes,// SD
DWORD dwCreationDisposition, // how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile // handle to template file
);
To use this function in .NET, we need to convert all the C types into .NET CLS types. To do this, we will create new class While writing the P/Invoke code, using attributes is very common. Attributes are classes that we apply to any target element (Assembly, Class, Constructor, Delegate, Enum, Event, Field, Interface, Method, Module, Note, Parameter, Property, ReturnValue, and Struct). By applying the attribute to the class, we can declare our intentions. Declarative programming has excited me for a long time. From the days of DOS, we were able to use DumpBin to see the intentions of the programmer regarding his code. This trend continues with COM, that enables us to enter more data about our intentions by using the [ DllImport("kernel32", SetLastError=true, CharSet=CharSet.Auto ) ];
The
public static extern IntPtr CreateFile (
String lpFileName,
int dwDesiredAccess,
int dwShareMode,
IntPtr lpSecurityAttributes,
int dwCreationDisposition,
int dwFlagsAndAttributes,
IntPtr hTemplateFile )
After compiling the The The declaration of the [ DllImport("kernel32", SetLastError=true, CharSet=CharSet.Auto ) ]
public static extern IntPtr CreateFile (
String lpFileName,
int dwDesiredAccess,
int dwShareMode,
IntPtr lpSecurityAttributes,
int dwCreationDisposition,
int dwFlagsAndAttributes,
IntPtr hTemplateFile )
The next step after creating a file, is to create an MMF object that actually maps the file to the memory area. Generally, the [ DllImport("kernel32", SetLastError=true, CharSet=CharSet.Auto) ]
public static extern IntPtr CreateFileMapping (
IntPtr hFile,
IntPtr lpAttributes,
int flProtect,
int dwMaximumSizeLow,
int dwMaximumSizeHigh,
String lpName );
There is nothing new in the declaration regarding the P/Invoke, but we need to understand the function parameters. The first is the handle to the file that we received in the previous function. If we will pass to this parameter [Flags]
public enum MapProtection
{
PageNone = 0x00000000,
// protection
PageReadOnly = 0x00000002,
PageReadWrite = 0x00000004,
PageWriteCopy = 0x00000008,
// attributes
SecImage = 0x01000000,
SecReserve = 0x04000000,
SecCommit = 0x08000000,
SecNoCache = 0x10000000,
}
The fourth and fifth parameters are responsible for setting the size of the file mapping. To solve the Windows 32-bit addressing limitation the API uses those parameters. There might be occasions when a bigger mapping size is needed, in which case we can set it with a 32-bit address space. With two parameters, we have 64-bits to set the mapping size. Naturally, for files that are less then 4 GB we don’t need to use the fourth parameter and so we will set it to 0. If we set the two parameters to 0, the length of the map file will be the length of the file. If we set the file map length, we must pay attention to the mapping file size, which should not be smaller than the file size. The last parameter is an important one. This parameter sets the name of the file that will be the unique identifier of the map file in the entire operating system. In this way, if more then one process uses this name to open the file, mapping will be mapped to the same file. When mapping to the system page we need to work around Microsoft design. The first parameter is the [ DllImport("kernel32", SetLastError=true, CharSet=CharSet.Auto) ]
public static extern IntPtr CreateFileMapping (
uint hFile,
IntPtr lpAttributes,
int flProtect,
int dwMaximumSizeLow,
int dwMaximumSizeHigh,
String lpName );
Creating memory file mapping with a unique name can be done by one process. Many processes can create many file mappings and obtain their MMF HANDLE. But this is not the case in this instance. To share data between processes, they need to share the same MMF. This sharing can be achieved by allowing any other process, except the process that opens the MMF, to use a unique name for opening an existing file mapping. By using the [ DllImport("kernel32", SetLastError=true, CharSet=CharSet.Auto) ]
public static extern IntPtr OpenFileMapping (
int dwDesiredAccess,
bool bInheritHandle,
String lpName );
The first parameter here sets the access rights to the file mapping. As with the protection levels, we create an enumeration that will hold all the options. The second parameter indicates if the child process will inherit this file-mapping handle. The last parameter is the unique name that we use in the The next step after obtaining the handle to the memory map file is to view all, or a part of the memory map file within the process. This operation creates a memory area in the process memory area, which points to the map file object. To do this, we need to declare the [ DllImport("kernel32", SetLastError=true) ]
public static extern IntPtr MapViewOfFile (
IntPtr hFileMappingObject,
int dwDesiredAccess,
int dwFileOffsetHigh,
int dwFileOffsetLow,
int dwNumBytesToMap );
The first parameter is the handle of the MMF that we obtain through Changes made to the memory area of the process map view are updated by the operating system to the physical memory and to the file when we view unmapping, or when the file-mapping object is removed. If there is a need to make the update immediately, you will need to call the [ DllImport("kernel32", SetLastError=true) ]
public static extern bool FlushViewOfFile (
IntPtr lpBaseAddress,
int dwNumBytesToFlush );
This function gets the base address of the view in the memory as the first parameter and the length of the view map file that needs to be written as the second parameter. To un-map the view you need to call: [ DllImport("kernel32", SetLastError=true) ]
public static extern bool UnmapViewOfFile ( IntPtr lpBaseAddress );
The only parameter here is the base address of the view of the file. Finally to release the memory map file object, we will use the [ DllImport("kernel32", SetLastError=true) ]
public static extern bool <CODE>CloseHandle ( IntPtr handle );
As stated in the beginning of the section, we created a wrapper class that encapsulates all the API function that we need in order to use the MMF functionality. Wrapping the MMF in a classesTill now we have created a wrapper static class that encapsulates all the P/Invoke work needed to work with the Win32 functions. The solution is the use of MMF as machinery to store data so it can be shared between processes. This solution is based on mapping files (Although using of system page file is also possible). In this section we will build classes that will hold the functionality and data that we need to use memory map files in our solution. In fact, we are going to build two main classes. The first one will hold all the functionality and data needed to: open file, creating and open memory map file, mapping and un-mapping view of file, and closing all the handles that were open. The second class will deal with the reading and writing data from the mapping view of the file (the virtual memory address of the hosting process) and flushing the memory map file. The memory map file classThe There are two sequences that might exist when we want to create a new instance of our class. The first is to create the object and use its functionality to obtain the base address of map view later. The second is to create the object with all the needed parameters so that we get the MMF handle at the end of the constructor. The logic behind the parametric constructor is simple: We first try to get a handle to the existing MMF by using its unique name. If we do not receive a valid handle, we will create a file for the given file name, and then create file mapping to the file using the given parameters. When the constructor has finished successfully, our class holds the handle to the MMF object. If someone has already opened an MMF object with the given name this function, execution time will be fast. If not, we need to do additional work (open file and mapping to it) that will result in a longer executing time public MemoryMappedFile( String fileName, MapProtection protection,
MapAccess access, long maxSize, String name)
{
IntPtr hFile = IntPtr.Zero;
try
{
Look for already open MMF object by its unique name. If the return handle is m_hMap = Win32MapApis.OpenFileMapping((int)access,false,name);
if (m_hMap == NULL_HANDLE )
{
int desiredAccess = GENERIC_READ;
if ( (protection == MapProtection.PageReadWrite) ||
(protection == MapProtection.PageWriteCopy) )
{
desiredAccess |= GENERIC_WRITE;
}
First, we will try to open the back file using the given parameters. If we succeed, we will use the file handle to create the MMF object. If the handle of the MMF object is hFile = Win32MapApis.CreateFile (
GetMMFDir() + fileName, desiredAccess, 0,
IntPtr.Zero, OPEN_ALWAYS, 0, IntPtr.Zero);
if (hFile != NULL_HANDLE)
{
m_hMap = Win32MapApis.CreateFileMapping (
hFile, IntPtr.Zero, (int)protection,
0,(int)(maxSize & 0xFFFFFFFF), name );
if (m_hMap != NULL_HANDLE)
m_maxSize = maxSize;
else
throw new FileMapIOException
( Marshal.GetHRForLastWin32Error() );
}
else
throw new FileMapIOException
( Marshal.GetHRForLastWin32Error() );
}
}
catch (Exception Err)
{
throw Err;
}
finally
{
If the handle of file is in use, we need to free it: if ( (hFile != NULL_HANDLE) && (hFile != INVALID_HANDLE_VALUE) )
Win32MapApis.CloseHandle(hFile);
}
}
While creating the physical file, use The other approach is to create an empty The public bool Open ( MapAccess access, String name )
{
bool RV = true;
try
{
m_hMap = Win32MapApis.OpenFileMapping ( (int)access, false, name );
if ( m_hMap == NULL_HANDLE )
RV=false;
return RV;
}
catch
{
return RV;
}
}
In this solution the most used function to get the MMF object handler is public bool OpenEx (int size,string FileName, MapProtection protection,
string name,MapAccess access)
{
bool RV = false;
IntPtr hFile = INVALID_HANDLE_VALUE;
try
{
Attempt to open an already created MMF object. If none exists, check if a backing file exists. If this is present, we obtain its size and the handle of the file by using m_hMap = Win32MapApis.OpenFileMapping ( (int)access, true, name );
if ( m_hMap == NULL_HANDLE)
{
Check if a backed physical file exists on disk: if ( System.IO.File.Exists (GetMMFDir() + FileName) )
{
long maxSize = size;
OFSTRUCT ipStruct = new OFSTRUCT ();
string MMFName = GetMMFDir() + FileName;
Open the physical file: hFile = Win32MapApis.OpenFile (MMFName, ipStruct ,2);
// determine file access needed
// we'll always need generic read access
int desiredAccess = GENERIC_READ;
if ( (protection == MapProtection.PageReadWrite) ||
(protection == MapProtection.PageWriteCopy) )
{
desiredAccess |= GENERIC_WRITE;
}
// open or create the file
// if it doesn't exist, it is created
Create a file-mapping object: m_hMap = Win32MapApis.CreateFileMapping (
hFile, IntPtr.Zero, (int)protection,
(int)((maxSize >> 32) & 0xFFFFFFFF),
(int)(maxSize & 0xFFFFFFFF), name );
RV = true;
}
else
RV = false;
}
else
RV = true;
return RV;
}
catch
{
return false;
}
Finally, close down the physical file handle: finally
{
if ( (hFile != NULL_HANDLE) && (hFile != INVALID_HANDLE_VALUE) )
Win32MapApis.CloseHandle(hFile);
}
}
While writing the HFILE OpenFile(
LPCSTR lpFileName, // file name
LPOFSTRUCT lpReOpenBuff, // file information
UINT uStyle // action and attributes
);
typedef struct _OFSTRUCT {
BYTE cBytes;
BYTE fFixedDisk;
WORD nErrCode;
WORD Reserved1;
WORD Reserved2;
CHAR szPathName[OFS_MAXPATHNAME];
} OFSTRUCT, *POFSTRUCT;
The structure holds a new concern. One of its members is an array of chars that require attention. To use the structure in managed code, we will create new class that will represent all the data existing in the Win32 structure: [StructLayout (LayoutKind.Sequential )]
public class OFSTRUCT
{
public const int OFS_MAXPATHNAME = 128;
public byte cBytes;
public byte fFixedDisc;
public UInt16 nErrCode;
public UInt16 Reserved1;
public UInt16 Reserved2;
[MarshalAs (UnmanagedType.ByValTStr,SizeConst=OFS_MAXPATHNAME)]
public string szPathName;
}
In this class declaration, we use the There are two new issues regarding P/Invoke in the [ DllImport("kernel32", SetLastError=true, CharSet=CharSet.Ansi ) ]
public static extern IntPtr OpenFile (String lpFileName,
[Out,MarshalAs (UnmanagedType.LPStruct )]
OFSTRUCT lpReOpenBuff,
int uStyle);
After getting the handle of the MMF object, the next step is to map view of the MMF object. The public MapViewStream MapView ( MapAccess access, long offset, int size,
string path )
{
IntPtr baseAddress = IntPtr.Zero;
bool iSWritable=true;
MapProtection protection=MapProtection.PageReadOnly;
try
{
Use the WIN32 function to obtain the base address in the hosted process of the map object viewing: baseAddress = Win32MapApis.MapViewOfFile (
m_hMap, (int)access,
(int)((offset >> 32) & 0xFFFFFFFF),
(int)(offset & 0xFFFFFFFF), 0 );
if ( baseAddress != IntPtr.Zero )
{
if ( access == MapAccess.FileMapRead )
protection = MapProtection.PageReadOnly;
else
protection = MapProtection.PageReadWrite;
m_maxSize = size;
Copy the bytes from the unmanaged memory heap to the byte array: byte[] bytes = new byte[m_maxSize];
Marshal.Copy(baseAddress,bytes,0,(int)m_maxSize);
if ( access == MapAccess.FileMapRead )
iSWritable = false;
else
iSWritable = true;
Return a new instance of our implementation of the return new MapViewStream(baseAddress, bytes,iSWritable);
}
return null;
}
catch
{
throw new FileMapIOException ( Marshal.GetHRForLastWin32Error() );
}
}
The Reading and writing the dataThis part of the task is the most important, complicated, and interesting. I’ll try to walk through it as clearly as I can. Until this point of our task, we managed to use the MMF WIN32 API from C# to get the base address of the MMF mapping in the hosted process of our DLL. From this point on, we need to move data between the memory area, which holds the map data, and the managed code. This is not an easy task because of the managed code notion. While working in managed code, the CLR is responsible for handling the memory. This fact prevents us from reaching the memory directly. The base address of the mapped MMF demands direct access to memory, in order to read and write data from it. There are solutions for this problem using C#. The first and best known is unsafe code. We can write blocks of unsafe code into our solution. From these blocks, we can manipulate the memory directly. The second option is to use the At this point, assemble all the puzzle parts that we have already put together. Using MMF, we can share data between processes. MMF gives us a base address of the area in the memory that the data has mapped in our process. To manipulate this data, we will use the I tried the above approach. I wrote my own Before writing our
After we become familiar with the Most of the functionality that exists in the CLR public class MapViewStream : MemoryStream //, IDisposable
{
private IntPtr m_baseaddress = IntPtr.Zero;
This object will be created when the user uses the public MapViewStream(IntPtr baseaddress, byte[] bytes,bool iSWritable) :
base(bytes,iSWritable)
{
m_baseaddress = baseaddress;
}
After storing the data and creating the base class we do not need to overload the regular public void Write ( byte[] buffer, int count )
{
try
{
Marshal.Copy(buffer,0,m_baseaddress,count);
}
catch(Exception Err)
{
throw Err;
}
}
We will add a public void Write (string str)
{
try
{
Marshal.Copy(str.ToCharArray (),0,m_baseaddress,str.Length);
}
catch(Exception Err)
{
throw Err;
}
}
The public string Read ()
{
try
{
return Marshal.PtrToStringUni(m_baseaddress);
}
catch(Exception Err)
{
throw Err;
}
}
The public override void Flush()
{
base.Flush();
Win32MapApis.FlushViewOfFile(m_baseaddress,0);
}
When we close our public override void Close()
{
Flush();
base.Close();
Win32MapApis.UnmapViewOfFile(m_baseaddress);
}
}
In this section we built a class that inherits from Caching the dataOur goal here is to share data between processes. Because of the leak of the object size information in the CLR, most of the object that we will cache will use serialization. Basically, we can serialize the object into a file and synchronize the access to the file. This way, every process can grab the object by using de-serialization. But the problem with this solution is that it is slow because of the need to make an IO operation every time we want to de-serialize an object from file, and because of the thread synchronization (to access the resource). Alternatively, using MMF we can be more efficient. We need to create the MMF object only once. Every process is then mapping the MMF object to a space in its memory. This way we can communicate between processes by using the name of the MMF object and improving the speed since reading a value its merely reading data from memory instead of from a file. In most of the cases, we will serialize\de-serialize object into\from file and then by using MMF we will access the file, as we access memory. The good news is that the serialization functionality is largely built into the CLR, so we are able to just use it. There is just one exception. If we want to serialize our types, we need to implement In this solution we are maintaining the cache. This means that we need to keep a lot of objects in our machinery. We need to watch the cached objects, so that if we need to get a certain object from the cache, we will know "who" is the object, what is its length, and where it is stored. The simple way to know "who" is the object is to give it a name, which can be followed by the object length. This way, every process can obtain the same object from the cache by using its name. The hash table object looks to be the most suitable object for storing and retrieving data quickly, but using this object will cause performance problems. The hash table will be used any time the machinery will be called and changed (add, get, change, and remove object). Every time we need to change the hash table, we need to use serialization because we cannot know its length. You may remember that serialization is more time consuming then handling the memory directly. Therefore, to be more dynamic, we will save the cached objects and manage data in strings, which we can write\read directly from unmanaged code. Another issue that we need to consider is where to keep the data that we cache. Basically, there are two approaches to the issue. The first is to keep all the data in the same MMF object (file). This approach requires us to know the dynamic size of the managed cached data string, so that we can extract the string, and to know where the request object is located and what is its length. In this case we will save the address offset of the cached object in the memory. Besides the cached object offset, we need to keep the length of the managed cached data string in the first 4 bytes. With this approach, we will have just one file that we will map. Using the other approach, we will have a file for every object. We will serialize or write objects directly to memory and reflect them in the file. In this way, we just need to keep the name of the file mapped for every object. The advantage here is that we don’t need to keep the size of the managed cache data string and the offset of every object, as every object will be allocated a special file to hold its data. Furthermore, this approach allows us to keep data of the object in unique files that can be preserved following computer shut down. Using files its much easier to maintain data that rapidly changes its size. The second alternative has been selected mainly for its simplicity to store rapidly changing objects. To simplify, we will name every back file with the object store name so we can know the object location by its name. To maintain all the backed files that we create in the same place, set special folder MMFfiles to save those files. Every file in this folder will have the name the user gave to the object plus a .nat extension. The cached object manage data string will be named always as The
Beside the public function, there are some private functions that are responsible for special tasks in the overall process. We walk through these while working on the class.
public class DevCache
{
int m_StringMMFs="";
private System.Threading.Mutex oMutex =
new System.Threading.Mutex(false,"MmfUpdater");
MemoryMappedFile oStringMMF = new MemoryMappedFile();
private const string ObjectNamesMMF = "ObjectNamesMMF";
Writing to MMFIn this section we will cover the private function that actually writes the cached objects into the MMF memory. We support writing object via serialization and without using serialization. The private int WriteString2MMF(string InObject, string obectName)
{
MemoryMappedFile map = new MemoryMappedFile();
Set the size variant to the string object length. We will use this to open MMF object and map view of MMF: int iSize = InObject.Length;
oMutex.WaitOne ();
We use the if (!map.OpenEx (iSize,obectName + ".nat",MapProtection.PageReadWrite,
obectName,MapAccess.FileMapAllAccess))
map = new MemoryMappedFile (obectName + ".nat",
MapProtection.PageReadWrite,
MapAccess.FileMapAllAccess,iSize,obectName);
Calling MapViewStream stream = map.MapView(MapAccess.FileMapAllAccess, 0,
(int)iSize,obectName + ".nat" );
Passing a string to the stream.Write(InObject);
stream.Close();
oMutex.ReleaseMutex();
return iSize;
}
The private int WriteObjectToMMF(object InObject, string obectName,int ObjectSize)
{
Check the if (InObject.GetType() == typeof(String) )
{
Add the this.StringMMFs = obectName;
return WriteString2MMF(InObject.ToString(), obectName);
}
MemoryMappedFile map = new MemoryMappedFile();
MemoryStream ms = new MemoryStream ();
BinaryFormatter bf= new BinaryFormatter();
int iSize = 0;
Use binary formatter to serialize the object to stream and obtain is size: bf.Serialize (ms,InObject);
iSize = (int)ms.GetBuffer().Length;
oMutex.WaitOne ();
Open MMF object with the object name: if (!map.OpenEx (iSize,obectName + ".nat",MapProtection.PageReadWrite,
obectName,MapAccess.FileMapAllAccess))
map = new MemoryMappedFile (obectName + ".nat",
MapProtection.PageReadWrite,MapAccess.FileMapAllAccess,
iSize,obectName);
Get MapViewStream stream = map.MapView(MapAccess.FileMapAllAccess, 0,
(int)iSize,obectName + ".nat" );
stream.Write(ms.GetBuffer(),iSize);
Update the MMF object and un-map the view of MMF object: stream.Close();
oMutex.ReleaseMutex();
return iSize;
}
The if (!oStringMMF.OpenEx(4,"stringMmf.nat", MapProtection.PageReadWrite,
"StringMmf",MapAccess.FileMapAllAccess))
return "";
MapViewStream stream = oStringMMF.MapView(MapAccess.FileMapAllAccess,0,4,
"stringMmf.nat");
string str = stream.Read();
stream.Close();
return str;
Setting a value in the string is more complicated. While setting the string, we want to add new objects names if they do not exist in the string, and we want to remove objects from the list in case the user replaces the object type from the string of any object. If we cannot open the MMF object, we create a new one using the existing if(m_StringMMFs.IndexOf (value) == -1 || value == "")
{
m_StringMMFs += value;
if (!oStringMMF.OpenEx(m_StringMMFs.Length ,"stringMmf.nat",
MapProtection.PageReadWrite,"StringMmf",
MapAccess.FileMapAllAccess))
oStringMMF = new MemoryMappedFile( "stringMmf.nat",
MapProtection.PageReadWrite,
MapAccess.FileMapAllAccess,m_StringMMFs.Length,
"StringMmf");
MapViewStream stream = oStringMMF.MapView(MapAccess.FileMapAllAccess,
0,m_StringMMFs.Length, "stringMmf.nat");
If the last object name in the string is removed we need to clear the last object name: if (m_StringMMFs == "")
stream.Write(" ");
else
stream.Write(m_StringMMFs & "*");
stream.Close();
}
AddObjectAdding an object is a complicated task. There are some scenarios where we can achieve this function:
To check if we access the mechanism on the first attempt, we will use the When a cache objects manage string MMF exists, we need to call the string from the memory and check if the request object name exists in the string. If the object name does not exist, we will use the If the request cache object name exists in the cache objects manage data string, all we need to do is just update the MMF object of the given object with the new value: public void AddObject(string objName, object inObject, bool UpdateDomain)
{
MemoryMappedFile map = new MemoryMappedFile();
Create string builder that holds the cache object manage data string: System.Text.StringBuilder oFilesMap= new System.Text.StringBuilder()
int iSize = 0;
oMutex.WaitOne ();
try
{
Check if a MMF exists for the cache objects manage data string: if (! map.OpenEx(0,ObjectNamesMMF + ".nat",MapProtection.PageReadWrite ,
ObjectNamesMMF,MapAccess.FileMapAllAccess))
{
If it does not exist, create the MMF and feed it with the request cache object data. Add to the cache objects manage data string the new cache object and it size. Create an MMF for the cache object manage data string, and write the string content: //Create MMF for the object and serialize it
iSize = WriteObjectToMMF(inObject,objName,0);
//add object name and mmf name to hash
oFilesMap.Append(objName + "#" + System.Convert.ToString(iSize) +
"@");
//create main MMF
WriteString2MMF(oFilesMap.ToString(),ObjectNamesMMF);
}
else
{
BinaryFormatter bf = new BinaryFormatter();
If cache objects manage data string MMF exists, call up its content: MapViewStream mmfStream = map.MapView(MapAccess.FileMapAllAccess, 0,
0,ObjectNamesMMF + ".nat");
mmfStream.Position = 0;
oFilesMap.Append (mmfStream.Read());
long StartPosition = mmfStream.Position;
mmfStream.Close ();
Check if the cache objects manage data string contains the request cache object name: if (oFilesMap.ToString().IndexOf(objName + "#") > -1 )
{
If the request cache object exists, we need to change its content. While doing this, we need to check if the new data is a string or an object and to act as is required: MemoryMappedFile MemberMap = new MemoryMappedFile();
bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream ();
Check if the request cache object data type is string. If not, we need to use serialization to get the object size: if (inObject.GetType() != typeof(String))
bf.Serialize (ms,inObject);
iSize = (int)ms.GetBuffer().Length;
Open the existing request cache object MMF object: MemberMap.OpenEx(iSize,objName + ".nat",
MapProtection.PageReadWrite,
objName,MapAccess.FileMapAllAccess);
MapViewStream stream = MemberMap.MapView
(MapAccess.FileMapAllAccess, 0,iSize,objName + ".nat");
stream.Position = 0;
Check again for the type. If not a string, we use the serialization byte array to write the data, and remove the request cache object from the string that holds not serialized objects. If the type is string, we simply send the string to be written and add the request cache object to not serialized string: if (inObject.GetType() != typeof(String))
{
stream.Write(ms.GetBuffer(),iSize);
m_StringMMFs = m_StringMMFs.Replace (objName,"");
StringMMFs = "";
}
else
{
stream.Write (inObject.ToString());
iSize = inObject.ToString().Length;
StringMMFs = objName;
}
stream.Close();
Change and update the new size of the object in the cache objects manage data string: string[] str = oFilesMap.ToString().Split('@');
for(int i = 0; i < str.Length; i++)
{
if (str[i].IndexOf (objName) > -1)
{
string strVal = str[i].Substring( str[i].IndexOf('#')+1);
oFilesMap.Replace(str[i],objName + "#" + iSize);
break;
}
}
WriteString2MMF(oFilesMap.ToString() ,ObjectNamesMMF);
}
else
{
If the request cache object name does not exist in the cache objects manage data string. We create a file and MMF for the new object and load them with the new object data: iSize = WriteObjectToMMF(inObject,objName,0);
Then we update the cache object manage data string and its MMF object: MapViewStream stream = map.MapView (MapAccess.FileMapAllAccess,
0,0,ObjectNamesMMF + ".nat" );
// update the main HashTable
oFilesMap.Append(objName + "#" + System.Convert.ToString(iSize)
+ "@");
// serialize new Hash
stream.Write (oFilesMap.ToString());
stream.Position = 0;
stream.Close();
}
}
}
catch (Exception e)
{
throw new Exception("Cannot Open File "+objName,e);
}
finally
{
oMutex.ReleaseMutex ();
}
}
GetObjectGetting an object from the cache is fairly simple. We get the object name from the cache objects manage data string. If the object name exists we can open the MMF object that resembles the object, de-serialize or read the object from the MMF and return it to the caller: public object GetObject(string objName)
{
MemoryMappedFile map = new MemoryMappedFile();
MemoryMappedFile mapOfName = new MemoryMappedFile();
string oFilesMap = "";
try
{
oMutex.WaitOne ();
Check if the cache objects manage data string exists. If not return null: if (! map.OpenEx (0,ObjectNamesMMF + ".NAT",MapProtection.PageReadWrite
,ObjectNamesMMF,MapAccess.FileMapAllAccess ))
throw new Exception ("No Desc FileFound");
Get the string from the MMF object: BinaryFormatter bf = | ||||||||||||||||||||