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:
- Run the first instance via VS debug run.
- Minimize the running instance.
- 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.
CodePudding user response:
Your
MethodAttributes
are incorrect, they do not represent an override of protected method, change to:MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig, methodInfo.ReturnType, new Type[] { typeof(Message).MakeByRefType() });
Your first call IL is faulty, you are trying to call a static method with one argument, so change to:
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);
Still it will fail due to access level of method, make it public:
public static void MessageHandler(ref Message m) { // ... }
I would say that there is no actual need to use
Reflection.Emit
here - switch to using source generators to generate wrapper class (will need require change of API) which will do what you want.