From: 011netservice@gmail.com
Date: 2022-04-24
Subject: IpcChannel.txt

歡迎來信交流


IPC = Intel Process Communication

IpcChannel Class
Provides a channel implementation that uses the IPC protocol to transmit messages.
.NET Framework 2.0 以後新增的功能, 可在同一台電腦中, 跨 Process 傳遞訊息(包括物件屬性、方法或事件).

public class IpcChannel : System.Runtime.Remoting.Channels.IChannelReceiver, System.Runtime.Remoting.Channels.IChannelSender, System.Runtime.Remoting.Channels.ISecurableChannel

----------
20211002

ChannelServices.SyncDispatchMessage(IMessage) Method
Synchronously dispatches the incoming message to the server-side chain(s) based on the URI embedded in the message.
[System.Security.SecurityCritical]
public static System.Runtime.Remoting.Messaging.IMessage SyncDispatchMessage (System.Runtime.Remoting.Messaging.IMessage msg);


Parameters
msg
IMessage
The message to dispatch.

Returns
IMessage
A reply message is returned by the call to the server-side chain.

Attributes
SecurityCriticalAttribute


ref: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.remoting.channels.channelservices.syncdispatchmessage?view=netframework-4.8&f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(System.Runtime.Remoting.Channels.ChannelServices.SyncDispatchMessage);k(TargetFrameworkMoniker-.NETFramework,Version%253Dv4.8);k(DevLang-csharp)%26rd%3Dtrue

// Create a custom 'RealProxy'.
public class MyProxy : RealProxy
{
   String myURIString;
   MarshalByRefObject myMarshalByRefObject;

   [PermissionSet(SecurityAction.LinkDemand)]
   public MyProxy(Type myType) : base(myType)
   {
      // RealProxy uses the Type to generate a transparent proxy.
      myMarshalByRefObject = (MarshalByRefObject)Activator.CreateInstance((myType));
      // Get 'ObjRef', for transmission serialization between application domains.
      ObjRef myObjRef = RemotingServices.Marshal(myMarshalByRefObject);
      // Get the 'URI' property of 'ObjRef' and store it.
      myURIString = myObjRef.URI;
      Console.WriteLine("URI :{0}", myObjRef.URI);
   }

   [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure)]
   public override IMessage Invoke(IMessage myIMessage)
   {
      Console.WriteLine("MyProxy.Invoke Start");
      Console.WriteLine("");

      if (myIMessage is IMethodCallMessage)
         Console.WriteLine("IMethodCallMessage");

      if (myIMessage is IMethodReturnMessage)
         Console.WriteLine("IMethodReturnMessage");

      Type msgType = myIMessage.GetType();
      Console.WriteLine("Message Type: {0}", msgType.ToString());
      Console.WriteLine("Message Properties");
      IDictionary myIDictionary = myIMessage.Properties;
      // Set the '__Uri' property of 'IMessage' to 'URI' property of 'ObjRef'.
      myIDictionary["__Uri"] = myURIString;
      IDictionaryEnumerator myIDictionaryEnumerator =
         (IDictionaryEnumerator) myIDictionary.GetEnumerator();

      while (myIDictionaryEnumerator.MoveNext())
      {
         Object myKey = myIDictionaryEnumerator.Key;
         String myKeyName = myKey.ToString();
         Object myValue = myIDictionaryEnumerator.Value;

         Console.WriteLine("\t{0} : {1}", myKeyName,
            myIDictionaryEnumerator.Value);
         if (myKeyName == "__Args")
         {
            Object[] myObjectArray = (Object[])myValue;
            for (int aIndex = 0; aIndex < myObjectArray.Length; aIndex++)
               Console.WriteLine("\t\targ: {0} myValue: {1}", aIndex,
                  myObjectArray[aIndex]);
         }

         if ((myKeyName == "__MethodSignature") && (null != myValue))
         {
            Object[] myObjectArray = (Object[])myValue;
            for (int aIndex = 0; aIndex < myObjectArray.Length; aIndex++)
               Console.WriteLine("\t\targ: {0} myValue: {1}", aIndex,
                  myObjectArray[aIndex]);
         }
      }

      IMessage myReturnMessage;

      myIDictionary["__Uri"] = myURIString;
      Console.WriteLine("__Uri {0}", myIDictionary["__Uri"]);

      Console.WriteLine("ChannelServices.SyncDispatchMessage");
      myReturnMessage = ChannelServices.SyncDispatchMessage(myIMessage);

      // Push return value and OUT parameters back onto stack.

      IMethodReturnMessage myMethodReturnMessage = (IMethodReturnMessage)
         myReturnMessage;
      Console.WriteLine("IMethodReturnMessage.ReturnValue: {0}",
         myMethodReturnMessage.ReturnValue);

      Console.WriteLine("MyProxy.Invoke - Finish");

      return myReturnMessage;
   }
}




----------
20210428

ref:
https://www.codeproject.com/Articles/19682/A-Pure-NET-Single-Instance-Application-Solution

First, a few words on remoting… Remoting allows an object residing on one application to be used by another. In order for remoting to work, there are some rules:
1. A class whose objects should be remotely instantiated must derive from the MarshalByRefObject class, to allow access to it across application domains.
2. Every remoting session must have a server, which will provide the objects, and a client which will use the objects.
3. In order for a client to be able to access a remote object, the server must register it and make it visible.
4. Data passed using remoting must be serializable, as remoting relies on serialization of objects.
  1. 跨 Process 傳遞的(Remote物件)必續繼承 MarshalByRefObject.
  2. 每一個執行 session 必須擁有一個 IPC Server 提供(Remote物件), 以及一個 Client 使用(Remote物件). 
  3. IPC Server 必須註冊(Remote 物件), 並且開放到 Client 可使用.
  4. (Remote 物件)本身必須可序列化.

----------
20210422

Examples:
https://docs.microsoft.com/en-us/dotnet/api/system.runtime.remoting.channels.ipc.ipcchannel?view=netframework-4.6&f1url=%3FappId%3DDev14IDEF1%26l%3DEN-US%26k%3Dk(System.Runtime.Remoting.Channels.Ipc.IpcChannel);k(TargetFrameworkMoniker-.NETFramework,Version%253Dv4.6);k(DevLang-csharp)%26rd%3Dtrue

The following code example shows how to use a IpcChannel to set up a remoting server and its client. The example contains three parts:

A server

A client

A remote object used by the server and the client.

The following code example shows a server.

using System;
using System.Runtime.Remoting.Channels.Ipc;
using System.Security.Permissions;

public class Server
{
[SecurityPermission(SecurityAction.Demand)]
    public static void Main(string[] args)
    {
        // Create the server channel.
        // Register the server channel.
		// server 註冊 IpcChannel ChannelName.
		// ChannelName = localhost:9090
        IpcChannel serverChannel = new IpcChannel("localhost:9090");
        System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(serverChannel);

        // Show the name of the channel.
        Console.WriteLine("The name of the channel is {0}.", serverChannel.ChannelName);

        // Show the priority of the channel.
        Console.WriteLine("The priority of the channel is {0}.", serverChannel.ChannelPriority);

        // Show the URIs associated with the channel.
        System.Runtime.Remoting.Channels.ChannelDataStore channelData =
            (System.Runtime.Remoting.Channels.ChannelDataStore) serverChannel.ChannelData;
        foreach (string uri in channelData.ChannelUris)
        {
            Console.WriteLine("The channel URI is {0}.", uri);
        }

        // Expose an object for remote calls.
        // server 註冊 typeof(RemoteObject)
        System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(RemoteObject), "RemoteObject.rem", System.Runtime.Remoting.WellKnownObjectMode.Singleton);

        // Parse the channel's URI.
        string[] urls = serverChannel.GetUrlsForUri("RemoteObject.rem");
        if (urls.Length > 0)
        {
            string objectUrl = urls[0];
            string objectUri;
            string channelUri = serverChannel.Parse(objectUrl, out objectUri);
            Console.WriteLine("The object URI is {0}.", objectUri);
            Console.WriteLine("The channel URI is {0}.", channelUri);
            Console.WriteLine("The object URL is {0}.", objectUrl);
        }

        // Wait for the user prompt.
        Console.WriteLine("Press ENTER to exit the server.");
        Console.ReadLine();
        Console.WriteLine("The server is exiting.");
    }
}

The following code example shows a client for this server.

using System;
using System.Runtime.Remoting.Channels.Ipc;
using System.Security.Permissions;

public class Client
{
[SecurityPermission(SecurityAction.Demand)]
    public static void Main(string[] args)
    {
        // Create the channel.
        // Register the channel.
        IpcChannel channel = new IpcChannel();
        System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(channel);

        // 取得 Entry 定義包含(typeof(RemoteObject), ChannelName, RemoteObjectName)
        // Holds values for an object type registered on the client as a server-activated type (single call or singleton).
		// ChannelName = localhost:9090
		// RemoteObjName = RemoteObject.rem
        System.Runtime.Remoting.WellKnownClientTypeEntry remoteType =
            new System.Runtime.Remoting.WellKnownClientTypeEntry(
			    typeof(RemoteObject), "ipc://localhost:9090/RemoteObject.rem");
				
        // 註冊(由 Server 建立的 RemoteObject Type)
        // Register as client for remote object.
        System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownClientType(remoteType);

        // Create a message sink.
		// 建立訊息接收器, 可以不用
        string objectUri;
        System.Runtime.Remoting.Messaging.IMessageSink messageSink =
            channel.CreateMessageSink("ipc://localhost:9090/RemoteObject.rem", null, out objectUri);
        Console.WriteLine("The URI of the message sink is {0}.", objectUri);
        if (messageSink != null)
        {
            Console.WriteLine("The type of the message sink is {0}.", messageSink.GetType().ToString());
        }

        // Create an instance of the remote object.
        RemoteObject service = new RemoteObject();

        // Invoke a method on the remote object.
		// 跨 Process 呼叫: Client 可呼叫由 Service 執行的方法, 並取得結果.
        Console.WriteLine("The client is invoking the remote object.");
        Console.WriteLine("The remote object has been called {0} times.",
            service.GetCount());
    }
}


The following code example shows the remote object used by the server and the client.
以下為跨 Process Client/Server 之間傳遞的物件.

using System;

// Remote object: 跨 Process Client/Server 之間傳遞的遠端物件.
// 這個物件由 Service Process 建立, 並且會保存在 Service Process 中.
public class RemoteObject : MarshalByRefObject
{
    private int callCount = 0;

    public int GetCount()  // 可跨 Process 呼叫的方法.
    {
        Console.WriteLine("GetCount has been called.");
        callCount++;
        return(callCount);
    }
}

Remarks
Channels are used by the.NET Framework remoting infrastructure to transport remote calls. When a client calls a remote object, the call is serialized into a message that is sent by a client channel and received by a server channel. After the message is received, it is deserialized and processed. Any returned values are transmitted by the server channel and received by the client channel.

The IpcChannel class is a convenience class combining the functionality of the IpcClientChannel class and the IpcServerChannel class.

Caution
When setting the exclusiveAddressUse property to false in the properties argument, several IpcServerChannel objects can be registered for the same named pipe. In such a case requests can go to any of the channels registered. This setting is considered secure only if ALCs are also used.


若 服務端和客戶端在不同的Windows帳戶運行的時候,會有驗證權限的問題, 可如下修改服務端的註冊方式:
Hashtable ht = new Hashtable();
ht["portName"] = "TestChannel";
ht["name"] = "ipc";
ht["authorizedGroup"] = "Everyone";
serverChannel= new IpcChannel(ht, null, provider);


----------
20140508
ref: https://kundesmurmur.blogspot.com/2014/05/ipcchannel.html

簡易IPCChannel 實作
Inter Process communication 指的是同一台電腦底下不同Process之間的溝通機制,
參考wiki-IPC可以發現,實現的方式有非常多種。
而在不同的程式語言基礎下,
各家提供的實作方式也都不太一樣。
Windows C#提供了一個蠻簡易的類別可以使用,
叫做IPCChannel,
沒錯!!就是這麼的直覺= = ,
其實他是直接組合了將兩個類別,
IpcClientChannel 和 IpcServerChannel 組合在一起。
參考MSDN: IPCChannel
以下是將程式碼簡化到最簡~
做出最簡易的程式碼。



首先是必備的遠端操控物件 RemoteObject
//務必繼承MarshalByRefObject
// Remote object: 跨 Process Client/Server 之間傳遞的遠端物件.
class RemoteObject : MarshalByRefObject
 {
  private int callCount = 0;
   
  //提供遠端呼叫操控使用的Method
  public int GetCount()
  {
   //這段程式碼會印在Server上
   Console.WriteLine( "GetCount has been called." );
   callCount++;
   //回傳結果至Client
   return ( callCount );
  }

 }

接著是IPCCommunicator

public class IPCCommunicator
 {
  private const String _serverChannel = "localhost:9090";
  private const String _remoteObjUri = "RemoteObject.rem";
  public void Server()
  {
   CreatIPCServerBy( _serverChannel );

   // Wait for the user prompt.
   Console.WriteLine( "Press ENTER to exit the server." );
   Console.ReadLine();
   Console.WriteLine( "The server is exiting." );
       }

  private void CreatIPCServerBy(String channelName)
  {
   // 新增一個IPCChannel Instance用來裝載IPC連線的相關資訊。
   // Create the server channel.
   IpcChannel serverChannel = new IpcChannel( channelName );

   // 將該Instance註冊為Server。
   // Register the server channel.
   ChannelServices.RegisterChannel( serverChannel );

   // 必須要註冊一個用來遠端操控的物件型別,Type就是RemoteObject 。
   // _remoteObjUri代表Uri的位置,加上原本Server的portName組成了Url=ipc://localhost:9090/RemoteObject.rem
   // 代表供外界操控連結的指定位置。
   // 採用Singleton代表所有聯繫同一個Url的操作物件都是操作同一個實體。
   // Expose an object for remote calls.
   RemotingConfiguration.RegisterWellKnownServiceType( typeof( RemoteObject ), _remoteObjUri, WellKnownObjectMode.Singleton );
  }

  public void Client()
  {
   //取得IPC Server的操控物件
   var service = GetRemoteObjBy( "ipc://" + _serverChannel + "/" + _remoteObjUri );
   Console.WriteLine( "The client is invoking the remote object." );
   Console.WriteLine( "The remote object has been called {0} times.", service.GetCount() );

  }


  private RemoteObject GetRemoteObjBy( String ObjUrl )
  {
   // 藉由Url取得已知操作物件。
   // Register as client for remote object.
   WellKnownClientTypeEntry remoteType = new WellKnownClientTypeEntry( typeof( RemoteObject ), ObjUrl );
   // 註冊該型別,使得所有Instance都能指向遠端操作的物件
   RemotingConfiguration.RegisterWellKnownClientType( remoteType );

   //回傳新的Instance,此時操作的就是Server上面的物件。
   return new RemoteObject();
  }
 }

以下為主程式:
class Program
 {
  static void Main( string[] args )
  {
   var ipcCommuncator = new IPCCommunicator();
_Retry:
   Console.WriteLine("Plz Choice Ur Want to ");
   switch (Console.ReadLine())
   {

    case "Server":
     ipcCommuncator.Server();
     Console.WriteLine( "type any key to Stop Server" );
     Console.Read();
     break;
    case "Client":
     ipcCommuncator.Client();
     Console.WriteLine( "type any key to Stop Client" );
     Console.Read();
     break;
    default:
     goto _Retry;
   }
  }
 }
 

----------
20201231
Server: Viewer
        IPCCommunicator ipcCommunicator;

        private void FMain_Shown(object sender, EventArgs e)
        {
			...
			ipcCommunicator = new IPCCommunicator();
			if (ipcCommunicator.ServerStart())
			{
				monitor = new DatabaseMonitor();
				monitor.Register(onDatabaseUpdated);
			}
			else
			{
				MessageBox.Show("無法建立IPC服務!", CProject.Me.SystemName, MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
			...
        }
		
        private void onDatabaseUpdated(object sender, EventArgs e)
        {
            this.InvokeIfRequired(() =>
            {
                TimerCleanScreen.Enabled = false;
            });
            // 讀取資料庫
            //
            TimerTraceData_Tick(sender, e);

            // 更新畫面
            //
            formQueryMap?.TimerJobData();
            if (lastUpdateTick != 0)
            {
                TimeSpan s = new TimeSpan(DateTime.Now.Ticks - lastUpdateTick);
                formQueryMap?.ShowMsg($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} 更新畫面 ({s.Seconds}s)");
            }
            else
            {
                formQueryMap?.ShowMsg($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss 更新畫面")}");
            }
            lastUpdateTick = DateTime.Now.Ticks;

            this.InvokeIfRequired(() =>
            {
                TimerCleanScreen.Enabled = true;
            });

        }
		

Client:		
        IPCCommunicator ipcCommunicator;
        DatabaseMonitor monitor;

        private void FMain_Load(object sender, EventArgs e)
        {
			...
			ipcCommunicator = new IPCCommunicator();
			monitor = ipcCommunicator.ClientStart();
			...
        }
		
        void TimerJob_TraceData()
        {
			...
			// 使用IPC通訊方式 通知Viewer MySQL資料已經更新
			try
			{
				monitor.UpdatedEventRise(new EventArgs());
			}
			catch
			{
				System.Diagnostics.Debug.WriteLine("提醒: Viewer的更新訊息通知埠未接上");
			}
			...
        }
		
		
		
		
IPCCommunicator.cs		
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Remoting.Channels;

namespace IPCCommLibrary
{
    public class IPCCommunicator
    {
        private const String _channelName = "localhost:60001";
        private const String _remoteObjName = "DatabaseMonitor.rem";

        public bool ServerStart()
        {
            return CreateIPCServer(_channelName);
        }

        private bool CreateIPCServer(String channelName)
        {
            try
            {
                IpcChannel serverChannel = new IpcChannel(_channelName);

                System.Runtime.Remoting.Channels.ChannelServices.
                    RegisterChannel(serverChannel, false);

                System.Runtime.Remoting.Channels.ChannelDataStore channelData =
                    (System.Runtime.Remoting.Channels.ChannelDataStore)
                    serverChannel.ChannelData;
                foreach (string uri in channelData.ChannelUris)
                {
                    System.Diagnostics.Debug.WriteLine($"The channel URI is {uri}.");
                }

                System.Runtime.Remoting.RemotingConfiguration.
                    RegisterWellKnownServiceType(
                        typeof(DatabaseMonitor),
                        _remoteObjName,
                        System.Runtime.Remoting.WellKnownObjectMode.Singleton);

                string[] urls = serverChannel.GetUrlsForUri("RemoteObject.rem");
                if (urls.Length > 0)
                {
                    string objectUrl = urls[0];
                    string objectUri;
                    string channelUri = serverChannel.Parse(objectUrl, out objectUri);
                    System.Diagnostics.Debug.WriteLine($"The object URI is {objectUri}.");
                    System.Diagnostics.Debug.WriteLine($"The channel URI is {channelUri}.");
                    System.Diagnostics.Debug.WriteLine($"The object URL is {channelUri}.");
                }
                return true;
            }
            catch(System.Runtime.Remoting.RemotingException e)
            {
                return false;
            }
        }

        public DatabaseMonitor ClientStart()
        {
            //IpcChannel channel = new IpcChannel();

            //System.Runtime.Remoting.Channels.ChannelServices.
            //   RegisterChannel(channel, false);

            System.Runtime.Remoting.WellKnownClientTypeEntry remoteType =
                new System.Runtime.Remoting.WellKnownClientTypeEntry(
                    typeof(DatabaseMonitor),
                    $"ipc://{_channelName}/{_remoteObjName}");
            System.Runtime.Remoting.RemotingConfiguration.
                RegisterWellKnownClientType(remoteType);

            //string objectUri;
            //System.Runtime.Remoting.Messaging.IMessageSink messageSink =
            //    channel.CreateMessageSink(
            //        $"ipc://{_channelName}/{_remoteObjName}", null,
            //        out objectUri);
            //if (messageSink != null)
            //{
            //    System.Diagnostics.Debug.WriteLine("The type of the message sink is {0}.",
            //        messageSink.GetType().ToString());
            //}

            return new DatabaseMonitor();
        }

    }
}

// Remote object: 跨 Process Client/Server 之間傳遞的遠端物件.
DatabaseMonitor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IPCCommLibrary
{
    public class DatabaseMonitor : MarshalByRefObject
    {
        public event EventHandler DatabaseUpdated;

        private static DatabaseMonitor _handler;

        public void UpdatedEventRise(EventArgs e)
        {
            //System.Diagnostics.Debug.WriteLine("db updated.");
            _handler?.DatabaseUpdated?.Invoke(this, e);
        }

        public void Register(EventHandler method)
        {
            _handler = this;
            DatabaseUpdated += new EventHandler(method);
        }
    }
}