We are working on a Windows service in .NET in which we would like to use behavior of a COM control that can only be initiated by a mouse click.
To this extend, we have created a service that creates a non-visible form and starts a message pump to allow interacting with the form. We are able to communicate with the form and its controls by Windows message (e.g. BM_CLICK
works just fine), but we have not been able to simulate a mouse click using messages WM_LBUTTONDOWN
or WM_LBUTTONUP
.
The code below outputs Clicked on (100,100)
when started as a normal process, but the click is not detected when the code is started as a service. Does anybody have an idea about what is wrong with our code or is it simply a limitation of code running inside a service context?
Please note that we are not trying to access controls outside of our own Windows context.
static class Program
{
private static void Main()
{
var service = new Service();
if (Environment.UserInteractive)
{
Logger.Info("Starting as interactive.");
service.Start();
Console.WriteLine("Press a key to stop.");
Console.ReadKey(true);
service.Shutdown();
}
else
{
Logger.Info("Starting as service.");
ServiceBase.Run(new ServiceBase[] {service});
}
}
}
public partial class Service : ServiceBase
{
public Service()
{
InitializeComponent();
}
[DllImport("User32.dll")]
public static extern Int32 SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
private const int WM_LBUTTONDOWN = 0x201;
private const int WM_LBUTTONUP = 0x202;
protected override void OnStart(string[] args)
{
base.OnStart(args);
Start();
}
private static void Log(string s)
{
var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "output.txt");
File.AppendAllLines(path, new[] {s});
}
private static IntPtr CreateLParam(int x, int y)
{
return (IntPtr)((y << 16) | (x & 0xffff));
}
public void Start()
{
Log("Start");
try
{
var handle = IntPtr.Zero;
var thread = new Thread(o =>
{
var form = new Form {Left = 0, Top = 0, Width = 1000, Height = 1000};
handle = form.Handle;
form.MouseClick += (sender, eventArgs) => Log($"Clicked on ({eventArgs.X},{eventArgs.Y})");
Application.Run(form);
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
Thread.Sleep(2000); // sleep 2 seconds
var lParam = CreateLParam(100, 100);
SendMessage(handle, WM_LBUTTONDOWN, 0x00000000, lParam);
SendMessage(handle, WM_LBUTTONUP, 0x00000000, lParam);
PostMessage(handle, WM_LBUTTONDOWN, 0x00000000, lParam);
PostMessage(handle, WM_LBUTTONUP, 0x00000000, lParam);
}
catch (Exception ex)
{
Log("Exception: " + ex.Message);
}
}
public void Shutdown()
{
}
}
The answers to this question is that it depends on what is considered a simulation of a mouse click.
If simulating a mouse click means sending a BM_CLICK
to a button, the answer is 'Yes'. This will trigger the OnClick
event of the button and the associated logic.
If simulating a mouse click means sending WM_LBUTTONDOWN
or WM_LBUTTONUP
messages to controls created inside the service context and then having the control respond to those messages, then the answer is also 'Yes'. There doesn't seem to be an inherent restriction in how Windows deals with services that prevents these messages from being sent or received.
How a control processes these received messages is a different question. The comment from @SimonMourier showed that sometimes additional logic is needed to obtain the same behavior as when the code was executing in a normal non-service context.
In our use case, we are trying to trigger mouse click behavior on an RDP client and we haven't been able to do that yet. It seems that this control uses IME to capture user input and I will create a separate post for this question.
User contributions licensed under CC BY-SA 3.0