winforms 未调用通过动态IL发出的方法重载

xu3bshqb  于 2023-01-17  发布在  其他
关注(0)|答案(1)|浏览(95)

因此,我尝试创建一个类来 Package 实现单示例应用程序(SIA)所需的所有位,它应该是Application.Run<T>()的直接替代品。
由于SIA需要覆盖WndProc方法,以便运行的示例可以将其自身置于前台,并且该覆盖需要调用基类方法,因此我在运行时创建了一个 Package 器类,该 Package 器类继承了实现WndProc覆盖的Form类。
SIA部分工作正常, Package 类也是如此,但是WndProc覆盖似乎没有被调用。
关于调用动态WndProc覆盖的任何指示都将是可取的。
目前代码:

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();
        }
    }
}

测试程序+结果:
1.通过VS调试运行来运行第一个示例。
1.最小化正在运行的示例。
1.通过输出文件夹中的.exe运行第二个示例。

  • 观察输出窗口,注意不存在“*SIA”消息。
  • 观察MessageHandler断点从未触发。
  • 注意调试(第一个)示例没有将其自身置于前台。

出于调试的目的,我在MainDialog上实现了一个WndProc覆盖,它将m.Msg值输出到输出窗口。

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

我已经确认运行的窗体是SingleInstanceFormRunner的示例。
此代码是.NET 7.0上的WinForms。
如果你需要更多的信息,请告诉我。谢谢。

jmp7cifd

jmp7cifd1#

1.您的MethodAttributes不正确,它们不代表受保护方法的重写,请更改为:

MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodName,
     MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig, 
     methodInfo.ReturnType, 
     new Type[] { typeof(Message).MakeByRefType() });

1.通过IL进行的第一次调用有错误,您尝试使用一个参数调用静态方法,因此请更改为:

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);

1.由于方法的访问级别,它仍然会失败,请将其设置为public:

public static void MessageHandler(ref Message m)
{
     // ...
}

1.我要说的是,实际上没有必要在这里使用Reflection.Emit-切换到使用source generators来生成 Package 器类(将需要更改API),这将完成您想要的工作。

相关问题