ファイルに対する画像を取得する(WPF)

WPFで、単にファイルに対する画像を取得しようとしたのですが、たったこれだけのことにも関わらず、
意外と、どこにもソースが落ちていない。

もちろん、 WindowsAPICodePack を使えば、簡単にできるんですが、大なた過ぎて、ちょっと・・・っていう感じだったり、あるいは、サムネイルを生成したいわけで、アイコン画像が欲しいわけじゃ無いとか、そういうカスタマイズ性がないみたいで、ちょっと困る感じ名部分があります。

というか、C++ならまだ良いんですが、C#だと、IExtractThumbnailとの戦いで消耗している人達ばっかりで、???って感じでした。

ということで、 IShellItemImageFactory を使ってサックリと作ってみました。

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;

namespace FileThumbnailExtractor
{
  public static class FileThumbnailExtractor
  {
    [StructLayoutAttribute(LayoutKind.Sequential)]
    struct SIZE
    {
      public int cx;
      public int cy;
    }

    [Flags]
    enum SIIGBF
    {
      RESIZETOFIT = 0,
      BIGGERSIZEOK = 1,
      MEMORYONLY = 2,
      ICONONLY = 4,
      THUMBNAILONLY = 8,
      INCACHEONLY = 0x10,
      CROPTOSQUARE = 0x20,
      WIDETHUMBNAILS = 0x40,
      ICONBACKGROUND = 0x80,
      SCALEUP = 0x100,
    }

    [ComImport, Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IShellItemImageFactory
    {
      [PreserveSig]
      void GetImage(SIZE size, SIIGBF flags, out IntPtr phbm);
    }

    [DllImport("shell32"), PreserveSig]
    extern static void SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, [In] ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object iunk);

    [DllImport("gdi32")]
    extern static int DeleteObject(IntPtr hObject);

    public static BitmapSource GetThumbnail(string fileName, int desiredWidth, int desiredHeight, ThumbnailType type)
    {
      object iunk = null;
      IntPtr hBmp = IntPtr.Zero;
      try
      {
        var IID_IShellItemImageFactory = new Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b");
        SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref IID_IShellItemImageFactory, out iunk);
        var factory = (IShellItemImageFactory)iunk;
        SIIGBF flags = SIIGBF.BIGGERSIZEOK;
        if (type == ThumbnailType.Icon) flags |= SIIGBF.ICONONLY;
        else if (type == ThumbnailType.Thumbnail) flags |= SIIGBF.THUMBNAILONLY;

        factory.GetImage(new SIZE { cx = desiredWidth, cy = desiredHeight }, flags, out hBmp);
        var bmp = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBmp, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
        bmp.Freeze();
        return bmp;
      }
      finally
      {
        DeleteObject(hBmp);
        Marshal.ReleaseComObject(iunk);
      }
    }

    public static async Task<BitmapSource> GetThumbnailAsync(string fileName, int desiredWidth, int desiredHeight, ThumbnailType type)
    {
      return await Task.Run(() => GetThumbnail(fileName, desiredWidth, desiredHeight, type));
    }
  }

  [Flags]
  public enum ThumbnailType
  {
    Icon = 1,
    Thumbnail = 2,
    Any = 3,
  }
}

Aero-Snap による Window サイズの変更を検出する

GetWindowPlacement では、ウィンドウが通常の状態でのウィンドウ座標と、実際にウィンドウが、最大化されている(SW_MAXIMIZE)のか、最小化されている(SW_MINIMIZE)のか、あるいは、通常の状態(SW_SHOWNORMAL)なのかが取得できます。従って、普通なら、この関数を呼び出せば、特に何の問題もなく、ウィンドウの状態を取得することが出来ます。

ところが、 Windows 8 で導入された Aero-Snap ([Win] + 矢印キー)では、この関数では関知できない状態になります。
具体的には、ウィンドウの状態が、通常の状態(SW_SHOWNORMAL)であると報告されます。これだと、アプリは自分の状況を正しく認識できません。

なので、 Aero-Snap 状態であることを知るためにはもうちょっと小細工をする必要があります。答えをそのまま書くと、 SW_SHOWNORMAL と認識された場合には、 GetWindowRect を使って取得したウィンドウサイズと rcNormalPosition の値を比較すると、 Aero-Snap かどうかを区別できるようです。

bool isResizeByAerosnap(HWND wnd)
{
  // The function works because of the fact that:
  // When the window is resized by Aero-snap, GetWindowPlacement returns
  // SW_SHOWNORMAL but wp.rcNormalPosition is not identical to the size
  // returned by GetWindowRect.
  WINDOWPLACEMENT wp;
  wp.length = sizeof(WINDOWPLACEMENT);
  GetWindowPlacement(wnd, &wp);
  if (wp.showCmd != SW_SHOWNORMAL)
  	return false;
  RECT rc;
  GetWindowRect(wnd, &rc);
  return memcmp(&rc, &wp.rcNormalPosition, sizeof(RECT)) != 0;
}

まぁ、問題はそんなことよりも、 Aero-Snap 状態を再現する API がないことなんですけどねw

C#からネイティブDLLを呼び出す場合のVSからのデバッグのジレンマを解決する

C#を使う最大のメリットって、やっぱり、Visual Studioですよね!」って自信を持って言いたいですね。

という心境ではあるんですが、私の仕事はどっちかというとC++よりなので、どうしても、DllImportはお友達という側面があります。そうすると、プログラム実行時に、

AnyCPUなアセンブリ.exe
ネイティブC++.dll

みたいな感じになって、要は、AnyCPUなアセンブリがネイティブC++ちゃんを呼び出す構図になるんですが、この構成、64-bitの環境で開発をしていたりすると相当なジレンマを抱えることになります。

普通に実行する場合には、x64環境なので、「ネイティブC++.dll」さんは、64-bit版を配置しておくべきなんですが、VS上からデバッグしようとすると、win32(32-bit版)を置いておかないと行けなかったり、あるいは、「ネイティブC++.dll」さんのデバッグ版は激遅なので、デバッグ中でも普通はリリース版を置いておきたかったりと、様々な面倒なシチュエーションに出くわすことになります。

で、これを解決する方法を考えたよという話です。
簡単に言うと、

AnyCPUなアセンブリ.exe
win32/
  ネイティブC++.dll
x64/
  ネイティブC++.dll

というディレクトリ構成にしてしまえという。「AnyCPUなアセンブリ.exe」は、賢いので、自分が実行されている環境に応じて、どっちをロードするか見てくれるという。

で、どうするかというと、次みたいなコードをアプリに取り込んでしまえという。
これは、MainWindow.xaml.csとかの例ですが、もっと早い段階がよければ、そこでやっても良いですね:

static MainWindow()
{
  SetDllDirectory("");
#if DEBUG
  if (setDllLocation(true))
    return;
#endif
  setDllLocation(false);
}

/// <summary>
/// Controlling DLL directory which native DLLs are loaded from.
/// </summary>
/// <param name="debug">Whether this is debug mode or not.</param>
/// <returns><c>true</c> if DLL directory is correctly set; otherwise <c>false</c>.</returns>
static bool setDllLocation(bool debug)
{
  var appDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
  if (debug) appDir = Path.Combine(appDir, "debug");
  var dllDir = Path.Combine(appDir, IntPtr.Size == 8 ? "x64" : "win32");
  if (Directory.Exists(dllDir))
  {
    Debug.WriteLine(string.Format("Set DLL Location ({0}): {1}", debug ? "Debug" : "Release", dllDir));
    SetDllDirectory(dllDir);
    return true;
  }
  return false;
}

[DllImport("Kernel32.dll")]
static extern bool SetDllDirectory(string lpPathName);

やってることの内容としては、 SetDllDirectory APIを呼び出して、DLLをロードするディレクトリを事前に変更しておこうと。
C++だと、delayloadがらみを調整して同じような事が出来ますが、C#でのDllImportは完全に実行時にバインディングを解決することになるので、かなりこういう芸当がやりやすいです。

で、上のコードにはオマケがあって、デバッグ版では、さらに、

AnyCPUなアセンブリ.exe
debug/
  win32/
    ネイティブC++.dll
  x64/
    ネイティブC++.dll

みたいなレイアウトにしておけば、デバッグ版のDLLを見つけてロードしてくれると。
いずれにしても、デバッグ時にDLLをコピーしたり移動したりの右往左往がかなり軽減されるという意味では開発が楽になると思います。

特定のプロセスが開いているファイルの一覧を取得する

そんなん、下の奴を見れば終わり:

っていう訳にもいきませぬ。
上の奴からたどれる範囲のコードでは、

32bit PID値に未対応

SYSTEM_HANDLE ではなく、SYSTEM_EXTENDED_HANDLE を使う。

ネットワーク共有上のファイルに未対応

\Device\Mup\ から始まるファイル名を適切に処理する。

っていう修正が必要です。C#だとポインタとかで割と死ねます。

ソース

次の様な使い方を想定:

foreach (var fn in ProcessUtils.EnumFilesOpened(hwnd))
{
  Console.WriteLine(fn);
}

全ソース。あんまりコメントなし:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace Handles
{
  public static class ProcessUtils
  {
    // HOWTO: Enumerate handles
    // http://forum.sysinternals.com/howto-enumerate-handles_topic18892.html
    public static IEnumerable<string> EnumFilesOpened(IntPtr hwnd)
    {
      uint pid;
      GetWindowThreadProcessId(hwnd, out pid);
      using (var proc = Process.GetProcessById((int)pid))
      {
        IntPtr hProcess = proc.Handle;
        foreach (var shi in EnumHandles((int)pid))
        {
          IntPtr hObj = IntPtr.Zero;
          try
          {
            if (!NT_SUCCESS(NtDuplicateObject(hProcess,
                             shi.HandleValue,
                             Process.GetCurrentProcess().Handle,
                             out hObj, 0, 0, 0)))
            {
              continue;
            }

            using (var nto1 = new NtObject(hObj, ObjectInformationClass.ObjectTypeInformation, typeof(OBJECT_TYPE_INFORMATION)))
            {
              var oti = ObjectTypeInformationFromBuffer(nto1.Buffer);
              if (oti.Name.ToString() != "File")
                continue;
            }

            //  this can lock NtQueryInformationFile (blocking IO named pipes), and 
            //  sometimes also NtQueryObject as well.
            //  It is recommended to skip this granted access, 
            //  although a correct explanation is still to be found.
            //
            if (shi.GrantedAccess == 0x0012019f   // seems mandatory (named pipes)
              || shi.GrantedAccess == 0x001a019f   // blocking named pipe (not a file anyways)

              // Ignore certain flags combinations that are 
              // frequently associated with system files and
              // folders. This gives us a large performance
              // advantage at the cost of some open file handles
              // potentially being missed. Since we don't have
              // control over what happens shortly after, we
              // cannot be perfect anyway.

              || shi.GrantedAccess == 0x00100000   // SYNCHRONIZE only
              || shi.GrantedAccess == 0x00160001   // used on directories
              || shi.GrantedAccess == 0x00100001   // used on directories
              || shi.GrantedAccess == 0x00100020)  // used on SxS files
            {
              continue;
            }

            using (var nto2 = new NtObject(hObj, ObjectInformationClass.ObjectNameInformation, typeof(OBJECT_NAME_INFORMATION)))
            {
              var oni = ObjectNameInformationFromBuffer(nto2.Buffer);
              yield return GetRegularFileNameFromDevice(oni.Name.ToString());
            }
          }
          finally
          {
            CloseHandle(hObj);
          }

        }
      }
    }

    /// <summary>
    /// Works much like as <c>(OBJECT_TYPE_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_TYPE_INFORMATION))</c>
    /// </summary>
    /// <param name="buffer">Pointer to byte buffer of OBJECT_TYPE_INFORMATION.</param>
    /// <returns>C# interpretation of OBJECT_TYPE_INFORMATION; it contains references to the buffer
    /// and the buffer should not be released until finishing access to the structure.</returns>
    static OBJECT_TYPE_INFORMATION ObjectTypeInformationFromBuffer(IntPtr buffer)
    {
#if USE_SAFE_CODE_ONLY
      return (OBJECT_TYPE_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_TYPE_INFORMATION));
#else
      unsafe { return *(OBJECT_TYPE_INFORMATION*)buffer.ToPointer(); }
#endif
    }

    /// <summary>
    /// Works much like as <c>(OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_NAME_INFORMATION))</c>
    /// </summary>
    /// <param name="buffer">Pointer to byte buffer of OBJECT_NAME_INFORMATION.</param>
    /// <returns>C# interpretation of OBJECT_NAME_INFORMATION; it contains references to the buffer
    /// and the buffer should not be released until finishing access to the structure.</returns>
    static OBJECT_NAME_INFORMATION ObjectNameInformationFromBuffer(IntPtr buffer)
    {
#if USE_SAFE_CODE_ONLY
      return (OBJECT_NAME_INFORMATION)Marshal.PtrToStructure(buffer, typeof(OBJECT_NAME_INFORMATION));
#else
      unsafe { return *(OBJECT_NAME_INFORMATION*)buffer.ToPointer(); }
#endif
    }

    class NtObject : IDisposable
    {
      public NtObject(IntPtr hObj, ObjectInformationClass infoClass, Type type)
      {
        Init(hObj, infoClass, Marshal.SizeOf(type));
      }

      public NtObject(IntPtr hObj, ObjectInformationClass infoClass, int estimatedSize)
      {
        Init(hObj, infoClass, estimatedSize);
      }

      public void Init(IntPtr hObj, ObjectInformationClass infoClass, int estimatedSize)
      {
        Close();

        // NOTE:
        // Buffer may be referenced by certain fields in m_obj and should not be
        // released before releasing m_obj.
        Buffer = Query(hObj, infoClass, estimatedSize);
      }

      public void Close()
      {
        if (Buffer != IntPtr.Zero)
        {
          Marshal.FreeCoTaskMem(Buffer);
          Buffer = IntPtr.Zero;
        }
      }

      public void Dispose()
      {
        Close();
      }

      /// <summary>
      /// Return the buffer. You can use <see cref="Marshal.PtrToStructure"/> to get the actual
      /// structure from the buffer.
      /// Basically I want to provide NTObject&lt;T&gt; but C# generics does not allow me to
      /// use T* pointer if T is a generic parameter.
      /// </summary>
      public IntPtr Buffer { get; private set; }

      public static IntPtr Query(IntPtr hObj, ObjectInformationClass infoClass, int estimatedSize)
      {
        int size = estimatedSize;
        IntPtr buf = Marshal.AllocCoTaskMem(size);
        int retsize = 0;
        while (true)
        {
          var ret = NtQueryObject(hObj, infoClass, buf, size, out retsize);
          if (NT_SUCCESS(ret))
            return buf;
          if (ret == NT_STATUS.INFO_LENGTH_MISMATCH || ret == NT_STATUS.BUFFER_OVERFLOW)
          {
            buf = Marshal.ReAllocCoTaskMem(buf, retsize);
            size = retsize;
          }
          else
          {
            Marshal.FreeCoTaskMem(buf);
            return IntPtr.Zero;
          }
        }
      }
    }

    static readonly string NETWORK_PREFIX = @"\Device\Mup\";

    static string GetRegularFileNameFromDevice(string strRawName)
    {
      if (strRawName.StartsWith(NETWORK_PREFIX))
        return @"\\" + strRawName.Substring(NETWORK_PREFIX.Length);

      string strFileName = strRawName;
      foreach (var drvPath in Environment.GetLogicalDrives())
      {
        var drv = drvPath.Substring(0, 2);
        var sb = new StringBuilder(MAX_PATH);
        if (QueryDosDevice(drv, sb, MAX_PATH) == 0)
          return strRawName;

        string drvRoot = sb.ToString();
        if (strFileName.StartsWith(drvRoot))
        {
          strFileName = drv + strFileName.Substring(drvRoot.Length);
          break;
        }
      }
      return strFileName;
    }

    static SYSTEM_EXTENDED_HANDLE SystemExtendedHandleFromPtr(IntPtr ptr, int offset)
    {
#if USE_SAFE_CODE_ONLY
      return (SYSTEM_EXTENDED_HANDLE)Marshal.PtrToStructure(buffer.Offset(offset), typeof(SYSTEM_EXTENDED_HANDLE));
#else
      unsafe
      {
        var p = (byte*)ptr.ToPointer() + offset;
        return *(SYSTEM_EXTENDED_HANDLE*)p;
      }
#endif
    }

    static int lastSizeUsed = 0x10000;

    static IEnumerable<SYSTEM_EXTENDED_HANDLE> EnumHandles(int processId)
    {
      int size = lastSizeUsed;
      IntPtr buffer = Marshal.AllocCoTaskMem(size);
      try
      {
        int required;
        while (NtQuerySystemInformation(SystemExtendedHandleInformation, buffer, size, out required) == NT_STATUS.INFO_LENGTH_MISMATCH)
        {
          size = required;
          buffer = Marshal.ReAllocCoTaskMem(buffer, size);
        }

        // FIXME: it should be race condition safe...
        if (lastSizeUsed < size)
          lastSizeUsed = size;

        // sizeof(SYSTEM_HANDLE) is 16 on 32-bit and 42 on 64-bit due to padding issues
        int entrySize = Marshal.SizeOf(typeof(SYSTEM_EXTENDED_HANDLE));
        int offset = Marshal.SizeOf(typeof(IntPtr)) * 2;
        int handleCount = Marshal.ReadInt32(buffer);

        for (int i = 0; i < handleCount; i++)
        {
          var shi = SystemExtendedHandleFromPtr(buffer, offset + entrySize * i);
          if (shi.UniqueProcessId != new IntPtr(processId))
            continue;

          yield return shi;
        }
      }
      finally
      {
        if (buffer != IntPtr.Zero)
          Marshal.FreeCoTaskMem(buffer);
      }
    }

    static string GetProcessCommandLine(uint processId)
    {
      IntPtr hProcess = OpenProcess(
        PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
        false, processId);
      if (hProcess == IntPtr.Zero)
        return string.Empty;

      try
      {
        return GetProcessCommandLine(hProcess);
      }
      catch
      {
        return string.Empty;
      }
      finally
      {
        CloseHandle(hProcess);
      }
    }

    static string GetProcessCommandLine(IntPtr hProcess)
    {
      int size;
      PROCESS_BASIC_INFORMATION pbi;
      int status = NtQueryInformationProcess(
        hProcess, PROCESSINFOCLASS.ProcessBasicInformation,
        out pbi, Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)),
        out size);
      int err = RtlNtStatusToDosError(status);
      if (err != 0)
        throw new Win32Exception(err);

      // PEB
      var peb = ObjectFromProcessMemory<PEB>(hProcess, pbi.PebBaseAddress);

      var upp = ObjectFromProcessMemory<RTL_USER_PROCESS_PARAMETERS>(hProcess, peb.ProcessParameters);
      if (upp.CommandLine.Length == 0)
        return string.Empty;
      return Encoding.Unicode.GetString(ReadProcessMemory(hProcess, upp.CommandLine.Buffer, upp.CommandLine.Length));
    }

    static Type ObjectFromProcessMemory<Type>(IntPtr hProcess, IntPtr lpBaseAddress) where Type : struct
    {
      return (Type)ObjectFromProcessMemory(hProcess, lpBaseAddress, typeof(Type));
    }

    static object ObjectFromProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, Type type)
    {
      int size = Marshal.SizeOf(type);
      return BufferToStructure(ReadProcessMemory(hProcess, lpBaseAddress, size), type);
    }

    static byte[] ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, int size)
    {
      byte[] buffer = new byte[size];
      IntPtr bytesRead;
      if (ReadProcessMemory(hProcess,
        lpBaseAddress,
        buffer,
        new IntPtr(size),
        out bytesRead))
      {
        if (bytesRead.ToInt32() < size)
          throw new ApplicationException(
            string.Format("Read only {0} of {1} bytes", bytesRead, size));
        return buffer;
      }
      throw new Win32Exception();
    }

    static object BufferToStructure(byte[] buffer, Type type)
    {
      unsafe
      {
        fixed (byte* p = buffer)
        {
          return Marshal.PtrToStructure(new IntPtr(p), type);
        }
      }
    }

    [StructLayout(LayoutKind.Sequential)]
    struct PROCESS_BASIC_INFORMATION
    {
      IntPtr Reserved1;
      public IntPtr PebBaseAddress; // PEB*
      IntPtr reserved2_0;
      IntPtr reserved2_1;
      public IntPtr UniqueProcessId; // ULONG_PTR
      IntPtr Reserved3;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct PEB
    {
      IntPtr Reserved1;
      IntPtr Reserved2;
      IntPtr Reserved3;
      public IntPtr Ldr; // struct PEB_LDR_DATA*
      public IntPtr ProcessParameters; // RTL_USER_PROCESS_PARAMETERS*
      // ...
    }

    [StructLayout(LayoutKind.Sequential)]
    struct RTL_USER_PROCESS_PARAMETERS
    {
      byte b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15;
      IntPtr ip0, ip1, ip2, ip3, ip4, ip5, ip6, ip7, ip8, ip9;
      public UNICODE_STRING ImagePathName;
      public UNICODE_STRING CommandLine;
    }

    enum PROCESSINFOCLASS
    {
      ProcessBasicInformation = 0,
      ProcessDebugPort = 7,
      ProcessWow64Information = 26,
      ProcessImageFileName = 27,
    }

    [DllImport("ntdll.dll")]
    static extern int NtQueryInformationProcess(IntPtr hProcess, PROCESSINFOCLASS pic,
    out PROCESS_BASIC_INFORMATION pbi, int cb, out int pSize);

    [DllImport("ntdll.dll")]
    static extern int RtlNtStatusToDosError(int Status);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, IntPtr nSize, out IntPtr lpNumberOfBytesRead);



    [DllImport("user32.dll")]
    static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr OpenProcess(
      uint dwDesiredAccess,
      bool bInheritHandle,
      uint dwProcessId);

    const uint PROCESS_DUP_HANDLE = 0x0040;
    const uint PROCESS_QUERY_INFORMATION = 0x0400U;
    const uint PROCESS_VM_READ = 0x0010U;

    [DllImport("kernel32.dll")]
    static extern int CloseHandle(IntPtr hObject);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);

    const int MAX_PATH = 260;

    [DllImport("ntdll.dll")]
    static extern NT_STATUS NtQuerySystemInformation(
      int SystemInformationClass,
      IntPtr SystemInformation,
      int SystemInformationLength,
      out int ReturnLength);

    const int SystemHandleInformation = 16;

    struct SYSTEM_EXTENDED_HANDLE
    {
      public IntPtr Object;
      public IntPtr UniqueProcessId;
      public IntPtr HandleValue;
      public uint GrantedAccess;
      public ushort CreatorBackTraceIndex;
      public ushort ObjectTypeIndex;
      public uint HandleAttributes;
      public uint Reserved;
    }

    const int SystemExtendedHandleInformation = 64;

    enum NT_STATUS : uint
    {
      SUCCESS = 0x00000000,
      BUFFER_OVERFLOW = 0x80000005,
      INFO_LENGTH_MISMATCH = 0xC0000004
    }

    static bool NT_SUCCESS(NT_STATUS status)
    {
      return ((uint)status & 0x80000000) == 0;
    }

    [DllImport("ntdll.dll")]
    static extern NT_STATUS NtDuplicateObject(
      IntPtr SourceProcessHandle,
      IntPtr SourceHandle,
      IntPtr TargetProcessHandle,
      out IntPtr TargetHandle,
      uint DesiredAccess, uint Attributes, uint Options);

    [DllImport("ntdll.dll")]
    static extern NT_STATUS NtQueryObject(
      IntPtr ObjectHandle,
      ObjectInformationClass ObjectInformationClass,
      IntPtr ObjectInformation,
      int ObjectInformationLength,
      out int returnLength);

    enum ObjectInformationClass : int {
      ObjectBasicInformation = 0,
      ObjectNameInformation = 1,
      ObjectTypeInformation = 2,
      ObjectAllTypesInformation = 3,
      ObjectHandleInformation = 4
    }

    [StructLayout(LayoutKind.Sequential)]
    struct OBJECT_NAME_INFORMATION
    { // Information Class 1
      public UNICODE_STRING Name;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct UNICODE_STRING
    {
      // NOTE:
      // UNICODE_STRING should be 8 byte on 32-bit and 16 byte on 64-bit
      // and reserved will condition it correctly.
      private IntPtr reserved;
      public IntPtr Buffer;

      public ushort Length
      {
        get { return (ushort)(reserved.ToInt64() & 0xffff); }
      }
      public ushort MaximumLength
      {
        get { return (ushort)(reserved.ToInt64() >> 16); }
      }

      public override string ToString()
      {
        if (Buffer == IntPtr.Zero)
          return "";
        return Marshal.PtrToStringUni(Buffer, Wcslen());
      }

      /// <summary>
      /// Calculate string length in C's wcslen compatible way.
      /// </summary>
      /// <returns>Length of the string.</returns>
      public int Wcslen()
      {
        unsafe
        {
          ushort* p = (ushort*)Buffer.ToPointer();
          for (ushort i = 0; i < Length; i++)
          {
            if (p[i] == 0)
              return i;
          }
          return Length;
        }
      }

    }

    [StructLayout(LayoutKind.Sequential)]
    struct GENERIC_MAPPING
    {
      public int GenericRead;
      public int GenericWrite;
      public int GenericExecute;
      public int GenericAll;
    }
  
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct OBJECT_TYPE_INFORMATION
    { // Information Class 2
      public UNICODE_STRING Name;
      public uint TotalNumberOfObjects;
      public uint TotalNumberOfHandles;
      public uint TotalPagedPoolUsage;
      public uint TotalNonPagedPoolUsage;
      public uint TotalNamePoolUsage;
      public uint TotalHandleTableUsage;
      public uint HighWaterNumberOfObjects;
      public uint HighWaterNumberOfHandles;
      public uint HighWaterPagedPoolUsage;
      public uint HighWaterNonPagedPoolUsage;
      public uint HighWaterNamePoolUsage;
      public uint HighWaterHandleTableUsage;
      public uint InvalidAttributes;
      public GENERIC_MAPPING GenericMapping;
      public uint ValidAccess;
      public byte SecurityRequired;
      public byte MaintainHandleCount;
      public ushort MaintainTypeList;
      public int PoolType;
      public int PagedPoolUsage;
      public int NonPagedPoolUsage;
    }
  }

  /// <summary>
  /// Extension methods for <see cref="IntPtr"/>.
  /// </summary>
  static class Helper
  {
    /// <summary>
    /// Offset the specified pointer.
    /// </summary>
    /// <param name="ptr">Base pointer.</param>
    /// <param name="offset">Offset in bytes.</param>
    /// <returns>New pointer.</returns>
    public static IntPtr Offset(this IntPtr ptr, int offset)
    {
      return new IntPtr(ptr.ToInt64() + offset);
    }

    /// <summary>
    /// Size of <see cref="IntPtr"/> value.
    /// </summary>
    public static readonly int PTR_SIZE = Marshal.SizeOf(typeof(IntPtr));
  }
}

XAMLでウィンドウの最大化・最小化・閉じるボタン群を制御する Attached Property

Attached Property の便利さに目覚めたので、最近はこういうのは全部、Attached Properyです。

<Window x:Class="AcpAcroHook.SettingsDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    ...
    xmlns:cbh="clr-namespace:CaptionBoxesHelper"
    cbh:CaptionBoxesHelper.ControlBoxes="Control">
...
</Window>

ControlBoxes はフラグになっているので、 Control, Maximize, Minimize をカンマ区切りで指定できます。なので、

cbh:CaptionBoxesHelper.ControlBoxes="Control,Minimize"

みたいな感じで指定できます。

以下、ソース:

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace CaptionBoxesHelper
{
  public static class CaptionBoxesHelper
  {
    public static readonly DependencyProperty ControlBoxesProperty =
      DependencyProperty.RegisterAttached(
        "ControlBoxes",
        typeof(ControlBoxes), typeof(CaptionBoxesHelper),
        new PropertyMetadata(ControlBoxes.None, controlBoxesChanged));

    public static void SetControlBoxes(DependencyObject dp, ControlBoxes value)
    {
      dp.SetValue(ControlBoxesProperty, value);
    }

    public static ControlBoxes GetControlBoxes(DependencyObject dp)
    {
      return (ControlBoxes)dp.GetValue(ControlBoxesProperty);
    }

    static void controlBoxesChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
      var window = o as Window;
      if (window == null)
        return;

      var cb = (ControlBoxes)e.NewValue;

      if (!window.IsLoaded)
        window.Loaded += (sender, ea) => updateStyleOfWindow(cb, window);
      else
        updateStyleOfWindow(cb, window);
    }

    static void updateStyleOfWindow(ControlBoxes cb, Window window)
    {
      var hwndSrc = PresentationSource.FromVisual(window) as HwndSource;
      var hwnd = hwndSrc.Handle;
      var flags = GetWindowLong(hwnd, GWL_STYLE);
      var newFlags = updateFlags(cb, flags);
      if (flags != newFlags)
        SetWindowLong(hwnd, GWL_STYLE, newFlags);
    }

    static uint updateFlags(ControlBoxes cb, uint flags)
    {
      if (cb.HasFlag(ControlBoxes.Minimize))
        flags |= WS_MINIMIZEBOX;
      else
        flags &= ~WS_MINIMIZEBOX;

      if (cb.HasFlag(ControlBoxes.Maximize))
        flags |= WS_MAXIMIZEBOX;
      else
        flags &= ~WS_MAXIMIZEBOX;

      if (cb.HasFlag(ControlBoxes.Control))
        flags |= WS_SYSMENU;
      else
        flags &= ~WS_SYSMENU;

      return flags;
    }

    const uint WS_SYSMENU = 0x00080000;
    const uint WS_MINIMIZEBOX = 0x00020000;
    const uint WS_MAXIMIZEBOX = 0x00010000;

    const int GWL_STYLE = -16;

    [DllImport("user32")]
    private static extern uint GetWindowLong(IntPtr hWnd, int index);

    [DllImport("user32")]
    private static extern uint SetWindowLong(IntPtr hWnd, int index, uint dwLong);
  }

  [Flags]
  public enum ControlBoxes
  {
    None = 0,
    Maximize = 1,
    Minimize = 2,
    Control = 4
  }
}