I'm interested in how plausible it would be to capture Win32 debug traces system-wide in the way DebugView does. I'm not interested in kernel messages thankfully, so I don't need any help there. This needs to be in C#, but I'm happy with unmanaged/unsafe if necessary.
Is there any global hook I can get or am I setting off down a difficult road?
I'm really not sure where the best place is to start on this.
I finally got there. It took some serious googling, but I found an article which helped...
All kudos go to Chritian Birkl for his rather excellent Code Project DbMon.NET - A simple .NET OutputDebugString capturer.
The code is pretty heavy-going, but here it is:
using System;
using System.Threading;
using System.Runtime.InteropServices;
public delegate void OnOutputDebugStringHandler(int pid, string text);
public sealed class DebugMonitor
{
private DebugMonitor()
{
;
}
#region Win32 API Imports
[StructLayout(LayoutKind.Sequential)]
private struct SECURITY_DESCRIPTOR
{
public byte revision;
public byte size;
public short control;
public IntPtr owner;
public IntPtr group;
public IntPtr sacl;
public IntPtr dacl;
}
[StructLayout(LayoutKind.Sequential)]
private struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[Flags]
private enum PageProtection : uint
{
NoAccess = 0x01,
Readonly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
Guard = 0x100,
NoCache = 0x200,
WriteCombine = 0x400,
}
private const int WAIT_OBJECT_0 = 0;
private const uint INFINITE = 0xFFFFFFFF;
private const int ERROR_ALREADY_EXISTS = 183;
private const uint SECURITY_DESCRIPTOR_REVISION = 1;
private const uint SECTION_MAP_READ = 0x0004;
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, uint
dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow,
uint dwNumberOfBytesToMap);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool InitializeSecurityDescriptor(ref SECURITY_DESCRIPTOR sd, uint dwRevision);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool SetSecurityDescriptorDacl(ref SECURITY_DESCRIPTOR sd, bool daclPresent, IntPtr dacl, bool daclDefaulted);
[DllImport("kernel32.dll")]
private static extern IntPtr CreateEvent(ref SECURITY_ATTRIBUTES sa, bool bManualReset, bool bInitialState, string lpName);
[DllImport("kernel32.dll")]
private static extern bool PulseEvent(IntPtr hEvent);
[DllImport("kernel32.dll")]
private static extern bool SetEvent(IntPtr hEvent);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFileMapping(IntPtr hFile,
ref SECURITY_ATTRIBUTES lpFileMappingAttributes, PageProtection flProtect, uint dwMaximumSizeHigh,
uint dwMaximumSizeLow, string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hHandle);
[DllImport("kernel32", SetLastError = true, ExactSpelling = true)]
private static extern Int32 WaitForSingleObject(IntPtr handle, uint milliseconds);
#endregion
public static event OnOutputDebugStringHandler OnOutputDebugString;
private static IntPtr m_AckEvent = IntPtr.Zero;
private static IntPtr m_ReadyEvent = IntPtr.Zero;
private static IntPtr m_SharedFile = IntPtr.Zero;
private static IntPtr m_SharedMem = IntPtr.Zero;
private static Thread m_Capturer = null;
private static object m_SyncRoot = new object();
private static Mutex m_Mutex = null;
public static void Start()
{
lock (m_SyncRoot)
{
if (m_Capturer != null)
throw new ApplicationException("This DebugMonitor is already started.");
if (Environment.OSVersion.ToString().IndexOf("Microsoft") == -1)
throw new NotSupportedException("This DebugMonitor is only supported on Microsoft operating systems.");
bool createdNew = false;
m_Mutex = new Mutex(false, typeof(DebugMonitor).Namespace, out createdNew);
if (!createdNew)
throw new ApplicationException("There is already an instance of 'DbMon.NET' running.");
SECURITY_DESCRIPTOR sd = new SECURITY_DESCRIPTOR();
if (!InitializeSecurityDescriptor(ref sd, SECURITY_DESCRIPTOR_REVISION))
{
throw CreateApplicationException("Failed to initializes the security descriptor.");
}
if (!SetSecurityDescriptorDacl(ref sd, true, IntPtr.Zero, false))
{
throw CreateApplicationException("Failed to initializes the security descriptor");
}
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
m_AckEvent = CreateEvent(ref sa, false, false, "DBWIN_BUFFER_READY");
if (m_AckEvent == IntPtr.Zero)
{
throw CreateApplicationException("Failed to create event 'DBWIN_BUFFER_READY'");
}
m_ReadyEvent = CreateEvent(ref sa, false, false, "DBWIN_DATA_READY");
if (m_ReadyEvent == IntPtr.Zero)
{
throw CreateApplicationException("Failed to create event 'DBWIN_DATA_READY'");
}
m_SharedFile = CreateFileMapping(new IntPtr(-1), ref sa, PageProtection.ReadWrite, 0, 4096, "DBWIN_BUFFER");
if (m_SharedFile == IntPtr.Zero)
{
throw CreateApplicationException("Failed to create a file mapping to slot 'DBWIN_BUFFER'");
}
m_SharedMem = MapViewOfFile(m_SharedFile, SECTION_MAP_READ, 0, 0, 512);
if (m_SharedMem == IntPtr.Zero)
{
throw CreateApplicationException("Failed to create a mapping view for slot 'DBWIN_BUFFER'");
}
m_Capturer = new Thread(new ThreadStart(Capture));
m_Capturer.Start();
}
}
private static void Capture()
{
try
{
IntPtr pString = new IntPtr(
m_SharedMem.ToInt32() + Marshal.SizeOf(typeof(int))
);
while (true)
{
SetEvent(m_AckEvent);
int ret = WaitForSingleObject(m_ReadyEvent, INFINITE);
if (m_Capturer == null)
break;
if (ret == WAIT_OBJECT_0)
{
FireOnOutputDebugString(
Marshal.ReadInt32(m_SharedMem),
Marshal.PtrToStringAnsi(pString));
}
}
}
catch
{
throw;
}
finally
{
Dispose();
}
}
private static void FireOnOutputDebugString(int pid, string text)
{
if (OnOutputDebugString == null)
return;
#if !DEBUG
try
{
#endif
OnOutputDebugString(pid, text);
#if !DEBUG
}
catch (Exception ex)
{
Console.WriteLine("An 'OnOutputDebugString' handler failed to execute: " + ex.ToString());
}
#endif
}
private static void Dispose()
{
if (m_AckEvent != IntPtr.Zero)
{
if (!CloseHandle(m_AckEvent))
{
throw CreateApplicationException("Failed to close handle for 'AckEvent'");
}
m_AckEvent = IntPtr.Zero;
}
if (m_ReadyEvent != IntPtr.Zero)
{
if (!CloseHandle(m_ReadyEvent))
{
throw CreateApplicationException("Failed to close handle for 'ReadyEvent'");
}
m_ReadyEvent = IntPtr.Zero;
}
if (m_SharedFile != IntPtr.Zero)
{
if (!CloseHandle(m_SharedFile))
{
throw CreateApplicationException("Failed to close handle for 'SharedFile'");
}
m_SharedFile = IntPtr.Zero;
}
if (m_SharedMem != IntPtr.Zero)
{
if (!UnmapViewOfFile(m_SharedMem))
{
throw CreateApplicationException("Failed to unmap view for slot 'DBWIN_BUFFER'");
}
m_SharedMem = IntPtr.Zero;
}
if (m_Mutex != null)
{
m_Mutex.Close();
m_Mutex = null;
}
}
public static void Stop()
{
lock (m_SyncRoot)
{
if (m_Capturer == null)
throw new ObjectDisposedException("DebugMonitor", "This DebugMonitor is not running.");
m_Capturer = null;
PulseEvent(m_ReadyEvent);
while (m_AckEvent != IntPtr.Zero)
;
}
}
private static ApplicationException CreateApplicationException(string text)
{
if (text == null || text.Length < 1)
throw new ArgumentNullException("text", "'text' may not be empty or null.");
return new ApplicationException(string.Format("{0}. Last Win32 Error was {1}",
text, Marshal.GetLastWin32Error()));
}
}
I wrote a tool that does it. It's not 100% equivalent to DebugView, but it has some other nice features (like the ability to colorize traces) :-).
It's available here: TraceSpy
Since it's 100% open source, it contains C# code that demonstrates how to do it.
I would start with the TraceListener class, although I'm not sure if this can be used to capture Win32 debug tracing.
Related
I'm working on an application that is a sound editor for a video game.
This application has an option to test the sounds in the game, to do this I use Process.Start with some special parameters to open the game .exe.
The game uses OutputDebugStringA() to print debug messages. The idea is to have a multiline textbox inside this form to show the debug messages only of the game process.
I've been doing some research and I know about the existence of DebugView from sysinternals, but is not useful for this case, I've also found DbMon.NET in code project, but for some reason causes the game to run very slow (with DebugView doesn't happen).
Any proposal?
Try this, you can specify the process ID inside the do while loop before printing it in the textbox.
public partial class Form1 : Form
{
private readonly IntPtr _bufferReadyEvent;
private readonly IntPtr _dataReadyEvent;
private readonly IntPtr _mapping;
private readonly IntPtr _file;
private const int WaitTimeout = 500;
private const uint WAIT_OBJECT_0 = 0;
private readonly Thread _reader;
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetEvent(IntPtr hEvent);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr CreateFileMapping(IntPtr hFile, IntPtr lpFileMappingAttributes, FileMapProtection flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, FileMapAccess dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint dwNumberOfBytesToMap);
[Flags]
private enum FileMapAccess
{
FileMapRead = 0x0004,
}
[Flags]
private enum FileMapProtection
{
PageReadWrite = 0x04,
}
public Form1()
{
InitializeComponent();
_bufferReadyEvent = CreateEvent(IntPtr.Zero, false, false, "DBWIN_BUFFER_READY");
if (_bufferReadyEvent == IntPtr.Zero)
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
_dataReadyEvent = CreateEvent(IntPtr.Zero, false, false, "DBWIN_DATA_READY");
if (_dataReadyEvent == IntPtr.Zero)
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
_mapping = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, FileMapProtection.PageReadWrite, 0, 4096, "DBWIN_BUFFER");
if (_mapping == IntPtr.Zero)
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
_file = MapViewOfFile(_mapping, FileMapAccess.FileMapRead, 0, 0, 1024);
if (_file == IntPtr.Zero)
Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
_reader = new Thread(Read)
{
IsBackground = true
};
_reader.Start();
}
private void Read()
{
try
{
do
{
SetEvent(_bufferReadyEvent);
uint wait = WaitForSingleObject(_dataReadyEvent, WaitTimeout);
if (wait == WAIT_OBJECT_0) // we don't care about other return values
{
int pid = Marshal.ReadInt32(_file);
string text = Marshal.PtrToStringAnsi(new IntPtr(_file.ToInt32() + Marshal.SizeOf(typeof(int)))).TrimEnd(null);
if (string.IsNullOrEmpty(text))
{
continue;
}
Textbox_DebugConsole.Invoke((MethodInvoker)delegate
{
Textbox_DebugConsole.AppendText(string.Format("{0} - {1}", pid, text + Environment.NewLine));
});
}
}
while (true);
}
catch (Exception e)
{
MessageBox.Show(e.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
I am tasked with finding a way to make another application appear on top of other windows (Always On Top). I am able to get processes that have a Window Title using the RetrieveProcesses() function. Once the user selects which process they want to modify, my application will call either MakeProcessOnTop or MakeProcessNormal. Both functions modify the main application's window. Before I added modifying its children, this worked correctly.
I then discovered this wouldn't work on child windows (like an email in outlook) so I set off to find a way to handle child windows. The way the following code is written, it will end up messing up child windows. How do I get the handle pointer of child windows but not child controls?
public static class ProcessManagement
{
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommands nCmdShow);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
static readonly IntPtr HWND_TOP = new IntPtr(0);
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
public static IEnumerable<Process> RetrieveProcesses()
{
List<Process> returnList = new List<Process>();
Process[] processArray = Process.GetProcesses();
foreach (Process p in processArray)
{
if (!String.IsNullOrEmpty(p.MainWindowTitle))
{
returnList.Add(p);
}
}
return returnList;
}
public static IntPtr GetProcessWindowHandle(int processId)
{
Process p = Process.GetProcessById(processId: processId);
return p.MainWindowHandle;
}
public static List<IntPtr> GetProcessChildWindowHandles(IntPtr parent)
{
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
EnumWindowsProc childProc = new EnumWindowsProc(EnumWindow);
EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated)
listHandle.Free();
}
return result;
}
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
GCHandle gch = GCHandle.FromIntPtr(pointer);
List<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}
list.Add(handle);
// You can modify this to check to see if you want to cancel the operation, then return a null here
return true;
}
public static bool MakeProcessOnTop(IntPtr targetWindowHandle, bool targetChildren = true)
{
bool bReturn = true;
if (!ShowWindow(targetWindowHandle, ShowWindowCommands.Minimize))
{
bReturn = false;
}
if (!ShowWindow(targetWindowHandle, ShowWindowCommands.Restore))
{
bReturn = false;
}
if (!ShowWindow(targetWindowHandle, ShowWindowCommands.ShowNoActivate))
{
bReturn = false;
}
if (!SetWindowPos(targetWindowHandle, HWND_TOPMOST, 0, 0, 0, 0, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE))
{
bReturn = false;
}
if (targetChildren)
{
List<IntPtr> childProcesses = GetProcessChildWindowHandles(targetWindowHandle);
foreach(IntPtr iPtr in childProcesses)
{
MakeProcessOnTop(iPtr, false);
}
}
return bReturn;
}
public static bool MakeProcessNormal(IntPtr targetWindowHandle, bool targetChildren = true)
{
bool bReturn = true;
if (!ShowWindow(targetWindowHandle, ShowWindowCommands.Minimize))
{
bReturn = false;
}
if (!ShowWindow(targetWindowHandle, ShowWindowCommands.Restore))
{
bReturn = false;
}
if (!ShowWindow(targetWindowHandle, ShowWindowCommands.ShowNoActivate))
{
bReturn = false;
}
if (!SetWindowPos(targetWindowHandle, HWND_NOTOPMOST, 0, 0, 0, 0, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE))
{
bReturn = false;
}
if (targetChildren)
{
List<IntPtr> childProcesses = GetProcessChildWindowHandles(targetWindowHandle);
foreach (IntPtr iPtr in childProcesses)
{
MakeProcessNormal(iPtr, false);
}
}
return bReturn;
}
}
Always On Top only makes sense for top level windows or possibly MDI children.
You could raise a child window by manipulating the Z order but it's not well defined how to put it back.
Given a Windows 8 (.1) computer with the following power options:
And screensaver configured as:
The goal is to programmatically turn the display on and "kill" the screensaver (so that it will be re-activated after idle time).
(Note, that according to the settings it's possible that only the screensaver is on, or, that the display is off completely after the screensaver was on for about one minute).
What i have tried is:
SendMessage(HWND_Broadcast, WM_SysCommand, SC_MONITORPOWER, (LPARAM) - 1);
in combination with
// From Microsoft's Knowledge Base article #140723:
// http://support.microsoft.com/kb/140723
// "How to force a screen saver to close once started
// in Windows NT, Windows 2000, and Windows Server 2003"
public static void KillScreenSaver()
{
IntPtr hDesktop = OpenDesktop("Screen-saver", 0, false, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
if (hDesktop != IntPtr.Zero)
{
if (!EnumDesktopWindows(hDesktop, KillScreenSaverFunc, IntPtr.Zero) || !CloseDesktop(hDesktop))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
TerminateWindow(GetForegroundWindow());
}
}
private static bool KillScreenSaverFunc(IntPtr hWnd, IntPtr lParam)
{
if (IsWindowVisible(hWnd))
{
TerminateWindow(hWnd);
}
return true;
}
private static void TerminateWindow(IntPtr hWnd)
{
if (!PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
And
public static void ActivateScreensaver()
{
SetScreenSaverActive(TRUE);
}
private static void SetScreenSaverActive(uint active)
{
IntPtr nullVar = IntPtr.Zero;
// Ignoring error since ERROR_OPERATION_IN_PROGRESS is expected.
// Methode is called to reset timer and to prevent possible errors as mentioned in Microsoft's Knowledge Base article #140723:
// http://support.microsoft.com/kb/140723
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, active, ref nullVar, SPIF_SENDWININICHANGE);
}
Which worked, partially. However, the display was turned off immediately again (as a matter of fact, most times one couldn't even see that the monitor was turned again apart from the power-led going "steady on" instead of "flashing on off = power safe" for a short time).
So i guess i'm missing a crucial part of the picture like reliably resetting system idle timer to make sure the screen is not immediately being turned off, again.
Edit: According to my investigations SetThreadExecutionState(ES_DISPLAY_REQUIRED) seems to do the trick in regards to resetting the idle-timer. However, i still don't know the correct sequence of calls. I'll self-answer when i figure it out...
The following code will:
interrupt a running screensaver
turn on a turned-off screen ("turn off" as mentioned in power options)
private static readonly ILog Log = LogManager.GetLogger(
MethodBase.GetCurrentMethod().DeclaringType);
public void TurnOnScreenAndInterruptScreensaver()
{
TryTurnOnScreenAndResetDisplayIdleTimer();
TryInterruptScreensaver();
}
/// <summary>
/// Moves the mouse which turns on a turned-off screen and also resets the
/// display idle timer, which is key, because otherwise the
/// screen would be turned off again immediately.
/// </summary>
private static void TryTurnOnScreenAndResetDisplayIdleTimer()
{
var input = new SendInputNativeMethods.Input {
type = SendInputNativeMethods.SendInputEventType.InputMouse, };
try
{
SendInputNativeMethods.SendInput(input);
}
catch (Win32Exception exception)
{
Log.Error("Could not send mouse move input to turn on display", exception);
}
}
private static void TryInterruptScreensaver()
{
try
{
if (ScreensaverNativeMethods.GetScreenSaverRunning())
{
ScreensaverNativeMethods.KillScreenSaver();
}
// activate screen saver again so that after idle-"timeout" it shows again
ScreensaverNativeMethods.ActivateScreensaver();
}
catch (Win32Exception exception)
{
Log.Error("Screensaver could not be deactivated", exception);
}
}
SendInputNativeMethods:
public static class SendInputNativeMethods
{
public static void SendInput(params Input[] inputs)
{
if (SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<Input>())
!= (uint)inputs.Length)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern uint SendInput(
uint nInputs,
[MarshalAs(UnmanagedType.LPArray), In] Input[] pInputs,
int cbSize);
[StructLayout(LayoutKind.Sequential)]
public struct Input
{
public SendInputEventType type;
public MouseKeybdhardwareInputUnion mkhi;
}
[StructLayout(LayoutKind.Explicit)]
public struct MouseKeybdhardwareInputUnion
{
[FieldOffset(0)]
public MouseInputData mi;
[FieldOffset(0)]
public KEYBDINPUT ki;
[FieldOffset(0)]
public HARDWAREINPUT hi;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct HARDWAREINPUT
{
public int uMsg;
public short wParamL;
public short wParamH;
}
public struct MouseInputData
{
public int dx;
public int dy;
public uint mouseData;
public MouseEventFlags dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[Flags]
public enum MouseEventFlags : uint
{
MOUSEEVENTF_MOVE = 0x0001,
MOUSEEVENTF_LEFTDOWN = 0x0002,
MOUSEEVENTF_LEFTUP = 0x0004,
MOUSEEVENTF_RIGHTDOWN = 0x0008,
MOUSEEVENTF_RIGHTUP = 0x0010,
MOUSEEVENTF_MIDDLEDOWN = 0x0020,
MOUSEEVENTF_MIDDLEUP = 0x0040,
MOUSEEVENTF_XDOWN = 0x0080,
MOUSEEVENTF_XUP = 0x0100,
MOUSEEVENTF_WHEEL = 0x0800,
MOUSEEVENTF_VIRTUALDESK = 0x4000,
MOUSEEVENTF_ABSOLUTE = 0x8000
}
public enum SendInputEventType : int
{
InputMouse,
InputKeyboard,
InputHardware
}
ScreensaverNativeMethods:
internal static class ScreensaverNativeMethods
{
private const int SPI_GETSCREENSAVERRUNNING = 0x0072;
private const int SPI_SETSCREENSAVEACTIVE = 0x0011;
private const int SPIF_SENDWININICHANGE = 0x0002;
private const uint DESKTOP_WRITEOBJECTS = 0x0080;
private const uint DESKTOP_READOBJECTS = 0x0001;
private const int WM_CLOSE = 0x0010;
private const int TRUE = 1;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SystemParametersInfo(
uint uiAction,
uint uiParam,
ref IntPtr pvParam,
uint fWinIni);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool PostMessage(
IntPtr hWnd,
uint msg,
IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr OpenDesktop(
string lpszDesktop,
uint dwFlags,
[In, MarshalAs(UnmanagedType.Bool)]bool fInherit,
uint dwDesiredAccess);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseDesktop(IntPtr hDesktop);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumDesktopWindows(
IntPtr hDesktop,
EnumDesktopWindowsProc callback,
IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetForegroundWindow();
private delegate bool EnumDesktopWindowsProc(IntPtr hDesktop, IntPtr lParam);
public static bool GetScreenSaverRunning()
{
IntPtr isRunning = IntPtr.Zero;
if (!SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, ref isRunning, 0))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return isRunning != IntPtr.Zero;
}
public static void ActivateScreensaver()
{
SetScreenSaverActive(TRUE);
}
private static void SetScreenSaverActive(uint active)
{
IntPtr nullVar = IntPtr.Zero;
// Ignoring error since ERROR_OPERATION_IN_PROGRESS is expected.
// Methode is called to reset timer and to prevent possible errors
// as mentioned in Microsoft's Knowledge Base article #140723:
// http://support.microsoft.com/kb/140723
SystemParametersInfo(
SPI_SETSCREENSAVEACTIVE,
active,
ref nullVar,
SPIF_SENDWININICHANGE);
}
// From Microsoft's Knowledge Base article #140723:
// http://support.microsoft.com/kb/140723
// "How to force a screen saver to close once started
// in Windows NT, Windows 2000, and Windows Server 2003"
public static void KillScreenSaver()
{
IntPtr hDesktop = OpenDesktop(
"Screen-saver",
0,
false,
DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
if (hDesktop != IntPtr.Zero)
{
if (!EnumDesktopWindows(hDesktop, KillScreenSaverFunc, IntPtr.Zero)
|| !CloseDesktop(hDesktop))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
else
{
TerminateWindow(GetForegroundWindow());
}
}
private static bool KillScreenSaverFunc(IntPtr hWnd, IntPtr lParam)
{
if (IsWindowVisible(hWnd))
{
TerminateWindow(hWnd);
}
return true;
}
private static void TerminateWindow(IntPtr hWnd)
{
if (!PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
I'm trying to set an already installed windows service to automatic delayed start in C#. How do I set a windows service to
Automatic (Delayed Start)
Can't find that value in the ServiceStartMode enum.
Edit:1
public class ServiceAutoStartInfo
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SERVICE_DELAYED_AUTO_START_INFO
{
public bool fDelayedAutostart;
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ChangeServiceConfig2(IntPtr hService, int dwInfoLevel, IntPtr lpInfo);
// Service configuration parameter
const int SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3;
public bool ChangeDelayedAutoStart(IntPtr hService, bool delayed)
{
// Validate service handle
if (hService != IntPtr.Zero)
{
// Create
SERVICE_DELAYED_AUTO_START_INFO info = new SERVICE_DELAYED_AUTO_START_INFO();
// Set the DelayedAutostart property
info.fDelayedAutostart = delayed;
// Allocate necessary memory
IntPtr hInfo = Marshal.AllocHGlobal(Marshal.SizeOf(
typeof(SERVICE_DELAYED_AUTO_START_INFO)));
// Convert structure to pointer
Marshal.StructureToPtr(info, hInfo, true);
// Change the configuration
bool result = ChangeServiceConfig2(hService, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, hInfo);
// Release memory
Marshal.FreeHGlobal(hInfo);
return result;
}
return false;
}
}
This is how I call it:
var controller = new ServiceController(s.ServiceName);
var autoDelay = new ServiceAutoStartInfo();
autoDelay.ChangeDelayedAutoStart(controller.ServiceHandle.DangerousGetHandle(), true);
But with no result.
Look into calling the Windows ChangeServiceConfig2 function, with dwInfoLevel of SERVICE_CONFIG_DELAYED_AUTO_START_INFO and a SERVICE_DELAYED_AUTO_START_INFO struct with fDelayedAutostart set to TRUE.
Or, you can do this with the command line:
sc.exe config <servicename> start= delayed-auto
I'm using the following, which works for me on Windows 7 (when run as admin):
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.ServiceProcess;
namespace ServiceManager
{
/// <summary>
/// Extensions to the ServiceController class.
/// </summary>
public static class ServiceControlerExtensions
{
/// <summary>
/// Set the start mode for the service.
/// </summary>
/// <param name="serviceController">The service controller.</param>
/// <param name="mode">The desired start mode.</param>
public static void SetStartMode(this ServiceController serviceController, ServiceStartModeEx mode)
{
IntPtr serviceManagerHandle = OpenServiceManagerHandle();
IntPtr serviceHandle = OpenServiceHandle(serviceController, serviceManagerHandle);
try
{
if (mode == ServiceStartModeEx.DelayedAutomatic)
{
ChangeServiceStartType(serviceHandle, ServiceStartModeEx.Automatic);
ChangeDelayedAutoStart(serviceHandle, true);
}
else
{
// Delayed auto-start overrides other settings, so it must be set first.
ChangeDelayedAutoStart(serviceHandle, false);
ChangeServiceStartType(serviceHandle, mode);
}
}
finally
{
if (serviceHandle != IntPtr.Zero)
{
CloseServiceHandle(serviceHandle);
}
if (serviceHandle != IntPtr.Zero)
{
CloseServiceHandle(serviceManagerHandle);
}
}
}
private static IntPtr OpenServiceHandle(ServiceController serviceController, IntPtr serviceManagerHandle)
{
var serviceHandle = OpenService(
serviceManagerHandle,
serviceController.ServiceName,
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG);
if (serviceHandle == IntPtr.Zero)
{
throw new ExternalException("Open Service Error");
}
return serviceHandle;
}
private static IntPtr OpenServiceManagerHandle()
{
IntPtr serviceManagerHandle = OpenSCManager(null, null, SC_MANAGER_ALL_ACCESS);
if (serviceManagerHandle == IntPtr.Zero)
{
throw new ExternalException("Open Service Manager Error");
}
return serviceManagerHandle;
}
private static void ChangeServiceStartType(IntPtr serviceHandle, ServiceStartModeEx mode)
{
bool result = ChangeServiceConfig(
serviceHandle,
SERVICE_NO_CHANGE,
(uint)mode,
SERVICE_NO_CHANGE,
null,
null,
IntPtr.Zero,
null,
null,
null,
null);
if (result == false)
{
ThrowLastWin32Error("Could not change service start type");
}
}
private static void ChangeDelayedAutoStart(IntPtr hService, bool delayed)
{
// Create structure that contains DelayedAutoStart property.
SERVICE_DELAYED_AUTO_START_INFO info = new SERVICE_DELAYED_AUTO_START_INFO();
// Set the DelayedAutostart property in that structure.
info.fDelayedAutostart = delayed;
// Allocate necessary memory.
IntPtr hInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SERVICE_DELAYED_AUTO_START_INFO)));
// Convert structure to pointer.
Marshal.StructureToPtr(info, hInfo, true);
// Change the configuration.
bool result = ChangeServiceConfig2(hService, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, hInfo);
// Release memory.
Marshal.FreeHGlobal(hInfo);
if (result == false)
{
ThrowLastWin32Error("Could not set service to delayed automatic");
}
}
private static void ThrowLastWin32Error(string messagePrefix)
{
int nError = Marshal.GetLastWin32Error();
var win32Exception = new Win32Exception(nError);
string message = string.Format("{0}: {1}", messagePrefix, win32Exception.Message);
throw new ExternalException(message);
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr OpenService(
IntPtr hSCManager,
string lpServiceName,
uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode,
SetLastError = true)]
private static extern IntPtr OpenSCManager(
string machineName,
string databaseName,
uint dwAccess);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern Boolean ChangeServiceConfig(
IntPtr hService,
UInt32 nServiceType,
UInt32 nStartType,
UInt32 nErrorControl,
String lpBinaryPathName,
String lpLoadOrderGroup,
IntPtr lpdwTagId,
[In] char[] lpDependencies,
String lpServiceStartName,
String lpPassword,
String lpDisplayName);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ChangeServiceConfig2(
IntPtr hService,
int dwInfoLevel,
IntPtr lpInfo);
[DllImport("advapi32.dll", EntryPoint = "CloseServiceHandle")]
private static extern int CloseServiceHandle(IntPtr hSCObject);
private const uint SERVICE_NO_CHANGE = 0xFFFFFFFF;
private const uint SERVICE_QUERY_CONFIG = 0x00000001;
private const uint SERVICE_CHANGE_CONFIG = 0x00000002;
private const uint SC_MANAGER_ALL_ACCESS = 0x000F003F;
private const int SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SERVICE_DELAYED_AUTO_START_INFO
{
public bool fDelayedAutostart;
}
}
}
namespace ServiceManager
{
public enum ServiceStartModeEx
{
Automatic = 2,
Manual = 3,
Disabled = 4,
DelayedAutomatic = 99
}
}
You call it like this:
var serviceController = new ServiceController("Windows Update");
try
{
serviceController.SetStartMode(ServiceStartModeEx.DelayedAutomatic);
}
finally
{
serviceController.Close();
}
Update: This only works for setting up new services and is not what the OP asked for:
You can use the DelayedAutoStart property of the ServiceInstaller.
installer.DelayedAutoStart = true;
I believe you need to combine both methods ChangeServiceConfig and ChangeServiceConfig2.
pseudo-code follows:
public static void ChangeServiceStartupType(ServiceStartupType type, ...)
{
if (type == AutomaticDelayed)
{
if (ChangeServiceConfig2(.., DelayedAutoStart, ..))
{
ChangeServiceConfig(.., Automatic, ..);
}
}
else
{
ChangeServiceConfig2(.., !DelayedAutoStart, ..)
ChangeServiceConfig(.., type, ..)
}
}
edit: you also need to remove "delayed-automatic" when requesting non-delayed startup-type. Otherwise it won't be possible to set "automatic" type. ("automatic-delayed" overrides "automatic")
SERVICE_DELAYED_AUTO_START_INFO structure documentation states:
fDelayedAutostart
If this member is TRUE, the service is started after other auto-start
services are started plus a short delay. Otherwise, the service is
started during system boot.
This setting is ignored unless the service is an auto-start service.
https://learn.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_delayed_auto_start_info?redirectedfrom=MSDN#members
So I think if the service is not auto-start it won't change it to auto delayed, you would have to use sc.exe
I'm writing a program that needs to be able to extract the thumbnail image from a file. I've gotten a hold of a class, ThumbnailCreator, which is able to do this. The source of this Class is below.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.IO;
using System.Drawing.Imaging;
internal class ThumbnailCreator : IDisposable
{
// Fields
private IMalloc alloc;
private Size desiredSize;
private bool disposed;
private static readonly string fileExtention = ".jpg";
private Bitmap thumbnail;
// Methods
public ThumbnailCreator()
{
this.desiredSize = new Size(100, 100);
}
public ThumbnailCreator(Size desiredSize)
{
this.desiredSize = new Size(100, 100);
this.DesiredSize = desiredSize;
}
public void Dispose()
{
if (!this.disposed)
{
if (this.alloc != null)
{
Marshal.ReleaseComObject(this.alloc);
}
this.alloc = null;
if (this.thumbnail != null)
{
this.thumbnail.Dispose();
}
this.disposed = true;
}
}
~ThumbnailCreator()
{
this.Dispose();
}
private bool getThumbNail(string file, IntPtr pidl, IShellFolder item)
{
bool CS;
IntPtr hBmp = IntPtr.Zero;
IExtractImage extractImage = null;
try
{
if (Path.GetFileName(PathFromPidl(pidl)).ToUpper().Equals(Path.GetFileName(file).ToUpper()))
{
int prgf;
IUnknown iunk = null;
Guid iidExtractImage = new Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1");
item.GetUIObjectOf(IntPtr.Zero, 1, ref pidl, ref iidExtractImage, out prgf, ref iunk);
extractImage = (IExtractImage) iunk;
if (extractImage != null)
{
SIZE sz = new SIZE {
cx = this.desiredSize.Width,
cy = this.desiredSize.Height
};
StringBuilder location = new StringBuilder(260, 260);
int priority = 0;
int requestedColourDepth = 0x20;
EIEIFLAG flags = EIEIFLAG.IEIFLAG_SCREEN | EIEIFLAG.IEIFLAG_ASPECT;
int uFlags = (int) flags;
extractImage.GetLocation(location, location.Capacity, ref priority, ref sz, requestedColourDepth, ref uFlags);
extractImage.Extract(out hBmp);
if (hBmp != IntPtr.Zero)
{
this.thumbnail = Image.FromHbitmap(hBmp);
}
Marshal.ReleaseComObject(extractImage);
extractImage = null;
}
return true;
}
CS = false;
}
catch (Exception)
{
if (hBmp != IntPtr.Zero)
{
UnManagedMethods.DeleteObject(hBmp);
}
if (extractImage != null)
{
Marshal.ReleaseComObject(extractImage);
}
throw;
}
return CS;
}
public Bitmap GetThumbNail(string file)
{
if (!File.Exists(file) && !Directory.Exists(file))
{
throw new FileNotFoundException(string.Format("The file '{0}' does not exist", file), file);
}
if (this.thumbnail != null)
{
this.thumbnail.Dispose();
this.thumbnail = null;
}
IShellFolder folder = getDesktopFolder;
if (folder != null)
{
IntPtr pidlMain;
try
{
int cParsed;
int pdwAttrib;
string filePath = Path.GetDirectoryName(file);
folder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, filePath, out cParsed, out pidlMain, out pdwAttrib);
}
catch (Exception)
{
Marshal.ReleaseComObject(folder);
throw;
}
if (pidlMain != IntPtr.Zero)
{
Guid iidShellFolder = new Guid("000214E6-0000-0000-C000-000000000046");
IShellFolder item = null;
try
{
folder.BindToObject(pidlMain, IntPtr.Zero, ref iidShellFolder, ref item);
}
catch (Exception)
{
Marshal.ReleaseComObject(folder);
this.Allocator.Free(pidlMain);
throw;
}
if (item != null)
{
IEnumIDList idEnum = null;
try
{
item.EnumObjects(IntPtr.Zero, ESHCONTF.SHCONTF_NONFOLDERS | ESHCONTF.SHCONTF_FOLDERS, ref idEnum);
}
catch (Exception)
{
Marshal.ReleaseComObject(folder);
this.Allocator.Free(pidlMain);
throw;
}
if (idEnum != null)
{
IntPtr pidl = IntPtr.Zero;
bool complete = false;
while (!complete)
{
int fetched;
if (idEnum.Next(1, ref pidl, out fetched) != 0)
{
pidl = IntPtr.Zero;
complete = true;
}
else if (this.getThumbNail(file, pidl, item))
{
complete = true;
}
if (pidl != IntPtr.Zero)
{
this.Allocator.Free(pidl);
}
}
Marshal.ReleaseComObject(idEnum);
}
Marshal.ReleaseComObject(item);
}
this.Allocator.Free(pidlMain);
}
Marshal.ReleaseComObject(folder);
}
return this.thumbnail;
}
private static string PathFromPidl(IntPtr pidl)
{
StringBuilder path = new StringBuilder(260, 260);
if (UnManagedMethods.SHGetPathFromIDList(pidl, path) != 0)
{
return path.ToString();
}
return string.Empty;
}
// Properties
private IMalloc Allocator
{
get
{
if (!this.disposed && (this.alloc == null))
{
UnManagedMethods.SHGetMalloc(out this.alloc);
}
return this.alloc;
}
}
public Size DesiredSize
{
get
{
return this.desiredSize;
}
set
{
this.desiredSize = value;
}
}
private static IShellFolder getDesktopFolder
{
get
{
IShellFolder ppshf;
UnManagedMethods.SHGetDesktopFolder(out ppshf);
return ppshf;
}
}
public Bitmap ThumbNail
{
get
{
return this.thumbnail;
}
}
// Nested Types
private enum EIEIFLAG
{
IEIFLAG_ASPECT = 4,
IEIFLAG_ASYNC = 1,
IEIFLAG_CACHE = 2,
IEIFLAG_GLEAM = 0x10,
IEIFLAG_NOBORDER = 0x100,
IEIFLAG_NOSTAMP = 0x80,
IEIFLAG_OFFLINE = 8,
IEIFLAG_ORIGSIZE = 0x40,
IEIFLAG_QUALITY = 0x200,
IEIFLAG_SCREEN = 0x20
}
[Flags]
private enum ESFGAO
{
SFGAO_CANCOPY = 1,
SFGAO_CANDELETE = 0x20,
SFGAO_CANLINK = 4,
SFGAO_CANMOVE = 2,
SFGAO_CANRENAME = 0x10,
SFGAO_CAPABILITYMASK = 0x177,
SFGAO_COMPRESSED = 0x4000000,
SFGAO_CONTENTSMASK = -2147483648,
SFGAO_DISPLAYATTRMASK = 0xf0000,
SFGAO_DROPTARGET = 0x100,
SFGAO_FILESYSANCESTOR = 0x10000000,
SFGAO_FILESYSTEM = 0x40000000,
SFGAO_FOLDER = 0x20000000,
SFGAO_GHOSTED = 0x80000,
SFGAO_HASPROPSHEET = 0x40,
SFGAO_HASSUBFOLDER = -2147483648,
SFGAO_LINK = 0x10000,
SFGAO_READONLY = 0x40000,
SFGAO_REMOVABLE = 0x2000000,
SFGAO_SHARE = 0x20000,
SFGAO_VALIDATE = 0x1000000
}
[Flags]
private enum ESHCONTF
{
SHCONTF_FOLDERS = 0x20,
SHCONTF_INCLUDEHIDDEN = 0x80,
SHCONTF_NONFOLDERS = 0x40
}
[Flags]
private enum ESHGDN
{
SHGDN_FORADDRESSBAR = 0x4000,
SHGDN_FORPARSING = 0x8000,
SHGDN_INFOLDER = 1,
SHGDN_NORMAL = 0
}
[Flags]
private enum ESTRRET
{
STRRET_WSTR,
STRRET_OFFSET,
STRRET_CSTR
}
[ComImport, Guid("000214F2-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IEnumIDList
{
[PreserveSig]
int Next(int celt, ref IntPtr rgelt, out int pceltFetched);
void Skip(int celt);
void Reset();
void Clone(ref ThumbnailCreator.IEnumIDList ppenum);
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1")]
private interface IExtractImage
{
void GetLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszPathBuffer, int cch, ref int pdwPriority, ref ThumbnailCreator.SIZE prgSize, int dwRecClrDepth, ref int pdwFlags);
void Extract(out IntPtr phBmpThumbnail);
}
[ComImport, Guid("00000002-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IMalloc
{
[PreserveSig]
IntPtr Alloc(int cb);
[PreserveSig]
IntPtr Realloc(IntPtr pv, int cb);
[PreserveSig]
void Free(IntPtr pv);
[PreserveSig]
int GetSize(IntPtr pv);
[PreserveSig]
int DidAlloc(IntPtr pv);
[PreserveSig]
void HeapMinimize();
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214E6-0000-0000-C000-000000000046")]
private interface IShellFolder
{
void ParseDisplayName(IntPtr hwndOwner, IntPtr pbcReserved, [MarshalAs(UnmanagedType.LPWStr)] string lpszDisplayName, out int pchEaten, out IntPtr ppidl, out int pdwAttributes);
void EnumObjects(IntPtr hwndOwner, [MarshalAs(UnmanagedType.U4)] ThumbnailCreator.ESHCONTF grfFlags, ref ThumbnailCreator.IEnumIDList ppenumIDList);
void BindToObject(IntPtr pidl, IntPtr pbcReserved, ref Guid riid, ref ThumbnailCreator.IShellFolder ppvOut);
void BindToStorage(IntPtr pidl, IntPtr pbcReserved, ref Guid riid, IntPtr ppvObj);
[PreserveSig]
int CompareIDs(IntPtr lParam, IntPtr pidl1, IntPtr pidl2);
void CreateViewObject(IntPtr hwndOwner, ref Guid riid, IntPtr ppvOut);
void GetAttributesOf(int cidl, IntPtr apidl, [MarshalAs(UnmanagedType.U4)] ref ThumbnailCreator.ESFGAO rgfInOut);
void GetUIObjectOf(IntPtr hwndOwner, int cidl, ref IntPtr apidl, ref Guid riid, out int prgfInOut, ref ThumbnailCreator.IUnknown ppvOut);
void GetDisplayNameOf(IntPtr pidl, [MarshalAs(UnmanagedType.U4)] ThumbnailCreator.ESHGDN uFlags, ref ThumbnailCreator.STRRET_CSTR lpName);
void SetNameOf(IntPtr hwndOwner, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] string lpszName, [MarshalAs(UnmanagedType.U4)] ThumbnailCreator.ESHCONTF uFlags, ref IntPtr ppidlOut);
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000000-0000-0000-C000-000000000046")]
private interface IUnknown
{
[PreserveSig]
IntPtr QueryInterface(ref Guid riid, out IntPtr pVoid);
[PreserveSig]
IntPtr AddRef();
[PreserveSig]
IntPtr Release();
}
[StructLayout(LayoutKind.Sequential)]
private struct SIZE
{
public int cx;
public int cy;
}
[StructLayout(LayoutKind.Explicit, CharSet=CharSet.Auto)]
private struct STRRET_ANY
{
// Fields
[FieldOffset(4)]
public IntPtr pOLEString;
[FieldOffset(0)]
public ThumbnailCreator.ESTRRET uType;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto, Pack=4)]
private struct STRRET_CSTR
{
public ThumbnailCreator.ESTRRET uType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=520)]
public byte[] cStr;
}
private class UnManagedMethods
{
// Methods
[DllImport("gdi32", CharSet=CharSet.Auto)]
internal static extern int DeleteObject(IntPtr hObject);
[DllImport("shell32", CharSet=CharSet.Auto)]
internal static extern int SHGetDesktopFolder(out ThumbnailCreator.IShellFolder ppshf);
[DllImport("shell32", CharSet=CharSet.Auto)]
internal static extern int SHGetMalloc(out ThumbnailCreator.IMalloc ppMalloc);
[DllImport("shell32", CharSet=CharSet.Auto)]
internal static extern int SHGetPathFromIDList(IntPtr pidl, StringBuilder pszPath);
}
}
As a test I created a quick console application, the code of which is below. This test worked fine and was able to extract a thumbnail, and saved it to a PNG file.
static void Main(string[] args)
{
try
{
Console.WriteLine("press a key to extract");
System.Console.ReadKey();
string path = #"C:\somefile.xyz";
ThumbnailCreator creator = new ThumbnailCreator();
creator.DesiredSize = new Size(600, 600);
Bitmap bm = creator.GetThumbNail(path);
bm.Save(#"C:\blah.png", System.Drawing.Imaging.ImageFormat.Png);
Console.WriteLine("press a key to exit");
System.Console.ReadKey();
}
catch (Exception exp)
{
Console.WriteLine(exp.Message);
}
}
My problem is the real application I want to use this in runs as a plug-in for another application. When I try to run the same test code in the plug-in creator.GetThumbNail(path); returns null.
I debugged a little further and found that in the method ThumbnailCreator.getThumbNail(string file, IntPtr pidl,IshellFolder item) the line extractImage.Extract(out hBmp); returns IntPtr.Zero. Whereas in the console application that works this method actually returns a number. Maybe this is the problem? Or maybe this problem happens before this. Honestly, I'm completely lost when it comes to Interop and Windows API stuff.
Does anyone know of any possible reasons why this class would work in a standalone console application, but not as part of a plug-in for another application?
Update
Here's a bit more detail of how this plug-in is created. To add a custom command to this program you create a class that implements an ICommand interface from is API. The interface has a single Execute() method where code is place that should run when the user executes the custom command from the program. The native application, however, is what actualy instantiates my command class and calls the execute method.
What could be different about the way that the native app instantiates my command class and or calls the Execute() method that would prevent the thumbnail extraction from working?
Just ran across this issue myself and I do say, it took some time to figure out that it was a bitness issue. In your scenario your console app was probably compiled as x64 and your Windows app as x86.
The ExtractImage interface (BB2E617C-0920-11d1-9A0B-00C04FC2D6C1) on a 64bit OS is only accessible from 64bit applications (because explorer is 64bit), thus resulting in a "Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))" exception in applications compiled as x86.
Code that calls the Shell API must be in a COM Single Threaded Apartment, try putting [STAThread] attribute on the thread that calls this.