Reliably determine PID of a process that updated clipboard [duplicate] - go

I am creating a clipboard manager in C# and from time to time I experience that the clipboard is being set empty by some application.
This happens in e.g. Excel when deselecting what is just copied, so I need to figure out if the clipboard is empty but how can I get the application name that updates the clipboard?
I hope that I somehow can get the HWnd handle of the application that updates the clipboard so I can then lookup the process behind it with this code:
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
...
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_CLIPBOARDUPDATE:
// How to get the "handle" HWnd?
IntPtr handle = ??? <============= HOW TO GET THIS ONE ???
// Get the process ID from the HWnd
uint processId = 0;
GetWindowThreadProcessId(handle, out processId);
// Get the process name from the process ID
string processName = Process.GetProcessById((int)processId).ProcessName;
Console.WriteLine("Clipboard UPDATE event from [" + processName + "]");
break;
}
default:
base.WndProc(ref m);
break;
}
}
I would have hoped that I could use the HWnd from the Message object, but this seems to be my own application - probably to notify the application with this process ID:
If I can get it any other way then this would of course be fully okay also but I would appreciate any insights on this :-)
Solution
Based on #Jimi's answer then this is very simple. I can just add the below 3 lines to my original code:
// Import the "GetClipboardOwner" function from the User32 library
[DllImport("user32.dll")]
public static extern IntPtr GetClipboardOwner();
...
// Replace the original line with "HOW TO GET THIS ONE" with this line below - this will give the HWnd handle for the application that has changed the clipboard:
IntPtr handle = GetClipboardOwner();

You can call GetClipboardOwner() to get the handle of the Window that last set or cleared the Clipboard (the operation that triggered the notification).
[...] In general, the clipboard owner is the window that last placed data in the Clipboard.
The EmptyClipboard function assigns Clipboard ownership.
There are special cases when a Process passes a null handle to OpenClipboard(): read the Remarks section of this function and the EmptyClipboard function.
Before calling EmptyClipboard, an application must open the Clipboard
by using the OpenClipboard function. If the application specifies a
NULL window handle when opening the clipboard, EmptyClipboard succeeds
but sets the clipboard owner to NULL. Note that this causes
SetClipboardData to fail.
▶ Here I'm using a NativeWindow derived class to setup a Clipboard listener. The Window that process the Clipboard update messages is created initializing a CreateParams object and passing this parameter to the NativeWindow.CreateHandle(CreateParams) method, to create an invisible Window.
Then override WndProc of the initialized NativeWindow, to receive WM_CLIPBOARDUPDATE notifications.
The AddClipboardFormatListener function is used to place the Window in the system Clipboard listeners chain.
▶ The ClipboardUpdateMonitor class generates an event when a Clipboard notification is received. The custom ClipboardChangedEventArgs object passed in the event contains the Handle of the Clipboard Owner, as returned by GetClipboardOwner(), the ThreadId and ProcessId returned by GetWindowThreadProcessId() and the Process name, identified by Process.GetProcessById().
You can setup a ClipboardUpdateMonitor object like this:
This class can also be initialized in Program.cs
private ClipboardUpdateMonitor clipboardMonitor = null;
// [...]
clipboardMonitor = new ClipboardUpdateMonitor();
clipboardMonitor.ClipboardChangedNotify += this.ClipboardChanged;
// [...]
private void ClipboardChanged(object sender, ClipboardChangedEventArgs e)
{
Console.WriteLine(e.ProcessId);
Console.WriteLine(e.ProcessName);
Console.WriteLine(e.ThreadId);
}
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Windows.Forms;
public sealed class ClipboardUpdateMonitor : IDisposable
{
private bool isDisposed = false;
private static ClipboardWindow window = null;
public event EventHandler<ClipboardChangedEventArgs> ClipboardChangedNotify;
public ClipboardUpdateMonitor()
{
window = new ClipboardWindow();
if (!NativeMethods.AddClipboardFormatListener(window.Handle)) {
throw new TypeInitializationException(nameof(ClipboardWindow),
new Exception("ClipboardFormatListener could not be initialized"));
}
window.ClipboardChanged += ClipboardChangedEvent;
}
private void ClipboardChangedEvent(object sender, ClipboardChangedEventArgs e)
=> ClipboardChangedNotify?.Invoke(this, e);
public void Dispose()
{
if (!isDisposed) {
// Cannot allow to throw exceptions here: add more checks to verify that
// the NativeWindow still exists and its handle is a valid handle
NativeMethods.RemoveClipboardFormatListener(window.Handle);
window?.DestroyHandle();
isDisposed = true;
}
}
~ClipboardUpdateMonitor() => Dispose();
private class ClipboardWindow : NativeWindow
{
public event EventHandler<ClipboardChangedEventArgs> ClipboardChanged;
public ClipboardWindow() {
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
var cp = new CreateParams();
cp.Caption = "ClipboardWindow";
cp.Height = 100;
cp.Width = 100;
cp.Parent = IntPtr.Zero;
cp.Style = NativeMethods.WS_CLIPCHILDREN;
cp.ExStyle = NativeMethods.WS_EX_CONTROLPARENT | NativeMethods.WS_EX_TOOLWINDOW;
this.CreateHandle(cp);
}
protected override void WndProc(ref Message m)
{
switch (m.Msg) {
case NativeMethods.WM_CLIPBOARDUPDATE:
IntPtr owner = NativeMethods.GetClipboardOwner();
var threadId = NativeMethods.GetWindowThreadProcessId(owner, out uint processId);
string processName = string.Empty;
if (processId != 0) {
using (var proc = Process.GetProcessById((int)processId)) {
processName = proc?.ProcessName;
}
}
ClipboardChanged?.Invoke(null, new ClipboardChangedEventArgs(processId, processName, threadId));
m.Result = IntPtr.Zero;
break;
default:
base.WndProc(ref m);
break;
}
}
}
}
Custom EventArgs object used to carry the information collected about the Clipboard Owner:
public class ClipboardChangedEventArgs : EventArgs
{
public ClipboardChangedEventArgs(uint processId, string processName, uint threadId)
{
this.ProcessId = processId;
this.ProcessName = processName;
this.ThreadId = threadId;
}
public uint ProcessId { get; }
public string ProcessName { get; }
public uint ThreadId { get; }
}
NativeMethods class:
internal static class NativeMethods
{
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AddClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll")]
internal static extern IntPtr GetClipboardOwner();
[DllImport("user32.dll", SetLastError = true)]
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
internal const int WM_CLIPBOARDUPDATE = 0x031D;
internal const int WS_CLIPCHILDREN = 0x02000000;
internal const int WS_EX_TOOLWINDOW = 0x00000080;
internal const int WS_EX_CONTROLPARENT = 0x00010000;
}

Related

using CFileDialog::AddCheckButton fails

OK, I am trying to use CFileDialog::AddCheckButton. The function call succeeds and I'm able to see the new check box. I'm unable to see any events and while I can override OnInitDialog, overriding OnOK is ignored. I'm not sure what I'm doing wrong:
//header
class CTPSaveDialog : public CFileDialog
{
DECLARE_DYNAMIC(CTPSaveDialog)
static const CString CTPSaveDialog::m_cstrFilter;
public:
BOOL m_bForce;
CTPSaveDialog(
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
CWnd* pParentWnd = NULL,
DWORD dwSize = 0);
~CTPSaveDialog();
virtual BOOL OnInitDialog();
DECLARE_MESSAGE_MAP()
afx_msg void OnBnClickedCheckForce();
virtual void OnOK();
};
// implementation
const CString CTPSaveDialog::m_cstrFilter = "JPEG images (*.jpg)|*.jpg|TIFF Format (*.tif)|*.tif|Windows Bitmap (*.bmp)|*.bmp|Portable Network Graphics (*.png)|*.png|GIF (*.gif)|*.gif||";
IMPLEMENT_DYNAMIC(CTPSaveDialog, CFileDialog)
CTPSaveDialog::CTPSaveDialog(LPCTSTR lpszDefExt, LPCTSTR lpszFileName, DWORD dwFlags, CWnd * pParentWnd, DWORD dwSize) :
CFileDialog(FALSE, lpszDefExt, lpszFileName, dwFlags, m_cstrFilter, pParentWnd, dwSize, TRUE)
{
AddCheckButton(IDC_CHK_FORCE, "Force", FALSE);
m_bForce = FALSE;
m_ofn.lpstrTitle = "Write Simulation To File";
}
CTPSaveDialog::~CTPSaveDialog()
{
}
BOOL CTPSaveDialog::OnInitDialog()
{
CFileDialog::OnInitDialog();
if (GetDlgItem(IDC_CHK_FORCE))
SendDlgItemMessage(IDC_CHK_FORCE, BM_SETCHECK, m_bForce ? BST_CHECKED : BST_UNCHECKED);
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
BEGIN_MESSAGE_MAP(CTPSaveDialog, CFileDialog)
ON_BN_CLICKED(IDC_CHK_FORCE, &CTPSaveDialog::OnBnClickedCheckForce)
END_MESSAGE_MAP()
void CTPSaveDialog::CTPSaveDialog()
{
m_bForce = !m_bForce;
}
void CTPSaveDialog::OnOK()
{
// TODO: Add your specialized code here and/or call the base class
CFileDialog::OnOK();
}
In CFileDialog with Vista style, windows messages are not handled in message map. Instead CFileDialog uses specific virtual functions. You only need to declare and define these functions.
Use OnCheckButtonToggled to detect if check box is clicked.
Use OnFileNameOK to detect when file is selected and Open/Save button is clicked.
Use SetCheckButtonState to set/unset the check button (not SendDlgItemMessage)
See CFileDialog for all available methods.
As stated in documentation, OnInitDialog is not supported either:
Some CFileDialog methods are not supported under Windows Vista or
later. Check the individual method topic for information about whether
the method is supported. In addition, the following inherited
functions are not supported under Windows Vista or later:
CDialog::OnInitDialog
...
Just do the initialization in the constructor or before calling DoModal(), and override these functions:
class CTPSaveDialog : public CFileDialog
{
...
virtual void OnCheckButtonToggled(DWORD dwIDCtl, BOOL bChecked);
virtual BOOL OnFileNameOK();
};
void CTPSaveDialog::OnCheckButtonToggled(DWORD dwIDCtl, BOOL bChecked)
{
if (dwIDCtl == IDC_CHK_FORCE)
TRACE("Is checked? %d\n", bChecked);
}
BOOL CTPSaveDialog::OnFileNameOK()
{
TRACE("Clicked Open/Save button\n");
//return FALSE to close the dialog
return FALSE;
}

PowerWriteACValueIndex changes are applied but not used

We use the methods PowerWriteACValueIndex and PowerWriteDCValueIndex to set the behavior of the sleep and shutdown button to Do nothing. When these methods are used to change the value this is reflected in the Control Panel, both button behaviors are changed to Do nothing. However the sleep and power button still function as before. After manually changing the behavior in the control panel to a different value and back the changes are applied. Am I using these methods incorrectly?
public static void DisableButtons()
{
Guid schemeId = GetActivePowerScheme();
DisablePowerAndSleepButtons(schemeId);
}
private static void DisablePowerAndSleepButtons(Guid schemeId)
{
// Lid close action
PowerWriteValueIndex(schemeId, ref PowrProf.SUBGROUP_BUTTONS,
ref PowrProf.OPTION_LIDACTION, PowrProf.ACTION_DO_NOTHING);
// Power button action
PowerWriteValueIndex(schemeId, ref PowrProf.SUBGROUP_BUTTONS,
ref PowrProf.OPTION_PBUTTONACTION, PowrProf.ACTION_DO_NOTHING);
// Sleep button action
PowerWriteValueIndex(schemeId, ref PowrProf.SUBGROUP_BUTTONS,
ref PowrProf.OPTION_SBUTTONACTION, PowrProf.ACTION_DO_NOTHING);
}
// Helper method that changes both the AC power setting and the DC (battery) power
// setting.
private static void PowerWriteValueIndex(Guid schemeGuid,
ref Guid subGroupOfPowerSettingsGuid, ref Guid powerSettingGuid, uint valueIndex)
{
// When on AC power
var hr = NativeMethods.PowerWriteACValueIndex(IntPtr.Zero, ref schemeGuid,
ref subGroupOfPowerSettingsGuid, ref powerSettingGuid, valueIndex);
if (hr != 0)
{
Console.WriteLine("Failed to write AC value");
}
// When on DC power (battery)
hr = NativeMethods.PowerWriteDCValueIndex(IntPtr.Zero, ref schemeGuid,
ref subGroupOfPowerSettingsGuid, ref powerSettingGuid, valueIndex);
if (hr != 0)
{
Console.WriteLine("Failed to write DC value");
}
}
private static Guid GetActivePowerScheme()
{
IntPtr ptrActiveGuid = IntPtr.Zero;
try
{
var hr = NativeMethods.PowerGetActiveScheme(IntPtr.Zero, ref ptrActiveGuid);
if (hr == 0)
{
var activeScheme = (Guid)Marshal.PtrToStructure(ptrActiveGuid, typeof(Guid));
return activeScheme;
}
return Guid.Empty;
}
finally
{
Marshal.FreeHGlobal(ptrActiveGuid);
}
}
private static class NativeMethods
{
[DllImport("powrprof.dll")]
public static extern uint PowerGetActiveScheme(IntPtr UserRootPowerKey, ref IntPtr ActivePolicyGuid);
[DllImport("powrprof.dll")]
public static extern uint PowerWriteACValueIndex(IntPtr RootPowerKey, ref Guid SchemeGuid, ref Guid SubGroupOfPowerSettingsGuid, ref Guid PowerSettingGuid, uint AcValueIndex);
[DllImport("powrprof.dll")]
public static extern uint PowerWriteDCValueIndex(IntPtr RootPowerKey, ref Guid SchemeGuid, ref Guid SubGroupOfPowerSettingsGuid, ref Guid PowerSettingGuid, uint DcValueIndex);
}
/// <summary>
/// Collections of guids to identify settings in PowrProf.dll
/// Source: http://adirondackpc.com/publicfiles/nos3s4-full.txt
/// </summary>
private static class PowrProf
{
public static Guid SUBGROUP_NONE = new Guid("fea3413e-7e05-4911-9a71-700331f1c294");
public static Guid SUBGROUP_SLEEP = new Guid("238c9fa8-0aad-41ed-83f4-97be242c8f20");
public static Guid SUBGROUP_VIDEO = new Guid("7516b95f-f776-4464-8c53-06167f40cc99");
public static Guid SUBGROUP_BUTTONS = new Guid("4f971e89-eebd-4455-a8de-9e59040e7347");
// Top level options
public static Guid OPTION_CONSOLELOCK = new Guid("0e796bdb-100d-47d6-a2d5-f7d2daa51f51");
// Sleep options
public static Guid OPTION_RTCWAKE = new Guid("bd3b718a-0680-4d9d-8ab2-e1d2b4ac806d");
// Turn the computer to sleep after x seconds, minmum value: 0, maximum value: 4294967295
public static Guid OPTION_STANDBYIDLE = new Guid("29f6c1db-86da-48c5-9fdb-f2b67b1f44da");
// Hybernate the computer after x seconds, minmum value: 0, maximum value: 4294967295
public static Guid OPTION_HYBERNATEIDLE = new Guid("9d7815a6-7ee4-497e-8888-515a05f02364");
// Display options
// Display brightness, minimum value: 0, maximum value: 100
public static Guid OPTION_BRIGHTNESS = new Guid("aded5e82-b909-4619-9949-f5d71dac0bcb");
// Dim display after x seconds, minmum value: 0, maximum value: 4294967295
public static Guid OPTION_VIDEODIM = new Guid("3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e");
// Turn off display after x seconds, minmum value: 0, maximum value: 4294967295
public static Guid OPTION_VIDEOIDLE = new Guid("3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e");
// Button options
// Lid close action
public static Guid OPTION_LIDACTION = new Guid("5ca83367-6e45-459f-a27b-476b1d01c936");
// Power button action
public static Guid OPTION_PBUTTONACTION = new Guid("7648efa3-dd9c-4e3e-b566-50f929386280");
// Sleep button action
public static Guid OPTION_SBUTTONACTION = new Guid("96996bc0-ad50-47ec-923b-6f41874dd9eb");
// Button actions
public static uint ACTION_DO_NOTHING = 0;
public static uint ACTION_SLEEP = 1;
public static uint ACTION_HIBERNATE = 2;
public static uint ACTION_SHUT_DOWN = 3;
// Universal constants
public static uint FALSE = 0;
public static uint TRUE = 1;
public static uint NEVER = 0;
}
You need to call PowerSetActiveScheme after calling either PowerWriteDCValueIndex or PowerWriteACValueIndex.
It's on the documentation under the remarks section:
Changes to the settings for the active power scheme do not take effect until you call the PowerSetActiveScheme function.

I run an EXE program from a Windows Service but I can't see form C#?

I run another program from Windows Service
private Process RunApp(string _appLocation)
{
//Create Process
Process _ret = new Process();
_ret.StartInfo.FileName = _appLocation;
//Run as Administrator.
_ret.StartInfo.Verb = "runas";
//Window = normal
_ret.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
_ret.StartInfo.CreateNoWindow = false;
_ret.Start();
return _ret;
}
I see it in Task Manager. it is working but I can't see window.
(If I can not see this then I can change Parent and see it)I try this:
Create new MidiChildWindow project and:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private void GetListeners()
{
Process[] Memory = Process.GetProcesses();
foreach (Process _prc in Memory)
{
if (_prc.ProcessName == "MyRunnedApplication")
{
SetParent(_prc.MainWindowHandle, this.Handle);
ShowWindow(_prc.MainWindowHandle, 1);
}
}
}
but its not work.
Runned my application is child the Windows service and I can not change it.
How I see it?
(Sory my bad english)

Figuring which printer name corresponds to which device ID

My goal is to open a printer connected via USB using the CreateFile (and then issue some WriteFiles and ReadFiles).
If the printer was an LPT one, I would simply do CreateFile("LPT1:", ...). But for USB printers, there is a special device path that must be passed to CreateFile in order to open that printer.
This device path, as I was able to find, is retrieved via SetupDiGetClassDevs -> SetupDiEnumDeviceInterfaces -> SetupDiGetDeviceInterfaceDetail -> DevicePath member and looks like this:
\\?\usb#vid_0a5f&pid_0027#46a072900549#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}
All that is fine, but what I have as the input is the human-readable printer name, as seen in Devices and Printers. The SetupDi* functions don't seem to use that, they only operate on device instance IDs. So the question is now how to get device instance ID from the printer name one would pass to OpenPrinter.
It's not difficult to observe that the GUID part of the above is the GUID_DEVINTERFACE_USBPRINT, and \\?\usb is fixed, so the only bit I'm really interested in is vid_0a5f&pid_0027#46a072900549#. This path I can easily look up manually in the printer properties dialog:
Go to Devices and Printers
Right-click the printer
Properties
Switch to Hardware tab
Select the printing device, such as ZDesigner LP2844-Z
Properties
Switch to Details tab
Select 'Parent' from the dropdown.
But I have no idea how to do that programmatically provided the only thing given is the printer name as seen in the Device and Printers panel.
P.S. 1: I'm not interested in opening the printer with OpenPrinter and then using WritePrinter / ReadPrinter. That has been done, works fine, but now the goal is different.
P.S. 2: I'll be OK with a simpler way to convert the readable printer name to something that can be passed to CreateFile.
P.S. 3: This question, to which I have posted an answer, is very related to what I ultimately want to do.
P.S. 4: The other way round is also fine: If it is possible to obtain the readable name from the SP_DEVINFO_DATA structure, that will also be the answer, although a less convenient one.
Below is what I finally have been able to come up with.
Please confirm that SYSTEM\CurrentControlSet\Control\Print\Printers\{0}\PNPData is a supported path, and not just happens to be there in the current implementation, subject to future changes.
There's a little problem with structure alignment, for which I've posted a separate question.
public static class UsbPrinterResolver
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SP_DEVINFO_DATA
{
public uint cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SP_DEVICE_INTERFACE_DATA
{
public uint cbSize;
public Guid InterfaceClassGuid;
public uint Flags;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
private struct SP_DEVICE_INTERFACE_DETAIL_DATA // Only used for Marshal.SizeOf. NOT!
{
public uint cbSize;
public char DevicePath;
}
[DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false, ExactSpelling = true)]
private static extern uint CM_Get_Parent(out uint pdnDevInst, uint dnDevInst, uint ulFlags);
[DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern uint CM_Get_Device_ID(uint dnDevInst, string Buffer, uint BufferLen, uint ulFlags);
[DllImport("cfgmgr32.dll", CharSet = CharSet.Auto, SetLastError = false, ExactSpelling = true)]
private static extern uint CM_Get_Device_ID_Size(out uint pulLen, uint dnDevInst, uint ulFlags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs([In(), MarshalAs(UnmanagedType.LPStruct)] System.Guid ClassGuid, string Enumerator, IntPtr hwndParent, uint Flags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, [In()] ref SP_DEVINFO_DATA DeviceInfoData, [In(), MarshalAs(UnmanagedType.LPStruct)] System.Guid InterfaceClassGuid, uint MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, [In()] ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, uint DeviceInterfaceDetailDataSize, out uint RequiredSize, IntPtr DeviceInfoData);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
private static extern int SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
private const uint DIGCF_PRESENT = 0x00000002U;
private const uint DIGCF_DEVICEINTERFACE = 0x00000010U;
private const int ERROR_INSUFFICIENT_BUFFER = 122;
private const uint CR_SUCCESS = 0;
private const int FILE_SHARE_READ = 1;
private const int FILE_SHARE_WRITE = 2;
private const uint GENERIC_READ = 0x80000000;
private const uint GENERIC_WRITE = 0x40000000;
private const int OPEN_EXISTING = 3;
private static readonly Guid GUID_PRINTER_INSTALL_CLASS = new Guid(0x4d36e979, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18);
private static readonly Guid GUID_DEVINTERFACE_USBPRINT = new Guid(0x28d78fad, 0x5a12, 0x11D1, 0xae, 0x5b, 0x00, 0x00, 0xf8, 0x03, 0xa8, 0xc2);
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
private static string GetPrinterRegistryInstanceID(string PrinterName) {
if (string.IsNullOrEmpty(PrinterName)) throw new ArgumentNullException("PrinterName");
const string key_template = #"SYSTEM\CurrentControlSet\Control\Print\Printers\{0}\PNPData";
using (var hk = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
string.Format(key_template, PrinterName),
Microsoft.Win32.RegistryKeyPermissionCheck.Default,
System.Security.AccessControl.RegistryRights.QueryValues
)
)
{
if (hk == null) throw new ArgumentOutOfRangeException("PrinterName", "This printer does not have PnP data.");
return (string)hk.GetValue("DeviceInstanceId");
}
}
private static string GetPrinterParentDeviceId(string RegistryInstanceID) {
if (string.IsNullOrEmpty(RegistryInstanceID)) throw new ArgumentNullException("RegistryInstanceID");
IntPtr hdi = SetupDiGetClassDevs(GUID_PRINTER_INSTALL_CLASS, RegistryInstanceID, IntPtr.Zero, DIGCF_PRESENT);
if (hdi.Equals(INVALID_HANDLE_VALUE)) throw new System.ComponentModel.Win32Exception();
try
{
SP_DEVINFO_DATA printer_data = new SP_DEVINFO_DATA();
printer_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
if (SetupDiEnumDeviceInfo(hdi, 0, ref printer_data) == 0) throw new System.ComponentModel.Win32Exception(); // Only one device in the set
uint cmret = 0;
uint parent_devinst = 0;
cmret = CM_Get_Parent(out parent_devinst, printer_data.DevInst, 0);
if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));
uint parent_device_id_size = 0;
cmret = CM_Get_Device_ID_Size(out parent_device_id_size, parent_devinst, 0);
if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get size of the device ID of the parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));
parent_device_id_size++; // To include the null character
string parent_device_id = new string('\0', (int)parent_device_id_size);
cmret = CM_Get_Device_ID(parent_devinst, parent_device_id, parent_device_id_size, 0);
if (cmret != CR_SUCCESS) throw new Exception(string.Format("Failed to get device ID of the parent of the device '{0}'. Error code: 0x{1:X8}", RegistryInstanceID, cmret));
return parent_device_id;
}
finally
{
SetupDiDestroyDeviceInfoList(hdi);
}
}
private static string GetUSBInterfacePath(string SystemDeviceInstanceID) {
if (string.IsNullOrEmpty(SystemDeviceInstanceID)) throw new ArgumentNullException("SystemDeviceInstanceID");
IntPtr hdi = SetupDiGetClassDevs(GUID_DEVINTERFACE_USBPRINT, SystemDeviceInstanceID, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hdi.Equals(INVALID_HANDLE_VALUE)) throw new System.ComponentModel.Win32Exception();
try
{
SP_DEVINFO_DATA device_data = new SP_DEVINFO_DATA();
device_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
if (SetupDiEnumDeviceInfo(hdi, 0, ref device_data) == 0) throw new System.ComponentModel.Win32Exception(); // Only one device in the set
SP_DEVICE_INTERFACE_DATA interface_data = new SP_DEVICE_INTERFACE_DATA();
interface_data.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));
if (SetupDiEnumDeviceInterfaces(hdi, ref device_data, GUID_DEVINTERFACE_USBPRINT, 0, ref interface_data) == 0) throw new System.ComponentModel.Win32Exception(); // Only one interface in the set
// Get required buffer size
uint required_size = 0;
SetupDiGetDeviceInterfaceDetail(hdi, ref interface_data, IntPtr.Zero, 0, out required_size, IntPtr.Zero);
int last_error_code = Marshal.GetLastWin32Error();
if (last_error_code != ERROR_INSUFFICIENT_BUFFER) throw new System.ComponentModel.Win32Exception(last_error_code);
IntPtr interface_detail_data = Marshal.AllocCoTaskMem((int)required_size);
try
{
// FIXME, don't know how to calculate the size.
// See https://stackoverflow.com/questions/10728644/properly-declare-sp-device-interface-detail-data-for-pinvoke
switch (IntPtr.Size)
{
case 4:
Marshal.WriteInt32(interface_detail_data, 4 + Marshal.SystemDefaultCharSize);
break;
case 8:
Marshal.WriteInt32(interface_detail_data, 8);
break;
default:
throw new NotSupportedException("Architecture not supported.");
}
if (SetupDiGetDeviceInterfaceDetail(hdi, ref interface_data, interface_detail_data, required_size, out required_size, IntPtr.Zero) == 0) throw new System.ComponentModel.Win32Exception();
// TODO: When upgrading to .NET 4, replace that with IntPtr.Add
return Marshal.PtrToStringAuto(new IntPtr(interface_detail_data.ToInt64() + Marshal.OffsetOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA), "DevicePath").ToInt64()));
}
finally
{
Marshal.FreeCoTaskMem(interface_detail_data);
}
}
finally
{
SetupDiDestroyDeviceInfoList(hdi);
}
}
public static string GetUSBPath(string PrinterName) {
return GetUSBInterfacePath(GetPrinterParentDeviceId(GetPrinterRegistryInstanceID(PrinterName)));
}
public static Microsoft.Win32.SafeHandles.SafeFileHandle OpenUSBPrinter(string PrinterName) {
return new Microsoft.Win32.SafeHandles.SafeFileHandle(CreateFile(GetUSBPath(PrinterName), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero), true);
}
}
Usage:
using (var sh = UsbPrinterResolver.OpenUSBPrinter("Zebra Large"))
{
using (var f = new System.IO.FileStream(sh, System.IO.FileAccess.ReadWrite))
{
// Read from and write to the stream f
}
}
Try this (Python code):
import _winreg
HKLM = _winreg.HKEY_LOCAL_MACHINE
#------------------------------------------------------------------------------
def getDevicePath(printerName):
key = _winreg.OpenKey(HKLM,
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\%s" \
% printerName)
value =_winreg.QueryValueEx(key, "Port")[0]
assert value.startswith("USB"), \
"Port does not start with 'USB': %s" % value
printerPortNumber = int(value.replace(u"USB", u""))
key = _winreg.OpenKey(HKLM,
r"SYSTEM\CurrentControlSet\Control\DeviceClasses" \
r"\{28d78fad-5a12-11d1-ae5b-0000f803a8c2}")
idx = 0
devicePath = None
while True:
try:
subKeyName = _winreg.EnumKey(key, idx)
subKey = _winreg.OpenKey(key, subKeyName)
try:
subSubKey = _winreg.OpenKey(subKey, r"#\Device Parameters")
baseName = _winreg.QueryValueEx(subSubKey, "Base Name")[0]
portNumber = _winreg.QueryValueEx(subSubKey, "Port Number")[0]
if baseName == "USB" and portNumber == printerPortNumber:
devicePath = subKeyName.replace("##?#USB", r"\\?\usb")
break
except WindowsError:
continue
finally:
idx += 1
except WindowsError:
break
return devicePath
Try this ... let me know if this helps ...
static void Main(string[] args)
{
ManagementObjectSearcher s = new ManagementObjectSearcher(#"Select * From Win32_PnPEntity");
foreach (ManagementObject device in s.Get())
{
// Try Name, Caption and/or Description (they seem to be same most of the time).
string Name = (string)device.GetPropertyValue("Name");
// >>>>>>>>>>>>>>>>>>>> Query String ...
if (Name == "O2Micro Integrated MMC/SD controller")
{
/*
class Win32_PnPEntity : CIM_LogicalDevice
{
uint16 Availability;
string Caption;
string ClassGuid;
string CompatibleID[];
uint32 ConfigManagerErrorCode;
boolean ConfigManagerUserConfig;
string CreationClassName;
string Description;
string DeviceID;
boolean ErrorCleared;
string ErrorDescription;
string HardwareID[];
datetime InstallDate;
uint32 LastErrorCode;
string Manufacturer;
string Name;
string PNPDeviceID;
uint16 PowerManagementCapabilities[];
boolean PowerManagementSupported;
string Service;
string Status;
uint16 StatusInfo;
string SystemCreationClassName;
string SystemName;
};
*/
try
{
Console.WriteLine("Name : {0}", Name);
Console.WriteLine("DeviceID : {0}", device.GetPropertyValue("DeviceID"));
Console.WriteLine("PNPDeviceID : {0}", device.GetPropertyValue("PNPDeviceID"));
Console.WriteLine("ClassGuid : {0}", device.GetPropertyValue("ClassGuid"));
Console.WriteLine("HardwareID :\n{0}", JoinStrings(device.GetPropertyValue("HardwareID") as string[]));
Console.WriteLine("CompatibleID :\n{0}", JoinStrings(device.GetPropertyValue("CompatibleID") as string[]));
}
catch (Exception e)
{
Console.WriteLine("ERROR: {0}", e.Message);
}
}
}
}
static string JoinStrings(string[] sarray)
{
StringBuilder b = new StringBuilder();
if (sarray != null)
{
foreach (string s in sarray)
b.Append(" '" + s + "'\n");
}
return b.ToString();
}
Don't have a USB printer to test against, but this provides the information you are looking for (including for USB devices)...
Description : O2Micro Integrated MMC/SD controller
DeviceID : PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05\4&26B31A7F&0&00E5
PNPDeviceID : PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05\4&26B31A7F&0&00E5
ClassGuid : {4d36e97b-e325-11ce-bfc1-08002be10318}
HardwareID :
'PCI\VEN_1217&DEV_8221&SUBSYS_04931028&REV_05'
'PCI\VEN_1217&DEV_8221&SUBSYS_04931028'
'PCI\VEN_1217&DEV_8221&CC_080501'
'PCI\VEN_1217&DEV_8221&CC_0805'
CompatibleID : 'PCI\VEN_1217&DEV_8221&REV_05'
'PCI\VEN_1217&DEV_8221'
'PCI\VEN_1217&CC_080501'
'PCI\VEN_1217&CC_0805'
'PCI\VEN_1217'
'PCI\CC_080501'
'PCI\CC_0805'
Also, for a URI, change the '\'s to '#'s in the URI you are intending of building.
so ..
usb\vid_0a5f&pid_0027\46a072900549\{28d78fad-5a12-11d1-ae5b-0000f803a8c2}
becomes
usb#vid_0a5f&pid_0027#46a072900549#{28d78fad-5a12-11d1-ae5b-0000f803a8c2}
====
As GSerg pointed out that Win32_Printer Class helps with the above code, but doesn't provide the device id.
But if I use Win32_Printer class and print out the "PortName" property, that, for the printers I have installed gives be a port/filename that I can use with CreateFile() and open the device.
e.g.:
Name : Microsoft XPS Document Writer
Description :
DeviceID : Microsoft XPS Document Writer
PNPDeviceID :
PortName : XPSPort:
Name : Fax
Description :
DeviceID : Fax
PNPDeviceID :
PortName : SHRFAX:
Here, writing to "XPSPORT:" or "SHRFAX:" sends data to the printer. What does this do for your USB printer?
Use WinObj from Microsoft to get the specific device name. http://technet.microsoft.com/en-us/sysinternals/bb896657.aspx . This will quickly get you the proper device name to use with CreateFile to write directly to your USB printer or simply writing directly to a USB printer adapter with old school parallel port output for custom circuitry!
To open the port associated with a specific printer, you may need to use ntcreatefile. Use the EnumPrinters function to return a printer_info_2 structure containing the port name for each printer. This port name can then be opened with ntcreatefile (an NT internal version of CreateFile) which is explained here: http://msdn.microsoft.com/en-us/library/bb432380(v=vs.85).aspx
Why does this work? There are three namespace levels in windows NT file/device names and the port name retrieved from EnumPrinters can only be opened with ntcreatefile because it is only in the NT namespace. There may be an equivalent win32 namespace link for certain devices and roundabout ways to match them with a printer name but this is difficult as others have shown in prior answers.
Check out the Global?? folder in WinObj tool to show the symbolic links between win32 namespace and NT namespace on your machine. The old school COM1, COM2, LPT1, etc. device names are simply windows NT namespace symbolic links as well. Google "win32 nt namespace" for a more detailed explanation. (Sorry, but as a new user, I can only post 2 hyperlinks.)
I am not really a c++ guy, but I don't really think trying to generate the device id from the name is the way to go. However, you can
Use EnumPrinters and read the PRINTER_INFO_2 structure to get the driver name, and then read the driver details from registry like in this example.
Generate the name for yourself by finding out the printer details possibly from the PRINTER INFO structures and constructing it correctly. See http://msdn.microsoft.com/en-us/library/windows/hardware/ff553356(v=vs.85).aspx for details.
EDIT: You can actually get names + device instance ids of printers from registry:
HKLM\System\CurrentControlSet\Control\Print\Printers\

GetPhysicalMonitorsFromHMONITOR returned handle is always null

On the Media Foundation SDK there is the GetPhysicalMonitorsFromHMONITOR function
that I am trying to implement using C# but with no luck ...
In the returned PHYSICAL_MONITOR[], the function returns the string description of the monitor but for some mysterious reasons, the hPhysicalMonitor handle remains at 0.
I have generated the signatures with P/Invoke Interop Assistant with minor modifications.
Does the PHYSICAL_MONITOR structure or anything else needs further tuning ?
Thank you.
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using WindowsFormsApplication1;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public enum MC_DISPLAY_TECHNOLOGY_TYPE
{
MC_SHADOW_MASK_CATHODE_RAY_TUBE,
MC_APERTURE_GRILL_CATHODE_RAY_TUBE,
MC_THIN_FILM_TRANSISTOR,
MC_LIQUID_CRYSTAL_ON_SILICON,
MC_PLASMA,
MC_ORGANIC_LIGHT_EMITTING_DIODE,
MC_ELECTROLUMINESCENT,
MC_MICROELECTROMECHANICAL,
MC_FIELD_EMISSION_DEVICE,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct PHYSICAL_MONITOR
{
public IntPtr hPhysicalMonitor;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string szPhysicalMonitorDescription;
}
#region Imports
[DllImport("user32.dll", EntryPoint = "MonitorFromWindow")]
public static extern IntPtr MonitorFromWindow(
[In] IntPtr hwnd, uint dwFlags);
[DllImport("dxva2.dll", EntryPoint = "GetMonitorTechnologyType")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorTechnologyType(
IntPtr hMonitor, ref MC_DISPLAY_TECHNOLOGY_TYPE pdtyDisplayTechnologyType);
[DllImport("dxva2.dll", EntryPoint = "GetMonitorCapabilities")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorCapabilities(
IntPtr hMonitor, ref uint pdwMonitorCapabilities, ref uint pdwSupportedColorTemperatures);
[DllImport("dxva2.dll", EntryPoint = "DestroyPhysicalMonitors")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyPhysicalMonitors(
uint dwPhysicalMonitorArraySize, ref PHYSICAL_MONITOR[] pPhysicalMonitorArray);
[DllImport("dxva2.dll", EntryPoint = "GetNumberOfPhysicalMonitorsFromHMONITOR")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetNumberOfPhysicalMonitorsFromHMONITOR(
IntPtr hMonitor, ref uint pdwNumberOfPhysicalMonitors);
[DllImport("dxva2.dll", EntryPoint = "GetPhysicalMonitorsFromHMONITOR")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetPhysicalMonitorsFromHMONITOR(
IntPtr hMonitor, uint dwPhysicalMonitorArraySize, [Out] PHYSICAL_MONITOR[] pPhysicalMonitorArray);
#endregion
public Form1() { InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e)
{
// Get monitor handle.
uint dwFlags = 0u;
IntPtr ptr = MonitorFromWindow(Handle, dwFlags);
// Get number of physical monitors.
uint pdwNumberOfPhysicalMonitors = 0u;
bool b1 = GetNumberOfPhysicalMonitorsFromHMONITOR(ptr, ref pdwNumberOfPhysicalMonitors);
if (b1)
{
// Get physical monitors.
uint dwPhysicalMonitorArraySize = 0u;
dwPhysicalMonitorArraySize = pdwNumberOfPhysicalMonitors;
PHYSICAL_MONITOR[] pPhysicalMonitorArray = new PHYSICAL_MONITOR[dwPhysicalMonitorArraySize];
//NOTE : Handles remain null !
bool b2 = GetPhysicalMonitorsFromHMONITOR(ptr, dwPhysicalMonitorArraySize, pPhysicalMonitorArray);
if (pPhysicalMonitorArray[0].hPhysicalMonitor
== IntPtr.Zero)
{
throw new Exception("ERROR !");
}
// Monitor has capabilities to do that ?
if (b2)
{
uint pdwMonitorCapabilities = 0u;
uint pdwSupportedColorTemperatures = 0u;
bool b3 = GetMonitorCapabilities(
ptr, ref pdwMonitorCapabilities, ref pdwSupportedColorTemperatures);
// If yes, get technology type.
if (b3)
{
MC_DISPLAY_TECHNOLOGY_TYPE type = MC_DISPLAY_TECHNOLOGY_TYPE.MC_SHADOW_MASK_CATHODE_RAY_TUBE;
bool b4 = GetMonitorTechnologyType(ptr, ref type);
if (b4)
{
// Do work.
}
else
{
throw new Exception("Couldn't get monitor technology type.");
}
}
else
{
throw new Exception("Couldn't get monitor capabilities.");
}
}
else
{
throw new Exception("The monitor doesn't have the required capabilities.");
}
bool b5 = DestroyPhysicalMonitors(dwPhysicalMonitorArraySize, ref pPhysicalMonitorArray);
if (!b5)
{
throw new Exception("Couldn't destroy physical monitors.");
}
}
else
{
throw new Exception("Couldn't get number of physical monitors.");
}
}
}
}
Your statement:
The function returns the string description of the monitor but for some mysterious reasons, the hMonitor handle remains at 0.
is correct. If you look at the docs here, you'll see that hMonitor is clearly an [in] parameter and will not be changed.
Update following comment:
Sorry, didn't realize you meant the physical handle being returned in the structure. All the information I can find on that particular problem seems to indicate that your monitor probably isn't fully DDC/CI compatible (e.g., here).
All your structure definitions look fine to me, based on the docs on MSDN for that particular call. And indeed, it is populating the description for you.
What is the value for the number of physical monitors being returned from GetNumberOfPhysicalMonitorsFromHMONITOR (pdwNumberOfPhysicalMonitors)?
Also, what is the size of your PHYSICAL_MONITOR structure and are you running in 32 or 64 bits?
It is alright that the hPhysicalMonitor value is 0. However, in the question's code sample all calls after the GetPhysicalMonitorsFromHMONITOR should use the hPhysicalMonitor reference instead of the ptr reference. The updated Form_Load method should be the following:
private void Form1_Load(object sender, EventArgs e)
{
// Get monitor handle.
uint dwFlags = 0u;
IntPtr ptr = MonitorFromWindow(Handle, dwFlags);
// Get number of physical monitors.
uint pdwNumberOfPhysicalMonitors = 0u;
bool b1 = GetNumberOfPhysicalMonitorsFromHMONITOR(ptr, ref pdwNumberOfPhysicalMonitors);
if (b1)
{
// Get physical monitors.
uint dwPhysicalMonitorArraySize = 0u;
dwPhysicalMonitorArraySize = pdwNumberOfPhysicalMonitors;
PHYSICAL_MONITOR[] pPhysicalMonitorArray = new PHYSICAL_MONITOR[dwPhysicalMonitorArraySize];
//NOTE : Handles remain null !
bool b2 = GetPhysicalMonitorsFromHMONITOR(ptr, dwPhysicalMonitorArraySize, pPhysicalMonitorArray);
// Monitor has capabilities to do that ?
if (b2)
{
uint pdwMonitorCapabilities = 0u;
uint pdwSupportedColorTemperatures = 0u;
bool b3 = GetMonitorCapabilities(pPhysicalMonitorArray[0].hPhysicalMonitor, ref pdwMonitorCapabilities, ref pdwSupportedColorTemperatures);
// If yes, get technology type.
if (b3)
{
MC_DISPLAY_TECHNOLOGY_TYPE type = MC_DISPLAY_TECHNOLOGY_TYPE.MC_SHADOW_MASK_CATHODE_RAY_TUBE;
bool b4 = GetMonitorTechnologyType(pPhysicalMonitorArray[0].hPhysicalMonitor, ref type);
if (b4)
{
// Do work.
}
else
{
throw new Exception("Couldn't get monitor technology type.");
}
}
else
{
throw new Exception("Couldn't get monitor capabilities.");
}
}
else
{
throw new Exception("The monitor doesn't have the required capabilities.");
}
bool b5 = DestroyPhysicalMonitors(dwPhysicalMonitorArraySize, ref pPhysicalMonitorArray);
if (!b5)
{
throw new Exception("Couldn't destroy physical monitors.");
}
}
else
{
throw new Exception("Couldn't get number of physical monitors.");
}
}
The monitor supports this function because with software like softMCCS and WinI2C/DDC,
the properties are returned correctly.
The return pdwNumberOfPhysicalMonitors value is 1 which is correct.
As you can see, its size is pdwNumberOfPhysicalMonitors :
PHYSICAL_MONITOR[] pPhysicalMonitorArray = new PHYSICAL_MONITOR[dwPhysicalMonitorArraySize];
And I am running Vista 32.
It is somewhat strange because half of it works, that's now about 4 days I am over it but still no progress ...
Thank you.

Resources