samedi 14 janvier 2023

Method overload emitted via dynamic IL is not being called

So I am attempting to create a class that will wrap up all the bits necessary to implement a single-instance application (SIA). It should be a drop-in replacement for Application.Run<T>().

Since the SIA needs to override the WndProc method so that the running instance can bring itsself to the foreground, and that override needs to call the base class method, I landed on creating a wrapper class at runtime that inherits from whatever the Form class is which implements an override for WndProc.

The SIA portion is working, as is the wrapper class. But the WndProc override does not appear to be getting called.

Any direction regarding getting the dynamic WndProc override to be called would be appreciated.

Code so far:

using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

namespace ScreenSaverControl {
    public static class SingleInstanceApplication<T> where T : Form {
        private const string APP_NAME = "SCREENSAVER_CONTROL";
        private const string COMPANY_NAME = "DWAK";

        private const int HWND_BROADCAST = 0xffff;
        public static readonly int WM_SHOW_RUNNING_INSTANCE = Win32Helper.RegisterWindowMessage($"WM_SHOW_RUNNING_INSTANCE_{APP_NAME}");

        private static Mutex _singleInstanceMutex = new Mutex(true, $"{APP_NAME}::{COMPANY_NAME}");

        private static Form? _instanceForm = null;
        private static Action _showRunningInstanceAction = ActivateRunningInstance;



        public static void Run() {
            MessageBox.Show(WM_SHOW_RUNNING_INSTANCE.ToString());
            if (_singleInstanceMutex.WaitOne(TimeSpan.Zero, true)) {
                CreateSingleInstanceFormRunner();
                Application.Run(_instanceForm);
                _singleInstanceMutex.ReleaseMutex();
            } else {
                ActivateRunningInstance();
            }
        }


        private static void ActivateRunningInstance() {
            Win32Helper.PostMessage(
                (IntPtr)HWND_BROADCAST,
                WM_SHOW_RUNNING_INSTANCE,
                IntPtr.Zero,
                IntPtr.Zero);
        }


        private static void MessageHandler(ref Message m) {
            Debug.WriteLine($"*SIA: {m.Msg}");    // This never fires.
            if (null == _instanceForm) { return; }
            if (SingleInstanceApplication<T>.WM_SHOW_RUNNING_INSTANCE == m.Msg) {
                if (_instanceForm.WindowState == FormWindowState.Minimized) {
                    _instanceForm.WindowState = FormWindowState.Normal;
                }
                if (!_instanceForm.Visible) { _instanceForm.Show(); }

                bool topMost = _instanceForm.TopMost;
                _instanceForm.TopMost = true;
                _instanceForm.TopMost = topMost;
            }

            //base.WndProc(ref m);
        }



        delegate void RefAction(ref Message message);

        private static void CreateSingleInstanceFormRunner() {
            //if (null == _instanceForm) { return; }
            string methodName = "WndProc";
            Type type = typeof(T);
            MethodInfo? methodInfo = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
            if (null == methodInfo) {
                methodInfo = typeof(Form).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
            }
            Type[] methodParams = methodInfo!.GetParameters().Select(p => p.ParameterType).ToArray();

            AssemblyName assemblyName = new AssemblyName("SingleInstanceAssembly");
            AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("SingleInstanceModule");
            TypeBuilder typeBuilder = moduleBuilder.DefineType("SingleInstanceFormRunner", TypeAttributes.Public, type);

            // Define the new method with ref parameter
            MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Private, methodInfo.ReturnType, new Type[] { typeof(Message).MakeByRefType() });
            RefAction newWndProc = MessageHandler;
            ILGenerator il = methodBuilder.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldarg_2);
            il.Emit(OpCodes.Call, newWndProc.Method);

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Call, methodInfo);
            il.Emit(OpCodes.Ret);

            Type dynamicType = typeBuilder.CreateType();
            _instanceForm = (T)Activator.CreateInstance(dynamicType);
        }
    }




    internal static class Win32Helper {
        [DllImport("user32")]
        public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
        [DllImport("user32")]
        public static extern int RegisterWindowMessage(string message);
    }
}

Program.cs:

namespace ScreenSaverControl {
    internal static class Program {
        [STAThread]
        static void Main() {
            ApplicationConfiguration.Initialize();
            SingleInstanceApplication<MainDialog>.Run();
        }
    }
}

Test procedure + results:

  1. Run the first instance via VS debug run.
  2. Minimize the running instance.
  3. Run the second instance via the .exe in the output folder.
  • Observe the output window and notice that no "*SIA" messages are present.
  • Observe that the MessageHandler breakpoint never fires.
  • Observe that the debug (first) instance does not bring itself to the foreground.

For debugging purposes I implemented a WndProc override on MainDialog which outputs the m.Msg value to the output window. I DO see those messages.

protected override void WndProc(ref Message m) {
    Debug.WriteLine(m.Msg);
    base.WndProc(ref m);
}

I have confirmed that the running form is an instance of SingleInstanceFormRunner.

This code is WinForms on .NET 7.0.

Let me know if there's additional information that's needed to assist. Thank you.





Aucun commentaire:

Enregistrer un commentaire