Getting Started
Recently, I came across an example of inter-process communication (IPC) between programs written in C# and C++, implemented using Windows API functions CreateFileMapping
and MapViewOfFile
, by chance.
However, the sample I encountered was in a messy implementation state, leading me to believe it wasn't implemented based on organized theory. Therefore, I decided to clean it up and generalize it for application in Windows Scripting Host (WSH).
Using this, I succeeded in implementing IPC in the scripting engine embedded in Windows. As information about the WSH scripting engine was quite helpful from Qiita's resources, I thought of sharing my findings.
Required Knowledge
- CreateFileMappingA function(winbase.h) - Link
- Creating Named Shared Memory - Link
- Windows Script Host - Link
- Example COM Class - Link
Implementation (COM Class Side)
Implementation of the NamedSharedMemory class
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace WelsonJS
{
public class NamedSharedMemory
{
private IntPtr hFile;
private IntPtr hFileMappingObject;
private string lpName;
private static Dictionary<string, NamedSharedMemory> memoryMap = new Dictionary<string, NamedSharedMemory>();
[Flags]
public enum FileProtection : uint
{
PAGE_NOACCESS = 1u,
PAGE_READONLY = 2u,
PAGE_READWRITE = 4u,
PAGE_WRITECOPY = 8u,
PAGE_EXECUTE = 0x10u,
PAGE_EXECUTE_READ = 0x20u,
PAGE_EXECUTE_READWRITE = 0x40u,
PAGE_EXECUTE_WRITECOPY = 0x80u,
PAGE_GUARD = 0x100u,
PAGE_NOCACHE = 0x200u,
PAGE_WRITECOMBINE = 0x400u,
SEC_FILE = 0x800000u,
SEC_IMAGE = 0x1000000u,
SEC_RESERVE = 0x4000000u,
SEC_COMMIT = 0x8000000u,
SEC_NOCACHE = 0x10000000u
}
[Flags]
public enum FileMapAccess
{
FILE_MAP_COPY = 1,
FILE_MAP_WRITE = 2,
FILE_MAP_READ = 4,
FILE_MAP_ALL_ACCESS = 0xF001F
}
public class FileMappingNative
{
public const int INVALID_HANDLE_VALUE = -1;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFileMapping(IntPtr hFile, IntPtr lpAttributes, FileProtection flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, FileMapAccess dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint dwNumberOfBytesToMap);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenFileMapping(FileMapAccess dwDesiredAccess, bool bInheritHandle, string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hHandle);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint GetLastError();
}
public NamedSharedMemory(string lpName)
{
this.lpName = lpName;
Open();
}
public bool Open()
{
if (memoryMap.ContainsKey(lpName))
{
hFile = memoryMap[lpName].hFile;
hFileMappingObject = memoryMap[lpName].hFileMappingObject;
return true;
}
try
{
hFile = FileMappingNative.CreateFileMapping((IntPtr)(-1), IntPtr.Zero, FileProtection.PAGE_READWRITE, 0u, 1024u, lpName);
hFileMappingObject = FileMappingNative.MapViewOfFile(hFile, FileMapAccess.FILE_MAP_ALL_ACCESS, 0u, 0u, 1024u);
memoryMap.Add(lpName, this);
}
catch
{
return false;
}
return IsInitialized();
}
public bool IsInitialized()
{
return hFile != IntPtr.Zero;
}
public string ReadText()
{
try
{
if (hFile == IntPtr.Zero || hFileMappingObject == IntPtr.Zero)
{
throw new Exception("Could not access the shared memory");
}
return Marshal.PtrToStringAnsi(hFileMappingObject);
}
catch (Exception e)
{
return "Exception: " + e.Message;
}
}
public bool WriteText(string text, int size = 1024)
{
try
{
if (hFile == IntPtr.Zero || hFileMappingObject == IntPtr.Zero)
{
throw new Exception("Could not access the shared memory");
}
byte[] bytes = Encoding.ASCII.GetBytes(text);
byte[] array = new byte[size + 1];
Array.Copy(bytes, array, bytes.Length);
Marshal.Copy(array, 0, hFileMappingObject, size);
}
catch
{
return false;
}
return true;
}
public bool Clear(int size = 1024)
{
try
{
Marshal.Copy(new byte[size + 1], 0, hFileMappingObject, size);
}
catch
{
return false;
}
return true;
}
public bool Close()
{
try
{
if (hFile == IntPtr.Zero || hFileMappingObject == IntPtr.Zero)
{
throw new Exception("Could not access the shared memory");
}
FileMappingNative.UnmapViewOfFile(hFileMappingObject);
hFileMappingObject = IntPtr.Zero;
FileMappingNative.CloseHandle(hFile);
hFile = IntPtr.Zero;
}
catch
{
return false;
}
return true;
}
}
}
Implementation of the COM Class
using System;
using System.Runtime.InteropServices;
namespace WelsonJS
{
[ComVisible(true)]
public class Toolkit
{
public static readonly string ApplicationName = "WelsonJS";
// (omitted)
[ComVisible(true)]
public bool WriteTextToSharedMemory(string lpName, string text)
{
NamedSharedMemory mem = new NamedSharedMemory(lpName);
if (mem.IsInitialized())
{
return mem.WriteText(text);
}
return false;
}
[ComVisible(true)]
public string ReadTextFromSharedMemory(string lpName)
{
NamedSharedMemory mem = new NamedSharedMemory(lpName);
if (mem.IsInitialized()) {
return mem.ReadText();
}
return "";
}
[ComVisible(true)]
public bool ClearSharedMemory(string lpName)
{
NamedSharedMemory mem = new NamedSharedMemory(lpName);
if (mem.IsInitialized())
{
return mem.Clear();
}
return false;
}
[ComVisible(true)]
public bool CloseSharedMemory(string lpName)
{
NamedSharedMemory mem = new NamedSharedMemory(lpName);
if (mem.IsInitialized())
{
return mem.Close();
}
return false;
}
// (omitted)
}
}
Implementation (WSH JavaScript Runtime Side)
Definitions
function NamedSharedMemory(name) {
var _interface = create().getInterface(); // CreateObject("WelsonJS.Toolkit");
this.name = name;
this.writeText = function(text) {
return _interface.WriteTextToSharedMemory(this.name, text);
};
this.readText = function() {
return _interface.ReadTextFromSharedMemory(this.name);
};
this.clear = function() {
return _interface.ClearSharedMemory(this.name);
};
this.close = function() {
return _interface.CloseSharedMemory(this.name);
};
}
exports.NamedSharedMemory = NamedSharedMemory;
Testing
{
"sharedmemory": function() {
var Toolkit = require("lib/toolkit");
var mem = new Toolkit.NamedSharedMemory("welsonjs_test");
console.log("Writing a text the to shared memory...");
mem.writeText("nice meet you");
console.log("Reading a text from the shared memory...");
console.log(mem.readText() + ", too");
console.log("Cleaning the shared memory...");
mem.clear()
console.log(mem.readText());
mem.close()
console.log("Closing the shared memory...");
console.log("Done");
}
}
Run two or more test instances simultaneously to verify if IPC is functioning correctly.
Additional Information
The example used can also be found in the WelsonJS framework project. Besides the memory-based IPC covered here, you can also find an IPC implementation primarily using the ADODB.Stream
module built into Windows (implemented for WSH JavaScript runtime as pipe-ipc).