Let's say I have two programs called launcher.exe and launchee.exe. The launcher display a button which -when clicked- starts launchee.exe and launchee.exe is a simple hello world program.
If I do nothing to prevent it, when the user will "pin to the taskbar" the hello world program, it will pin launchee.exe and will not go through the launcher to start the application.
What is the best way to tell Windows to pin launcher.exe and not launchee.exe ?
To make things concrete here's an example of implementation of launcher.exe in C#:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Diagnostics;
public class Launcher : Form
{
static public void Main ()
{
Application.Run (new Launcher ());
}
public Launcher ()
{
Button b = new Button ();
b.Text = "Launch";
b.Click += new EventHandler (Button_Click);
Controls.Add (b);
}
private void Button_Click (object sender, EventArgs e)
{
Process.Start("launchee.exe");
System.Environment.Exit(0);
}
}
and launchee.exe:
using System;
using System.Drawing;
using System.Windows.Forms;
public class Launchee : Form
{
static public void Main ()
{
Application.Run (new Launchee ());
}
public Launchee ()
{
Label b = new Label();
b.Text = "Hello World !";
Controls.Add (b);
}
}
I propose an anwser based on binding the low level API to access the AppUserModelID. I find this solution fragile and messy. It is largely inspired by the Windows Api CodePack that seems to have been discontinued by Microsoft. I hope someone will propose a cleaner solution.
Its purpose is to set the AppUserId to be "Stackoverflow.chain.process.pinning" and manually set the RelaunchCommand as well as the DisplayName properties (they have to be set together according to AppUserModelID).
To use it in the example implementation, one needs to call TaskBar.SetupLauncher(this) and TaskBar.SetupLaunchee(this) respectively in the Launcher and Launchee constructors.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
internal struct PropertyKey
{
Guid formatId;
int propertyId;
internal PropertyKey(Guid guid, int propertyId)
{
this.formatId = guid;
this.propertyId = propertyId;
}
}
[StructLayout(LayoutKind.Explicit)]
internal struct PropVariant
{
[FieldOffset(0)] internal ushort vt;
[FieldOffset(8)] internal IntPtr pv;
[FieldOffset(8)] internal UInt64 padding;
[DllImport("Ole32.dll", PreserveSig = false)]
internal static extern void PropVariantClear(ref PropVariant pvar);
internal PropVariant(string value)
{
this.vt = (ushort)VarEnum.VT_LPWSTR;
this.padding = 0;
this.pv = Marshal.StringToCoTaskMemUni(value);
}
internal void Clear()
{
PropVariantClear (ref this);
}
}
[ComImport,
Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPropertyStore
{
int GetCount([Out] out uint propertyCount);
void GetAt([In] uint propertyIndex, [Out, MarshalAs(UnmanagedType.Struct)] out PropertyKey key);
void GetValue([In, MarshalAs(UnmanagedType.Struct)] ref PropertyKey key, [Out, MarshalAs(UnmanagedType.Struct)] out PropVariant pv);
void SetValue([In, MarshalAs(UnmanagedType.Struct)] ref PropertyKey key, [In, MarshalAs(UnmanagedType.Struct)] ref PropVariant pv);
void Commit();
}
internal static class TaskBar {
[DllImport("shell32.dll")]
static extern int SHGetPropertyStoreForWindow(
IntPtr hwnd,
ref Guid iid /*IID_IPropertyStore*/,
[Out(), MarshalAs(UnmanagedType.Interface)]out IPropertyStore propertyStore);
internal static class Key {
private static Guid propGuid = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3");
internal static PropertyKey AppId = new PropertyKey(propGuid, 5);
internal static PropertyKey RelaunchCommand = new PropertyKey(propGuid, 2);
internal static PropertyKey DisplayName = new PropertyKey(propGuid, 4);
}
private static void ClearValue(IPropertyStore store, PropertyKey key) {
var prop = new PropVariant();
prop.vt = (ushort)VarEnum.VT_EMPTY;
store.SetValue(ref key, ref prop);
}
private static void SetValue(IPropertyStore store, PropertyKey key, string value) {
var prop = new PropVariant(value);
store.SetValue(ref key, ref prop);
prop.Clear();
}
internal static IPropertyStore Store(IntPtr handle) {
IPropertyStore store;
var store_guid = new Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99");
int rc = SHGetPropertyStoreForWindow(handle, ref store_guid, out store);
if (rc != 0) throw Marshal.GetExceptionForHR(rc);
return store;
}
internal static void SetupLauncher(Form form) {
IntPtr handle = form.Handle;
var store = Store(handle);
SetValue (store, Key.AppId, "Stackoverflow.chain.process.pinning");
form.FormClosed += delegate { Cleanup(handle); };
}
internal static void SetupLaunchee(Form form) {
IntPtr handle = form.Handle;
var store = Store(handle);
SetValue (store, Key.AppId, "Stackoverflow.chain.process.pinning");
string exePath = System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "launcher.exe");
SetValue (store, Key.RelaunchCommand, exePath);
SetValue (store, Key.DisplayName, "Test");
form.FormClosed += delegate { Cleanup(handle); };
}
internal static void Cleanup(IntPtr handle) {
var store = Store(handle);
ClearValue (store, Key.AppId);
ClearValue (store, Key.RelaunchCommand);
ClearValue (store, Key.DisplayName);
}
}
I had the same problem and finally managed to find a nice solution for this. Setting the RelaunchCommand was not working for me any more with the newest Windows 10 Update.
For simplicity i used "App" as name instead of "Launchee" as it could be confused with Launcher easily.
Short Version:
Launcher.exe and App.exe are grouped together in the taskbar. Laucher.exe does the update part and starts the App.exe as usual. If you choose 'Pin to taskbar' when the Launcher is running, it will pin the Launcher to the taskbar. If the App is already started and you pin this one to the taskbar, it will still pin the Launcher to the taskbar as they are grouped together.
Long Version:
That both Applications are grouped together in the taskbar, they share the same AppID. This could be done like described here: How to group different apps in Windows task bar?
The starter should have an UI that an icon is shown in the taskbar. In my case it is a SplashScreen as UI. It starts the App.exe and the Laucher waits two seconds until the App.exe is started that they share for a small amount of time the same symbol in the taskbar. Then the Launcher could close and if you pin the App afterwards it will pin the Launcher to the taskbar.
Here you could find an example Application which is started, it's a WPF App:
using System.Runtime.InteropServices;
using System.Windows;
namespace TestApp
{
public partial class MainWindow : Window
{
[DllImport("shell32.dll", SetLastError = true)]
private static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
private const string AppID = "73660a02-a7ec-4f9a-ba25-c55ddbf60225"; // generate your own with: Guid.NewGuid();
public MainWindow()
{
SetCurrentProcessExplicitAppUserModelID(AppID);
InitializeComponent();
Topmost = true; // to make sure UI is in front once
Topmost = false;
}
}
}
Second WPF App which is the Launcher/Starter:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
namespace TestStarter
{
public partial class MainWindow : Window
{
[DllImport("shell32.dll", SetLastError = true)]
private static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
private const string AppID = "73660a02-a7ec-4f9a-ba25-c55ddbf60225"; // generate your own with: Guid.NewGuid();
public MainWindow()
{
SetCurrentProcessExplicitAppUserModelID(AppID);
InitializeComponent();
Process.Start(#"C:\Test\TestApp.exe");
ExitAfterDelay();
}
private async void ExitAfterDelay()
{
await Task.Delay(2000);
Environment.Exit(0);
}
}
}
Related
I'm trying to make a program that opens a browser on each multi-display.
When I did it with a notepad, it worked. However, when it's a browser didn't work and showed the error "System.InvalidOperationException: Process must exit before requested information can be determined". I will appreciate your help with this situation.
This is my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp5
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern int MoveWindow(IntPtr hwnd, int x, int y,
int nWidth, int nHeight, int bRepaint);
private void Form1_Load(object sender, EventArgs e)
{
foreach (Screen item in Screen.AllScreens)
{
//Open a web browser
System.Diagnostics.Process process1 = System.Diagnostics.Process.Start("C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe");
//System.Diagnostics.Process process2 = System.Diagnostics.Process.Start("notepad.exe");
process1.WaitForInputIdle();
//process2.WaitForInputIdle();
//Move the browser window
MoveWindow(process1.MainWindowHandle, item.Bounds.X, item.Bounds.Y, item.Bounds.Width, item.Bounds.Height, 1);
//MoveWindow(process2.MainWindowHandle, item.Bounds.X, item.Bounds.Y, item.Bounds.Width, item.Bounds.Height, 1);
}
}
}
}
It seems that msedge.exe use a host process to start different tabs, so we can't use the created process ID to handle it.
Compare created process ID with processes in the Task Manager, the
process 38756 is missing.
Another tool to browse edge relative processes is the built-in task manager of edge.
We can't find process 38756 as well.
So re-search the target edge process is necessary.
This repo https://github.com/alex-tomin/Tomin.Tools.KioskMode demostrate how to move chrome window into different monitors and set as full screen.
I tried to extract the necessary and modify your code like below, hope it helps:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace TestWinFormsApp
{
// Get full definition of SetWindowPosFlags here:
// https://www.pinvoke.net/default.aspx/Enums/SetWindowPosFlags.html
[Flags]
public enum SetWindowPosFlags : uint
{
SWP_NOREDRAW = 0x0008,
SWP_NOZORDER = 0x0004
}
// Get full definition of ShowWindowCommands here:
// https://www.pinvoke.net/default.aspx/Enums/ShowWindowCommand.html
public enum ShowWindowCommands
{
Maximize = 3,
Restore = 9,
}
public static class WinApi
{
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommands nCmdShow);
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load_1(object sender, EventArgs e)
{
var ignoreHandles = new List<IntPtr>();
foreach (Screen item in Screen.AllScreens)
{
this.StartEdgeProcess();
var windowHandle = this.GetWindowHandle("msedge", ignoreHandles);
ignoreHandles.Add(windowHandle);
WinApi.ShowWindow(windowHandle, ShowWindowCommands.Restore);
WinApi.SetWindowPos(windowHandle, IntPtr.Zero, item.Bounds.Left, item.Bounds.Top, 800, 600, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOREDRAW);
WinApi.ShowWindow(windowHandle, ShowWindowCommands.Maximize);
}
}
private void StartEdgeProcess()
{
var process = new Process();
process.StartInfo.FileName = "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe";
process.StartInfo.Arguments = "about:blank" + " --new-window -inprivate ";
process.Start();
process.WaitForInputIdle();
}
private IntPtr GetWindowHandle(string processName, List<IntPtr> ignoreHandlers)
{
IntPtr? windowHandle = null;
while (windowHandle == null)
{
windowHandle = Process.GetProcesses()
.FirstOrDefault(process => process.ProcessName == processName
&& process.MainWindowHandle != IntPtr.Zero
&& !string.IsNullOrWhiteSpace(process.MainWindowTitle)
&& !ignoreHandlers.Contains(process.MainWindowHandle))
?.MainWindowHandle;
}
return windowHandle.Value;
}
}
}
I am trying to make a Windows Forms Application be able to repeatedly show and then hide itself.
The idea is to create a small app that overlays an image on the screen every time a modifier key like Num Lock or Caps lock is pressed. I have the detection of the keyboard keys working flawlessly, but I am not having much luck with creating a form that I can show and then hide repeatedly.
The way I see it, (please correct me if I'm wrong) there are two possible ways to make the form behave like I want it to:
Start the form normally in Program.cs and then hold the logic for hiding and showing the form and displaying the image inside Form1.cs
The form would call this.Hide() and this.Show() to hide and show itself, but whenever I try to do this, I cannot get this.Hide() to hide the form; the form remains visible on top of all of the open windows.
Hold the logic for hiding and showing the form in Program.cs and then just hold the logic to display the image in Form1.cs
I've been toying around with the code from this guide to show and hide the from from Program.cs with a class wrapper for the form, but as it turns out, form.Showdialog() prevents any further code execution until the form is closed.
I've been playing around with the code myself, and neither of the above methods have worked. Am I thinking about this in the wrong way entierly? Can a WFA ever behave in the way I want it to?
The code for this is a bit messy, and I'm not sure what parts are the most relevant here, but I'll do my best to include it here:
Program.cs:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
namespace KeyboardIndicators {
// ApplicationContext wrapper
public abstract class TrayIconApplicationContext : ApplicationContext {
private NotifyIcon lockIcon;
private ContextMenu lockIconContext;
protected TrayIconApplicationContext() {
// Wire up the ApplicationExitHandler to ApplicationExit events
Application.ApplicationExit += this.ApplicationExitHandler;
lockIconContext = new ContextMenu {
};
// Create lockIcon tray icon and make it visible
lockIcon = new NotifyIcon {
ContextMenu = lockIconContext,
Text = Application.ProductName,
Icon = new Icon("icon.ico"),
Visible = true
};
}
protected NotifyIcon LockIcon { get { return lockIcon; } }
protected ContextMenu LockIconContext { get { return lockIconContext; } }
// ApplicationExit event handler
private void ApplicationExitHandler(object sender, EventArgs e) {
this.OnApplicationExit(e);
}
// Performs cleanup to end the application
protected virtual void OnApplicationExit(EventArgs e) {
// TODO(Neil): Add meaningful thread cleanup here soon
if (lockIcon != null) {
lockIcon.Visible = false;
lockIcon.Dispose();
}
if (lockIconContext != null)
LockIconContext.Dispose();
}
}
// TrayIconApplicationContext wrapper for Form1 to control the activation of the form window
class FormApplicationContext : TrayIconApplicationContext {
public FormApplicationContext() {
// Add Exit menu item
MenuItem exit = new MenuItem("E&xit");
this.LockIconContext.MenuItems.Add(exit);
exit.Click += this.ExitContextMenuClickHandler;
//KeyboardIndicators indicators = new KeyboardIndicators();
//indicators.RunListener();
{
using(Form form = new Form1("NumLock", true))
form.ShowDialog();
}
}
private void ExitContextMenuClickHandler(object sender, EventArgs eventArgs) {
this.ExitThread();
}
}
public class KeyboardIndicators {
class LockState {
// Is the numlock key on?
public bool Num;
// Is the capslock key on?
public bool Caps;
// Is the scroll lock key on?
public bool Scroll;
}
public void RunListener() {
try {
// Store the old keyboard lock state
LockState prevState = new LockState() {
Num = Control.IsKeyLocked(Keys.NumLock),
Caps = Control.IsKeyLocked(Keys.CapsLock),
Scroll = Control.IsKeyLocked(Keys.Scroll)
};
while (true) {
// Store the new keyboard lock state
LockState newState = new LockState() {
Num = Control.IsKeyLocked(Keys.NumLock),
Caps = Control.IsKeyLocked(Keys.CapsLock),
Scroll = Control.IsKeyLocked(Keys.Scroll)
};
//TODO(Neil): Handle simultaneous presses better, i.e. queue the balloon tips
if (newState.Num != prevState.Num) {
Form1 form = new Form1("NumLock", newState.Num);
} else if (newState.Caps != prevState.Caps) {
Form1 form = new Form1("CapsLock", newState.Caps);
} else if (newState.Scroll != prevState.Scroll) {
Form1 form = new Form1("ScrollLock", newState.Scroll);
}
// Set the previous lock state to the new one in prep for the next iteration
prevState = newState;
// Sleep for 500ms
Thread.Sleep(500);
}
} catch (ThreadAbortException) { /* No need to do anything, just catch the ThreadAbortException.*/ }
}
}
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1("NumLock", true));
Application.Run(new FormApplicationContext());
}
}
}
Form1.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace KeyboardIndicators {
public partial class Form1 : Form {
public Form1(String activatedModifier, bool lockState) {
InitializeComponent();
ShowForm(activatedModifier);
//this.Show();
//this.Hide();
}
public void ShowForm(String activatedModifier) {
PictureBox pictureBox = new PictureBox();
Image myBitmap = Image.FromFile("cube.png");
Size bitmapSize = new Size(myBitmap.Width, myBitmap.Height);
switch (activatedModifier) {
case "NumLock":
break;
case "CapsLock":
break;
case "ScrollLock":
break;
}
this.Size = bitmapSize;
pictureBox.ClientSize = bitmapSize;
pictureBox.Image = myBitmap;
pictureBox.Dock = DockStyle.Fill;
this.Controls.Add(pictureBox);
this.FormBorderStyle = FormBorderStyle.None;
}
protected override CreateParams CreateParams {
get {
CreateParams createParams = base.CreateParams;
createParams.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
return createParams;
}
}
}
}
As an example I have created a Winforms Application which displays "NUM" and / or "CAPS" if any of those keys is pressed otherwise the form is hidden.
The detection of the keys is based on the C# Low Level Keyboard Hook.
Program.cs
using System;
using System.Windows.Forms;
namespace WinFormsKeyHook
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
Form1.cs
Note: Add a label named 'label1' to Form1 in Designer.
using System.Collections.Generic;
using System.Windows.Forms;
namespace WinFormsKeyHook
{
public partial class Form1 : Form
{
private static bool _caps;
private static bool _num;
public Form1()
{
InitializeComponent();
KeyboardHook kh = new KeyboardHook();
kh.KeysToObserve.AddRange(new List<Keys> { Keys.CapsLock, Keys.NumLock });
kh.InstallHook();
kh.KeyDown = key => ProcessKeyDown(key);
_caps = Control.IsKeyLocked(Keys.CapsLock);
_num = Control.IsKeyLocked(Keys.NumLock);
}
private void ProcessKeyDown(Keys key)
{
if (key == Keys.CapsLock)
{
_caps = !_caps;
}
if (key == Keys.NumLock)
{
_num = !_num;
}
this.ShowState(_num, _caps);
}
internal void ShowState(bool num, bool caps)
{
if (!num && !caps)
{
this.Hide();
return;
}
this.label1.Text = "";
this.label1.Text += num ? "NUM " : "";
this.label1.Text += caps ? "CAPS" : "";
if (!this.Visible)
{
this.Show();
}
}
}
}
KeyboardHook.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WinFormsKeyHook
{
public class KeyboardHook
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private IntPtr _hookID = IntPtr.Zero;
public List<Keys> KeysToObserve { get; set; } = new List<Keys>();
public Action<Keys> KeyDown;
public void InstallHook()
{
_hookID = SetHook(HookCallback);
}
~KeyboardHook()
{
UnhookWindowsHookEx(_hookID);
}
public IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}
}
public delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
public IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
var key = (Keys)vkCode;
Console.WriteLine(key);
KeyDown(key);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
}
I wrote a little program to close my notebook screen when the lid closes and turn the screen on when the lid opens.
I tried a lot, but there is still something wrong in my code. The function "OnLidPowerEvent" doesn't work.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Runtime.InteropServices;
namespace DeviceWatcher
{
public partial class Service1 : ServiceBase
{
[DllImport("User.dll", CharSet = CharSet.Unicode)]
public static extern int SendMessage(int hWnd,int Msg,int wParam,int lParam);
public const int HWND_BROADCAST = 0xffff;
public const int WM_SYSCOMMAND = 0x0112;
public const int SC_MONITORPOWER = 0xf170;
// Broadcast Msg to close screen
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
public static extern int RegisterPowerSettingNotification(IntPtr Recipient, Guid guid, int flags);
static Guid GUID_LIDSWITCH_STATE_CHANGE = new Guid(0xBA3E0F4D, 0xB817, 0x4094, 0xA2, 0xD1, 0xD5, 0x63, 0x79, 0xE6, 0xA0, 0xF3);
public const int DEVICE_NOTIFY_SERVICE_HANDLE = 1;
// using DEVICE_NOTIFY_SERVICE_HANDLE as flag
public delegate int callbackEx(int control, int eventType, IntPtr eventData, IntPtr context);
callbackEx hamster = OnLidPowerEvent;
[DllImport("Advapi32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr RegisterServiceCtrlHandlerEx(string lpServiceName, callbackEx lpHandlerProc, IntPtr lpContext);
internal struct POWERBROADCAST_SETTING
{
public Guid PowerSetting;
public uint DataLength;
public byte Data;
}
private static POWERBROADCAST_SETTING pos = new POWERBROADCAST_SETTING();
public Service1()
{
InitializeComponent();
//this.CanHandlePowerEvent = true;
// C# Service can not deal GUID_LIDSWITCH_STATE_CHANGE
this.ServiceName = "DeviceWatcher";
IntPtr serviceControlHandle = RegisterServiceCtrlHandlerEx("DeviceWatcher", hamster, IntPtr.Zero);
RegisterPowerSettingNotification(serviceControlHandle,GUID_LIDSWITCH_STATE_CHANGE,DEVICE_NOTIFY_SERVICE_HANDLE);
// should I using this.serviceHandle or this.ServiceHandle
using (System.IO.StreamWriter sw = new System.IO.StreamWriter("C:\\log.txt", true))
{
sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Service Init.");
}
}
private static int OnLidPowerEvent(int control, int eventType, IntPtr eventData, IntPtr context)
{
using (System.IO.StreamWriter sw = new System.IO.StreamWriter("C:\\log.txt", true))
{
sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "PowerFunc Called.");
}
// never reach this Function when my Notebook Lid Closes. :-(
// below need some check on pos data
pos = (POWERBROADCAST_SETTING)Marshal.PtrToStructure(eventData, typeof(POWERBROADCAST_SETTING));
if (pos.PowerSetting == GUID_LIDSWITCH_STATE_CHANGE)
{
if (pos.Data == 0)
{
SendMessage(HWND_BROADCAST,WM_SYSCOMMAND,SC_MONITORPOWER,2);
// when lid Closes,turn screen off
}
else if (pos.Data == 1)
{
SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, -1);
// when lid opens ,turn screen on
}
}
return 0;
}
protected override void OnStart(string[] args)
{
using (System.IO.StreamWriter sw = new System.IO.StreamWriter("C:\\log.txt", true))
{
sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Service Start Func.");
}
}
protected override void OnStop()
{
using (System.IO.StreamWriter sw = new System.IO.StreamWriter("C:\\log.txt", true))
{
sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Service Stop Func.");
}
}
}
}
The answer is simple:
1st : Windows Service can't send message,using a common application will working fine.
2nd : C# service maybe somehow different from C/C++ service,by using RegisterPowerSettingNotification() will never make it work.
After I find the reasons,I wrote a pure C and Winapi program.
It works perfect.
Thanks a lot for those people concerned about this question!
I'm referring back to a post i made a long time ago as i've only just got back to working with this WRG305API.dll.
With referral to: calling C++ functions containing callbacks in C#
Ive been trying to write an app that interfaces with this DLL and thanks to the guys that helped me back then. I've been able to get it working for a short period of time.
Ive been stopped by this annoying issue which is stated as:
A callback was made on a garbage collected delegate of type 'WinFFT!WinFFT.winradioIO.winRadioIOWrapper+CallbackFunc::Invoke'. This may cause application crashes ...
Here is the wrapper code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace WinFFT.winradioIO
{
class winRadioIOWrapper
{
private const string APIDLL_PATH = "WRG305API.dll";
public delegate void CallbackFunc(IntPtr p);
public CallbackFunc mycallback;
[StructLayout(LayoutKind.Sequential)]
public struct Features
{
public uint feature;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RadioInfo
{
public uint length;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
public string serialNumber;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
public string productName;
public UInt64 minFrequency;
public UInt64 maxFrequency;
public Features feature;
}
[DllImport(APIDLL_PATH)]
public static extern int OpenRadioDevice(int deviceNumber);
[DllImport(APIDLL_PATH)]
public static extern bool CloseRadioDevice(int radioHandle);
[DllImport(APIDLL_PATH)]
public static extern int GetRadioList(ref RadioInfo info, int bufferSize, ref int infoSize);
[DllImport(APIDLL_PATH)]
public static extern bool IsDeviceConnected(int radioHandle);
[DllImport(APIDLL_PATH)]
public static extern bool GetInfo(int radioHandle, ref RadioInfo info);
[DllImport(APIDLL_PATH)]
public static extern int GetFrequency(int radioHandle);
[DllImport(APIDLL_PATH)]
public static extern bool SetFrequency(int radioHandle, int frequency);
[DllImport(APIDLL_PATH)]
private static extern bool CodecStart(int hRadio, CallbackFunc func, IntPtr CallbackTarget);
[DllImport(APIDLL_PATH)]
private static extern uint CodecRead(int hRadio, byte[] Buf, uint Size);
[DllImport(APIDLL_PATH)]
private static extern bool CodecStop(int hRadio);
public static bool startIFStream(int radioHandle)
{
bool bStarted = CodecStart(radioHandle, MyCallbackFunc, IntPtr.Zero);
return bStarted;
}
// Note: this method will be called from a different thread!
private static void MyCallbackFunc(IntPtr pData)
{
// Sophisticated work goes here...
}
public static void readIFStreamBlock(int radioHandle, byte[] streamDumpLocation, uint blockSize)
{
CodecRead(radioHandle, streamDumpLocation, blockSize);
}
public static bool stopIFStream(int radioHandle)
{
bool bStoped = CodecStop(radioHandle);
return bStoped;
}
}
}
Here is my Application interfacing class layer which provides the friendly methods to use the dll interface:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace WinFFT.winradioIO
{
class radioInterface
{
enum DeviceStatus { Disconnected, Connected, Unknown };
private static DeviceStatus deviceStatus = DeviceStatus.Unknown;
public static string radioName, serialNumber;
public static int minimumFreq, maximumFreq;
static int radioHandle;
static radioInterface()
{
InitializeDeviceConnection();
}
public static void duffMethod(IntPtr ptr)
{
}
private static void InitializeDeviceConnection()
{
winRadioIOWrapper.CloseRadioDevice(radioHandle);
deviceStatus = DeviceStatus.Disconnected;
// Get Radio Info
winRadioIOWrapper.RadioInfo radioInfo = new winRadioIOWrapper.RadioInfo();
int aStructSize = Marshal.SizeOf(radioInfo), anInfoSize = 0;
radioInfo.length = (uint)aStructSize;
if (winRadioIOWrapper.GetRadioList(ref radioInfo, aStructSize, ref anInfoSize) == 1)
{
radioName = radioInfo.productName;
serialNumber = radioInfo.serialNumber;
minimumFreq = (int)radioInfo.minFrequency;
maximumFreq = (int)radioInfo.maxFrequency;
}
// Open device
radioHandle = winRadioIOWrapper.OpenRadioDevice(0);
CheckDeviceConnection();
}
private static void CheckDeviceConnection()
{
bool anIsDeviceConnected = winRadioIOWrapper.IsDeviceConnected(radioHandle);
if (deviceStatus == DeviceStatus.Unknown ||
deviceStatus == DeviceStatus.Disconnected && anIsDeviceConnected ||
deviceStatus == DeviceStatus.Connected && !anIsDeviceConnected)
{
if (anIsDeviceConnected)
{
deviceStatus = DeviceStatus.Connected;
winRadioIOWrapper.startIFStream(radioHandle);
}
else
{
winRadioIOWrapper.CloseRadioDevice(radioHandle);
deviceStatus = DeviceStatus.Disconnected;
}
}
}
public static void ReadIFStream(ref byte[] bufferLocation)
{
winRadioIOWrapper.readIFStreamBlock(radioHandle, bufferLocation, (uint)bufferLocation.Length);
}
public static void SetFreq(int valueInHz)
{
winRadioIOWrapper.SetFrequency(radioHandle, valueInHz);
}
public static void ShutDownRadio()
{
winRadioIOWrapper.CloseRadioDevice(radioHandle);
}
}
}
I Understand why AVIDeveloper chose the path, its is brilliant for continuously streaming out the data from the WinRadio Reciever (which is what the DLL is for), but the function CodecRead in the DLL allows one to copy a specified number of bytes from the radio's buffer. This is the path I went as I want to control how regularly I take the data and hence why I left the delegate function alone. But with this as it is at the moment, I am loosing the delegate in the wrapper to the GC and I am really stumped on how to prevent this.
Thanks in Advance guys.
Store the delegate as a field.
private static CallbackFunc _callback = new CallbackFunc(MyCallbackFunc);
public static bool startIFStream(int radioHandle)
{
bool bStarted = CodecStart(radioHandle, _callback, IntPtr.Zero);
return bStarted;
}
My winform application is launched by another application (the parent), I need determine the pid of the application which launch my application using C#.
WMI is the easier way to do this in C#. The Win32_Process class has the ParentProcessId property. Here's an example:
using System;
using System.Management; // <=== Add Reference required!!
using System.Diagnostics;
class Program {
public static void Main() {
var myId = Process.GetCurrentProcess().Id;
var query = string.Format("SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {0}", myId);
var search = new ManagementObjectSearcher("root\\CIMV2", query);
var results = search.Get().GetEnumerator();
results.MoveNext();
var queryObj = results.Current;
var parentId = (uint)queryObj["ParentProcessId"];
var parent = Process.GetProcessById((int)parentId);
Console.WriteLine("I was started by {0}", parent.ProcessName);
Console.ReadLine();
}
}
Output when run from Visual Studio:
I was started by devenv
Using Brian R. Bondy's answer as a guide, as well as what is on PInvoke.net, and some Reflector output, I have produced this, for use in LinqPad MyExtensions:
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
static class ProcessExtensions
{
public static int ParentProcessId(this Process process)
{
return ParentProcessId(process.Id);
}
public static int ParentProcessId(int Id)
{
PROCESSENTRY32 pe32 = new PROCESSENTRY32{};
pe32.dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32));
using(var hSnapshot = CreateToolhelp32Snapshot(SnapshotFlags.Process, (uint)Id))
{
if(hSnapshot.IsInvalid)
throw new Win32Exception();
if(!Process32First(hSnapshot, ref pe32))
{
int errno = Marshal.GetLastWin32Error();
if(errno == ERROR_NO_MORE_FILES)
return -1;
throw new Win32Exception(errno);
}
do {
if(pe32.th32ProcessID == (uint)Id)
return (int)pe32.th32ParentProcessID;
} while(Process32Next(hSnapshot, ref pe32));
}
return -1;
}
private const int ERROR_NO_MORE_FILES = 0x12;
[DllImport("kernel32.dll", SetLastError=true)]
private static extern SafeSnapshotHandle CreateToolhelp32Snapshot(SnapshotFlags flags, uint id);
[DllImport("kernel32.dll", SetLastError=true)]
private static extern bool Process32First(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe);
[DllImport("kernel32.dll", SetLastError=true)]
private static extern bool Process32Next(SafeSnapshotHandle hSnapshot, ref PROCESSENTRY32 lppe);
[Flags]
private enum SnapshotFlags : uint
{
HeapList = 0x00000001,
Process = 0x00000002,
Thread = 0x00000004,
Module = 0x00000008,
Module32 = 0x00000010,
All = (HeapList | Process | Thread | Module),
Inherit = 0x80000000,
NoHeaps = 0x40000000
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESSENTRY32
{
public uint dwSize;
public uint cntUsage;
public uint th32ProcessID;
public IntPtr th32DefaultHeapID;
public uint th32ModuleID;
public uint cntThreads;
public uint th32ParentProcessID;
public int pcPriClassBase;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=260)] public string szExeFile;
};
[SuppressUnmanagedCodeSecurity, HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort=true)]
internal sealed class SafeSnapshotHandle : SafeHandleMinusOneIsInvalid
{
internal SafeSnapshotHandle() : base(true)
{
}
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode=true)]
internal SafeSnapshotHandle(IntPtr handle) : base(true)
{
base.SetHandle(handle);
}
protected override bool ReleaseHandle()
{
return CloseHandle(base.handle);
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true, ExactSpelling=true)]
private static extern bool CloseHandle(IntPtr handle);
}
}
If you have control over the parent application, you could modify the parent to pass its PID to the child when it launches the process.
Check the th32ParentProcessID member of a CreateToolhelp32Snapshot enumeration.
For an example of how to do this see my post here.
Since you are using C# though you'll need to use DllImports. In the linked post there are MSDN pages for each for the functions you need. At the bottom of each MSDN page they have the code for DllImport for C#.
There is also an easier way using managed code only but it doesn't work if you have the more than one process name started by different applications.