[C #] develop plug-ins based on IpcChannel process communication and remove MAF constraints

The process isolation of MAF cannot be realized by MEF and IOC. The advantage is that the process can be ended immediately. For example, in case of time-consuming tasks, it cannot be cancelled immediately, which wastes CPU and occupies resources in vain. However, the isolation of MAF is too strong. Multiple projects are stored in separate folders, resulting in the inability to share DLL files, which undoubtedly increases the size of the installation package, And at least 7 items (plus the main program) are required. To change an interface protocol, 7 item codes need to be changed. It's really cumbersome. Refer to the official IpcChannel communication , it only needs three items to build: ipserverchannel, ipclientchannel and RemoteObject. Here, start the server first, and then start the customer service to send a request. For improvement, the customer service actively starts the server and generates a url according to the guid to generate a unique service. In addition, the server does not need to reference the DLL in advance, Instead, the assembly and remote class specified by the customer service side are loaded. In this way, the server side is just an empty shell, similar to MAF's AddInProcess. Some small partners may have reached this step, It is found that when the customer service side calls the RegisterWellKnownClientType method of the same remote class for the second time, it throws "remote processing configuration failure, and the exception is" RemotingException: trying to redirect the activation of the type "MySC.MyComponent, MyServiceComponent", and the type has been redirected ", which is reasonable and can be done without redirection, Use RealProxy to directly get the instance of the remote class. The method of using this instance is proxy (Ipc communication is effective), while the method of directly creating the remote class is executed by the customer service side (non proxy). When the customer service side exits, it needs to ensure that the server side exits and use CreateJobObject to solve it (after looking at the MAF source code, I can't find how to ensure the plug-in exits)

Server:

using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Threading;

namespace IpcServerChannelApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(String.Join(" ", args));

            var guid = args[0];

            var list = from a in args.Skip(1)
                       let txts = a.Split('|')
                       select new
                       {
                           AssemblyName = txts[0],
                           TypeName = txts[1]
                       };

            // Create and register an IPC channel
            IpcServerChannel serverChannel = new IpcServerChannel(guid);
            ChannelServices.RegisterChannel(serverChannel, false);

            foreach (var item in list)
            {
                var assembly = Assembly.LoadFrom(item.AssemblyName);
                var type = assembly.GetType(item.TypeName, true, false);
                // Expose an object
                RemotingConfiguration.RegisterWellKnownServiceType(type, type.Name, WellKnownObjectMode.Singleton);
                Console.WriteLine("RegisterWellKnownServiceType: {0}", item.TypeName);
            }

            var readyEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "IpcProxy:" + guid);
            readyEvent.Set();

            // Wait for calls
            Console.WriteLine("Listening on {0}", serverChannel.GetChannelUri());
            Console.ReadLine();
        }
    }
}

Customer service end:

using IpcChannelModel;
using System;
using System.Data;
using System.Data.SQLite;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Security.Permissions;
using System.Threading;

namespace IpcClientChannelApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"E:\AppAnalyserFiles\data\DataBrowse.db";
            var connectString = ConnectionStringFactory(path);

            using (var proxy = new IpcProcessProxy<SqliteContract>())
            {
                if (proxy.Start())
                {
                    var sqliteContract = proxy.Contract;

                    if (sqliteContract.Open(connectString))
                    {
                        Console.WriteLine("connect sqlite :" + path);
                        var sql = "select count(*) from DataBrowse";
                        var count = sqliteContract.ExecuteScalar(sql);
                        Console.WriteLine(sql);
                        Console.WriteLine(count);
                    }
                }
            }

            Thread.Sleep(1000);

            using (var proxy = new IpcProcessProxy<SqliteContract>())
            {
                if (proxy.Start())
                {
                    var sqliteContract = proxy.Contract;
                  
                    if (sqliteContract.Open(connectString))
                    {
                        Console.WriteLine("connect sqlite :" + path);
                        var sql = "select count(*) from DataBrowse";
                        var count = sqliteContract.ExecuteScalar(sql);
                        Console.WriteLine(sql);
                        Console.WriteLine(count);
                    }
                }
            }

            Thread.Sleep(1000);
            {
                var sqliteContract = new SqliteContract();

                if (sqliteContract.Open(connectString))
                {
                    Console.WriteLine("connect sqlite :" + path);
                    var sql = "select count(*) from DataBrowse";
                    var count = sqliteContract.ExecuteScalar(sql);
                    Console.WriteLine(sql);
                    Console.WriteLine(count);
                }
            }

            Console.ReadLine();
        }

        private static string ConnectionStringFactory(string path)
        {
            var builder = new SQLiteConnectionStringBuilder
            {
                DataSource = path,
                Version = 3,
                UseUTF16Encoding = true,
                Pooling = false,
                CacheSize = 100,
                DateTimeFormat = SQLiteDateFormats.CurrentCulture,
                JournalMode = SQLiteJournalModeEnum.Wal,
                DefaultDbType = DbType.String,
                SyncMode = SynchronizationModes.Normal,
                BusyTimeout = 11
            };

            return builder.ToString();
        }
    }

    class IpcProcessProxy<T> : IDisposable where T : MarshalByRefObject, new()
    {
        public bool Start(int startUpTimeout = 10000)
        {
            var type = typeof(T);
            var guid = Guid.NewGuid().ToString();
            var startInfo = new ProcessStartInfo
            {
                CreateNoWindow = true,
                UseShellExecute = false,
                FileName = "IpcServerChannelApp.exe",
                Arguments = $"{guid} {type.Assembly.Location}|{type.FullName}"
            };

            process = Process.Start(startInfo);

            if (process != null && !process.HasExited)
            {
                hJob = NativeMethods.CreateJobObjectW(lpName: "Job:" + guid);

                if (hJob != IntPtr.Zero)
                {
                    // Set data for use in increasing limits
                    var size = Marshal.SizeOf(typeof(NativeMethods.JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
                    var newStructBasic = new NativeMethods.JOBOBJECT_BASIC_LIMIT_INFORMATION
                    {
                        LimitFlags = 0x00002000
                    };
                    var newStructExtend = new NativeMethods.JOBOBJECT_EXTENDED_LIMIT_INFORMATION
                    {
                        BasicLimitInformation = newStructBasic
                    };

                    var pStructAddr = Marshal.AllocHGlobal(size + 1);
                    Marshal.StructureToPtr(newStructExtend, pStructAddr, false);

                    // Set limits
                    NativeMethods.SetInformationJobObject(hJob, 9, pStructAddr, size);

                    Marshal.FreeHGlobal(pStructAddr);

                    NativeMethods.AssignProcessToJobObject(hJob, process.Handle);
                }

                // wait until it's ready
                using (var readyEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "IpcProxy:" + guid))
                {
                    if (readyEvent.WaitOne(startUpTimeout, false))
                    {
                        clientChannel = new IpcClientChannel();
                        ChannelServices.RegisterChannel(clientChannel, false);

                        var url = $"ipc://{guid}/{type.Name}";
                        var messageSink = clientChannel.CreateMessageSink(url, null, out _);

                        var myProxy = new RealProxyBase(typeof(T), url, messageSink);
                        t = (T)myProxy.GetTransparentProxy();
                        return true;
                    }
                }
            }

            Dispose();
            return false;
        }

        public void Dispose()
        {
            if (clientChannel != null)
            {
                ChannelServices.UnregisterChannel(clientChannel);
                clientChannel = null;
            }
            if (process != null)
            {
                if (!process.HasExited)
                    process.Kill();
                process = null;
            }
            if (hJob != IntPtr.Zero)
                NativeMethods.TerminateJobObject(hJob, 0);
        }

        public T Contract => t;

        private Process process;
        private IpcClientChannel clientChannel;
        private T t;
        private IntPtr hJob;
    }

    class RealProxyBase : RealProxy
    {
        private string url;
        private IMessageSink messageSink;

        [PermissionSet(SecurityAction.LinkDemand)]
        public RealProxyBase(Type myType, string url, IMessageSink messageSink) : base(myType)
        {
            this.url = url;
            this.messageSink = messageSink;
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
        public override IMessage Invoke(IMessage message)
        {
            message.Properties["__Uri"] = url;
            return messageSink.SyncProcessMessage(message);
        }
    }

    static class NativeMethods
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
        {
            public long PerProcessUserTimeLimit;
            public long PerJobUserTimeLimit;
            public int LimitFlags;
            public uint MinimumWorkingSetSize;
            public uint MaximumWorkingSetSize;
            public int ActiveProcessLimit;
            public long Affinity;
            public int PriorityClass;
            public int SchedulingClass;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
        {
            public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
            public IO_COUNTERS IoInfo;
            public uint ProcessMemoryLimit;
            public uint JobMemoryLimit;
            public uint PeakProcessMemoryUsed;
            public uint PeakJobMemoryUsed;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct IO_COUNTERS
        {
            public long ReadOperationCount;
            public long WriteOperationCount;
            public long OtherOperationCount;
            public long ReadTransferCount;
            public long WriteTransferCount;
            public long OtherTransferCount;
        }

        /// <summary>
        ///Create task object
        /// </summary>
        ///< param name = "lpjobattributes" > pointer to the structure of security related attributes, usually without < / param >
        ///< param name = "lpname" > task name < / param >
        /// <returns></returns>
        [DllImport("Kernel32.dll")]
        public static extern IntPtr CreateJobObjectW([In, Optional] IntPtr lpJobAttributes, [In, Optional] string lpName);

        /// <summary>
        ///Add process to task object
        /// </summary>
        ///< param name = "hjob" > task handle < / param >
        ///< param name = "hProcess" > process pointer < / param >
        /// <returns></returns>
        [DllImport("Kernel32.dll")]
        public static extern bool AssignProcessToJobObject([In] IntPtr hJob, [In] IntPtr hProcess);

        /// <summary>
        ///Set task limits
        /// </summary>
        ///< param name = "hjob" > task handle < / param >
        ///< param name = "jobobjectinformationclass" > set classification < / param >
        ///< param name = "lpjobobjectinformation" > set parameter structure address pointer < / param >
        ///< param name = "cbjobobjectinformationlength" > set parameter structure length < / param >
        /// <returns></returns>
        [DllImport("Kernel32.dll", SetLastError = true)]
        public static extern bool SetInformationJobObject([In] IntPtr hJob, [In] int jobObjectInformationClass, [In] IntPtr lpJobObjectInformation, [In] int cbJobObjectInformationLength);


        /// <summary>
        ///Add process to task object
        /// </summary>
        ///< param name = "hjob" > task handle < / param >
        ///< param name = "hProcess" > process pointer < / param >
        /// <returns></returns>
        [DllImport("Kernel32.dll")]
        public static extern bool TerminateJobObject([In] IntPtr hJob, [In] UInt32 uExitCode);
    }
}

Remote class:

using System;
using System.Collections.Generic;

namespace IpcChannelModel
{
    public class SqliteContract : MarshalByRefObject, IDisposable
    {
        private SqliteClient client = new SqliteClient();

        public bool Open(string connectString) => client.Open(connectString);

        public int Execute(string sql) => client.Execute(sql);

        public object ExecuteScalar(string sql) => client.ExecuteScalar(sql);

        public List<T> Query<T>(string sql) => client.Query<T>(sql);

        public void Dispose() => client.Dispose();
    }
}

  

using System;
using System.Collections.Generic;
using System.Data.SQLite;

namespace IpcChannelModel
{
    class SqliteClient : IDisposable
    {
        private SQLiteConnection conn;

        public bool Open(string connectString)
        {
            conn = new SQLiteConnection(connectString).OpenAndReturn();
            return conn != null;
        }

        public int Execute(string sql) => conn.Execute(sql);

        public object ExecuteScalar(string sql) => conn.ExecuteScalar(sql);

        public List<T> Query<T>(string sql) => throw new NotImplementedException();

        public void Dispose() => conn.Dispose();
    }
}

  

Keywords: C#

Added by invarbrass on Mon, 07 Mar 2022 15:24:22 +0200