Memory based IPC on WSH Javascript runtime (C#, C++ compatible)

## 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](https://learn.microsoft.com/ko-kr/windows/win32/api/winbase/nf-winbase-createfilemappinga)
* Creating Named Shared Memory - [Link](https://learn.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory)
* Windows Script Host - [Link](https://en.wikipedia.org/wiki/Windows_Script_Host)
* Example COM Class - [Link](https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/interop/example-com-class)

## Implementation (COM Class Side)

### Implementation of the NamedSharedMemory class

```csharp
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

```csharp
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

```js
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

```js
{
    "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](https://github.com/gnh1201/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).