149
200

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# Win32API完全入門

Last updated at Posted at 2024-03-31

はじめに

対象とする読者について

本記事の対象者としては以下のような人を想定しています。

  • C#でこれからWin32APIを使ってみたい。
  • C言語のことがあまりよく分かっていない。
  • 今までは適当に使っていたので一度きちんと理解したい。

自分が同じような状況であったため、一から調べて整理してみました。自分が理解した順番や内容で記載することで、また、具体的な使用例によってできることの広さや動作を感じ取ってもらうことで、理解の助けになればと思っています。

但し、分かっている人からすると冗長な説明になっている部分や好ましくない内容、正確性に欠ける内容などもあると思います。実際に使用する場合はその点にご留意願います。

Win32APIについて

Windows API - Wikipedia

Microsoft Windowsのシステムコール用APIのこと。特に32ビットプロセッサで動作するWindows 95以降やWindows NTで利用できるものはWin32 APIと呼ばれる。
64ビットプロセッサ向けのWin64 APIも含める場合は「Windows API」という包括的な名称が正確だが、慣習的にWin32と言えばWin64も含んでいることがある

APIについて調べたい場合は、

C#からWin32APIを利用するにあたって

マネージドであるC#からアンマネージド(ネイティブ)であるWin32APIを利用する場合、

アンマネージ DLL 関数の処理 - .NET Framework | Microsoft Learn

プラットフォーム呼び出しは、マネージド コードがダイナミック リンク ライブラリ (DLL) に実装されたアンマネージド関数 (Windows API に含まれているものなど) を呼び出すことを可能にするサービスです。 これはエクスポートされた関数を見つけて呼び出し、必要に応じて相互運用の境界を越えて、その引数 (整数、文字列、配列、構造体、その他) をマーシャリングします。

型のマーシャリング - .NET | Microsoft Learn

マーシャリングとは、マネージド コードとネイティブ コードの間でやり取りする必要がある場合に型を変換するプロセスです。

プラットフォーム呼び出し (P/Invoke) - .NET | Microsoft Learn

P/Invoke は、アンマネージド ライブラリ内の構造体、コールバック、および関数をマネージド コードからアクセスできるようにするテクノロジです。

Microsoft Learnリンク一覧

Microsoft Learn 日本語⇔英語切り替え

Microsoft Learnを見ているとき、日本語と英語を切り替えながら読みたいときがあります。以下のブラウザ拡張機能を使うとページ右下に言語を切り替えるボタン(リンク)が追加されます。

  1. 「en-us と他の言語の切り替え」( Chrome/Edge🡭 Firefox🡭 )をインストールします。
  2. オプション画面を開いて「ja-jp」を選択してSaveします。
  3. 適当なMicrosoft Learnのページを開くとページ右下にen-usボタンが表示されます。
  4. ボタンをクリックするか、右クリックしてリンクを新しいタブで開くを選択して英語サイトを開きます。
  5. 英語サイトの場合でも同様です。ja-jpボタンが表示されるのでクリックして日本語サイトを表示します。

実際に使ってみる

SetWindowTextを使用して、起動したメモ帳のタイトルを変更します。

//C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe -target:winexe *.cs
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace win32api_sample
{
    //---------- Win32API用定義開始 ----------

    //Windows APIの定義
    internal static class NativeMethods
    {
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        public static extern int SetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string lpString);
    }

    //---------- Win32API用定義終了 ----------

    internal class Program
    {
        static void Main(string[] args)
        {
            using (Process process = new Process())
            {
                //メモ帳を起動
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.FileName = @"C:\Windows\System32\notepad.exe";
                process.Start();
                process.WaitForInputIdle(3000);

                //タイトルバーのテキスト変更
                NativeMethods.SetWindowText(process.MainWindowHandle, "変更後のメモ帳のタイトルバーのテキスト");
            }
        }
    }
}

SetWindowTextを使用するにあたり、C言語の記述を

BOOL SetWindowTextW(
  [in]           HWND    hWnd,
  [in, optional] LPCWSTR lpString
);

C#の記述に置き換えて定義しています。

using System.Runtime.InteropServices;

internal static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int SetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string lpString);
}
  1. System.Runtime.InteropServicesusingします。

    • System.Runtime.InteropServicesはCOM 相互運用機能とプラットフォーム呼び出しサービスをサポートするさまざまなメンバーを提供します。
  2. internalキーワードを付けたNativeMethodsクラス内に定義します。

  3. DllImportを使用してDLLと関数を指定します。
    今回の場合は、SetWindowTextページの要件(最下部)の箇所を見てuser32.dllであることを確認します。

  4. 関数名を記載します(末尾のA,W参照)。

  5. 関数をstaticextern修飾子でマークします。
    アンマネージ DLL 関数の処理 - .NET Framework | Microsoft Learn

  6. 引数を設定します。

関数を定義する手順

DllImport属性の設定

以下の例のCharSet = CharSet.Unicodeの部分。とりあえず使用したいだけであれば、ネット上の使用例をそのまま記載すればいいと思います。詳細はDllImportを参照。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int SetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string lpString);

関数名の設定

末尾のA,W

関数名にはSetWindowTextASetWindowTextWのように末尾がA,Wで終わるものがあります。

A : ANSI版(つまり古い)
W : Unicode版

  • FunctionAFunctionWがある場合は末尾からA,Wを取ったFunctionを使用します。
  • 実際にはFunctionは存在しません。マクロ定義により、プラットフォームに応じてFunctionAFunctionWのいずれかが呼び出されます。
  • 使用する場合はUnicodeであることを明示します。
  • パフォーマンスを少しでも上げたい場合は、ExactSpelling = trueと組み合わせて、直接FunctionWを指定します。(CharSet/ExactSpelling参照)

例)SetWindowTextASetWindowTextWの場合

  1. 末尾からA,Wを取ったSetWindowTextを使用します。
  2. Unicodeを使用することを明示します。CharSet = CharSet.Unicode
internal static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int SetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string lpString);
}

参考URL

引数の設定

例えばSetWindowTextWの場合、

BOOL SetWindowTextW(
  [in]           HWND    hWnd,
  [in, optional] LPCWSTR lpString
);

第1引数であるHWND hWndはC#ではIntPtr hWndのように記載を変える必要があります。

  1. 変数名についてはそのまま流用します。
  2. 型については対応するC#での記載に変更します。詳細はを参照。

定数の設定

関数で使用する定数(ごみ箱の中身をクリアSHERB_NOCONFIRMATIONなど)を定義する必要があります。

定数値を調べたい場合は、

  1. Microsoft Learnで検索して確認します。
    1. HWND_TOPMOST -1のように直接記載があるものはその値を使用します。
    2. 列挙型(enum)の場合は、明示がない限りゼロ始まりです。例えばDWMWINDOWATTRIBUTEであればDWMWA_NCRENDERING_ENABLEDが0、DWMWA_NCRENDERING_POLICYが1になります。DWMWA_CAPTION_COLORDWMWA_WINDOW_CORNER_PREFERENCEが33なので35になります。
    typedef enum DWMWINDOWATTRIBUTE {
        DWMWA_NCRENDERING_ENABLED,
        DWMWA_NCRENDERING_POLICY,
        DWMWA_TRANSITIONS_FORCEDISABLED,
        //...
        DWMWA_WINDOW_CORNER_PREFERENCE = 33,
        DWMWA_BORDER_COLOR,
        DWMWA_CAPTION_COLOR,
        //...
    } ;
    
  2. 見つからない場合は、MinGW-w64を開きます。
  3. MingW-W64-buildsをdownloadします。
  4. includeフォルダ(mingw32\i686-w64-mingw32\include)のみ解凍します。
  5. 解凍先フォルダをVSCodeで開いて調べたい値を検索します。

整数など / ブーリアン / 文字列
ポインタ、配列 / コールバック関数へのポインタ / 任意の型へのポインタ
構造体、共用体

型の調べ方

  1. 型の定義(範囲)を確認します。
    Windows Data Types (BaseTsd.h) - Win32 apps | Microsoft Learn
    例えば、DWORDであれば、

    1. DWORDを検索します。
    2. typedef unsigned long DWORD;のためunsigned longと同義。
    3. Data Type Ranges | Microsoft Learnを確認します。
    4. unsigned longは4バイトで 0 ~ 4,294,967,295

    例えば、WPARAMであれば、

    1. WPARAMを検索します。
    2. typedef UINT_PTR WPARAM;のためUINT_PTRと同義。
    3. UINT_PTRを検索します。
    4. 32ビットか64ビットかによって定義が変わります。
      34bitであればunsigned intなので0 ~ 4,294,967,295
      64bitであればunsigned __int64なので0 ~ 18,446,744,073,709,551,615
  2. 対応するC#の型を確認します。
    整数数値型 - C# リファレンス - C# | Microsoft Learn
    プラットフォーム呼び出しのデータ型
    例えば、DWORDであれば、

    1. 0 ~ 4,294,967,295なので、uint

    例えば、WPARAMであれば、

    1. 34bitであれば0 ~ 4,294,967,295なのでuint、64bitであれば0 ~ 18,446,744,073,709,551,615なのでulong
    2. つまりポインターの有効桁数の型なのでUIntPtr

整数など

データ型 C言語 C# .NET サイズ 範囲
BYTE
UCHAR
UINT8
unsigned char byte System.Byte 1(8) 0 ~ 255
CHAR char sbyte System.SByte 1(8) -128 ~ 127
INT8 signed char sbyte System.SByte 1(8) -128 ~ 127
SHORT short short System.Int16 2(16) -32,768 ~ 32,767
INT16 signed short short System.Int16 2(16) -32,768 ~ 32,767
ATOM
WORD
USHORT
UINT16
unsigned short ushort System.UInt16 2(16) 0 ~ 65,535
INT int int System.Int32 4(32) -2,147,483,648
~ 2,147,483,647
INT32
LONG32
signed int int System.Int32 4(32) -2,147,483,648
~ 2,147,483,647
LONG long int System.Int32 4(32) -2,147,483,648
~ 2,147,483,647
UINT
UINT32
DWORD32
ULONG32
unsigned int uint System.UInt32 4(32) 0 ~ 4,294,967,295
DWORD
ULONG
unsigned long uint System.UInt32 4(32) 0 ~ 4,294,967,295

intは処理系依存(基本データ型)などを意識する必要はなく4バイト固定です。

次のデータ型は常にポインターのサイズ

データ型 C言語 C# .NET サイズ
INT_PTR int or
__int64
nint IntPtr 4(32) or
8(64)
LPARAM
LRESULT
LONG_PTR
long or
__int64
nint IntPtr 4(32) or
8(64)
WPARAM
UINT_PTR
unsigned int or
unsigned __int64
nuint UIntPtr 4(32) or
8(64)
SIZE_T
ULONG_PTR
unsigned long or
unsigned __int64
nuint UIntPtr 4(32) or
8(64)

ブーリアン

データ型 C言語 C# .NET サイズ
BOOLEAN unsigned char byte or
[MarshalAs(UnmanagedType.U1)] bool
System.Byte 1(8)
BOOL int int or bool or
[MarshalAs(UnmanagedType.Bool)] bool
System.Int32 4(32)

ブール値と相互運用機能によると、構造体内のboolとP/Invokeのパラメータのboolの既定は4バイト整数値になります。但し、移植される可能性がある規則(非移植規則)の中にCA1414があるため、BOOLEANBOOLいずれであってもMarshalAsで明示するほうがよいと思います。

//1バイトBOOLEAN
public byte b;
[MarshalAs(UnmanagedType.U1)] public bool b;//明示してboolで使用

//4バイトBOOL
//既定は4バイトBOOL
public bool b;

//明示せずintで使用
public int b;

//明示して使用
[MarshalAs(UnmanagedType.Bool)] public bool b;

//明示して使用(戻り値の例)
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool EmptyClipboard();

参考URL

文字列

データ型         C言語 文字列           C#
PSTR
LPSTR
char* ANSI GetWindowTextAのようなANSI版で使われる型。Unicode版がある場合はUnicode版の関数を使用します。
末尾のA,Wを参照)
GetProcAddressのようにANSI文字列のみを受け付ける関数があります。その場合は以下の「ANSI文字列の使用例」を参照
PCSTR const char* ANSI 同上
LPCSTR __nullterminated const char* ANSI 同上
PWSTR
LPWSTR
wchar_t* Unicode 以下の「Unicode文字列の使用例」を参照
PCWSTR
LPCWSTR
const wchar_t* Unicode 同上
LPTSTR ANSI
Unicode
ANSIの場合 : LPSTRと同義
Unicodeの場合 : LPWSTRと同義
つまりLPWSTRとみなしてよい
LPCTSTR ANSI
Unicode
ANSIの場合 : LPCSTRと同義
Unicodeの場合 : LPCWSTRと同義
つまりLPCWSTRとみなしてよい
WCHAR wchar_t Unicode char(System.Char)

ANSI文字列の使用例

以下の「Unicode文字列の使用例」において、

  1. CharSet = CharSet.UnicodeCharSet = CharSet.Ansi に変更
  2. [MarshalAs(UnmanagedType.LPWStr)][MarshalAs(UnmanagedType.LPStr)] に変更

Unicode文字列の使用例

例)SetWindowTextを使ってウィンドウのタイトル バーのテキストを変更

stringを利用して文字列を渡します。

using System;
using System.Runtime.InteropServices;

internal static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int SetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string lpString);
}

private void SetText(IntPtr hWnd, string str)
{
    NativeMethods.SetWindowText(hWnd, str);
}

例)GetWindowTextを使ってウィンドウのタイトル バーのテキストを取得

以下のURLによるとStringBuilderは効率が悪く、問題が起きる可能性があるため、推奨は手段1のArrayPoolからのchar[]の利用のようです。効率が必須であれば直接C言語で記載すると思いますので、どちらを使用するかは問題が起きる可能性を考慮して決めればよいと思います。

ネイティブ相互運用性のベスト プラクティス - .NET | Microsoft Learn

手段1 char[]を利用して文字列を受け取ります。

using System;
using System.Buffers;
using System.Runtime.InteropServices;

internal static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int GetWindowText(IntPtr hWnd, [Out] char[] lpString, int nMaxCount);
}

private string GetText(IntPtr hWnd)
{
    char[] buffer = ArrayPool<char>.Shared.Rent(256 + 1);
    int length = NativeMethods.GetWindowText(hWnd, buffer, buffer.Length);
    return new string(buffer, 0, length);
}

手段2 StringBuilderを利用して文字列を受け取ります。

using System;
using System.Text;
using System.Runtime.InteropServices;

internal static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int GetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpString, int nMaxCount);
}

private string GetText(IntPtr hWnd)
{
    var buffer = new StringBuilder(256);
    NativeMethods.GetWindowText(hWnd, buffer, buffer.Capacity);
    return buffer.ToString();
}

参考URL

ポインタ、配列

  • ほとんどのポインター型名は、プレフィックスPまたはLPで始まります。これらは値渡しではなく、参照渡しとなります。入出力で使う場合はref、出力を受け取る場合はoutとします。
  • 参照型の配列は、既定では In パラメーターとして渡されます。呼び出し元が結果を受け取るためには、明示的にInAttributeOutAttributeを適用する必要があります。
  • PWSTRPointer to Wide STRing)などの文字列の場合は文字列を参照して設定します。
説明 C言語の例 C#の例
LPDWORD
(LP+DWORD)
DWORDへのポインタ LPDWORD word ref uint word(入出力)
out uint word(出力)
LPRECT
(LP+RECT)
RECT構造体へのポインタ LPRECT lpRect out RECT lpRect
整数の配列
整数の多次元配列
文字列の配列
構造体の配列
int* arr
int arr[][COL_DIM]
char** arr
INPUT* arr
[In, Out] int[] arr
[In, Out] int[,] arr
[In, Out] string[] arr
[In, Out] INPUT[] arr
整数の配列(参照)
サイズ変更可能
int** ppArray ref IntPtr array

参考URL

コールバック関数へのポインタ

例えばEnumWindowsの場合、

C言語

BOOL EnumWindows(
  [in] WNDENUMPROC lpEnumFunc,
  [in] LPARAM      lParam
);

の第1引数は「コールバック関数へのポインタ」になります。端的に言えば引数が関数ということです。このような場合、C#ではデリゲートを使用します。デリゲートはメソッドへの参照を表す型ですので、C言語でいう関数ポインタ、関数のアドレス渡しのようなものになります。

C#

internal static class NativeMethods
{
    //関数を渡すためにデリゲートを使用
    [return: MarshalAs(UnmanagedType.Bool)]
    public delegate bool EnumWC(IntPtr hWnd, IntPtr lParam);

    [DllImport("user32.dll")]
    public static extern int EnumWindows(EnumWC lpEnumFunc, IntPtr lParam);
}

参考URL

任意の型へのポインタ

ウィンドウハンドルHWNDについて型の調べ方で確認すると、
typedef HANDLE HWND;typedef PVOID HANDLE;typedef void *PVOID;
つまり「任意の型へのポインタ」となります。ポインタにアドレスを確保するためには、アドレスのサイズの容量が必要です。アドレスのサイズを確保するためIntPtrを使用します。

データ型 C言語 C# .NET サイズ
HWND
HANDLE
HINSTANCE
PVOID
void * nint IntPtr 4(32) or 8(64)

また、nullの代わりにIntPtr.Zeroを使用します。

//nullの代わりにIntPtr.Zeroを使用する例
if(NativeMethods.GetWindow(hWnd, GW_OWNER) == IntPtr.Zero)
{
    //オーナーウィンドウでない場合の処理
}

参考URL

構造体、共用体

構造体

StructLayoutAttributeLayoutKindSequentialに設定します。これにより各メンバーが出現する順番でメモリ内に順次配列されます。メンバーを定義する場合はC言語と同じ順番とします。

C言語

typedef struct tagRECT {
    LONG left;
    LONG top;
    LONG right;
    LONG bottom;
} RECT, *PRECT, *NPRECT, *LPRECT;

C#

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

共用体

構造体として定義したうえで、StructLayoutAttributeLayoutKindExplicitに設定し、FieldOffsetAttributeを使用することでフィールドの物理的な位置を指定します。共用体とするためにFieldOffset(0)としています。

C言語

union MYUNION
{
    int number;
    double d;
}

C#

[StructLayout(LayoutKind.Explicit)]
public struct MyUnion
{
    [FieldOffset(0)]
    public int i;
    [FieldOffset(0)]
    public double d;
}

DllImport属性

ネイティブ相互運用性のベスト プラクティス - .NET | Microsoft Learn

設定 デフォルト 推奨 説明
PreserveSig true 既定値を維持 戻り値としてHRESULTを持つ関数の場合、
true : HRESULTが返ります。
false : 例外発生(戻り値は null)
SetLastError false API によって異なります Marshal.GetLastWin32Errorを使用してエラー値を取得する場合は、trueに設定
CharSet - 文字を使うときは明示 基本今のWindowsではCharSet.Unicode
末尾のA,W参照)
GetProcAddressのようにANSI文字列のみを受け付ける関数もあります。
ExactSpelling false true 以下をしないので少しパフォーマンスが上昇
CharSet.Ansiの場合FunctionAを探します。
CharSet.Unicodeの場合FunctionWを探します。
末尾のA,W参照)

PreserveSig

DllImportAttribute.PreserveSig フィールド (System.Runtime.InteropServices) | Microsoft Learn

戻り値としてHRESULTを持つ関数の受け取り方を設定します。

true(既定) : 失敗した場合、HRESULT(int)として受け取ります。

internal static class NativeMethods
{
    [DllImport("shlwapi.dll")]
    public static extern int SHAutoComplete(IntPtr hwndEdit, uint dwFlags);
}
private void ForceError(object sender, EventArgs e)
{
    int HRESULT = NativeMethods.SHAutoComplete(new IntPtr(0), 0);
    MessageBox.Show(HRESULT.ToString());
}

false : 失敗した場合、例外を発生させます。

internal static class NativeMethods
{
    [DllImport("shlwapi.dll", PreserveSig = false)]//失敗で例外を発生させる
    public static extern void SHAutoComplete(IntPtr hwndEdit, uint dwFlags);
}

private void ForceError(object sender, EventArgs e)
{
    try
    {
        NativeMethods.SHAutoComplete(new IntPtr(0), 0);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

SetLastError

DllImportAttribute.SetLastError フィールド (System.Runtime.InteropServices) | Microsoft Learn

エラーを取得する必要がある場合は、誤って上書きされないようにtrueに設定し、他の呼び出しを行う前にエラーを取得します。

internal static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int SetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string lpString);

    [DllImport("kernel32.dll")]
    public static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr Arguments);
}
private void ForceError()
{
    int ret = NativeMethods.SetWindowText(IntPtr.Zero, "");
    if (ret == 0)
    {
        //エラーコードを取得
        int errCode = Marshal.GetLastWin32Error();

        //エラーコードの表示
        MessageBox.Show($"エラーコード:{errCode:X8}");

        //エラーメッセージを取得
        StringBuilder message = new StringBuilder(256);
        NativeMethods.FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, (uint)errCode, 0, message, message.Capacity, IntPtr.Zero);

        //エラーメッセージを表示
        MessageBox.Show($"エラーメッセージ:{message}");
    }
}

CharSet/ExactSpelling

DllImportAttribute.CharSet フィールド (System.Runtime.InteropServices) | Microsoft Learn
DllImportAttribute.ExactSpelling フィールド (System.Runtime.InteropServices) | Microsoft Learn

引数に文字列がある場合は、AnsiまたはUnicodeを明示します。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int SetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string lpString);

上記の場合は、Unicode指定ですので末尾がWSetWindowTextWが呼ばれますが、直接SetWindowTextWを指定することもできます。この場合は末尾Wの関数を探索する必要はないのでExactSpelling = trueとします。

[DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern int SetWindowTextW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string lpString);

各種関数

GetWindowText

GetWindowTextW 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウタイトルを取得します。使用例はマウスカーソル下のウィンドウを参照。

using System.Buffers;
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowText(IntPtr hWnd, [Out] char[] lpString, int nMaxCount);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpString, int nMaxCount);

GetWindowTextLength

GetWindowTextLengthW 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウタイトルの文字数を取得します。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowTextLength(IntPtr hWnd);

GetClassName

GetClassNameW 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウのクラス名を取得します。使用例はマウスカーソル下のウィンドウを参照。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetClassName(IntPtr hWnd, [Out] char[] lpClassName, int nMaxCount);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetClassName(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpClassName, int nMaxCount);

GetCursorPos

GetCursorPos 関数 (winuser.h) - Win32 apps | Microsoft Learn

マウス カーソルの位置を画面座標で取得します。使用例はマウスカーソル下のウィンドウを参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorPos(out POINT lpPoint);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int x;
    public int y;
}

WindowFromPoint

WindowFromPoint 関数 (winuser.h) - Win32 apps | Microsoft Learn

指定したポイントを含むウィンドウへのハンドルを取得します。使用例はマウスカーソル下のウィンドウを参照。
引数のPOINT構造体についてはGetCursorPosを参照。

[DllImport("user32.dll")]
public static extern IntPtr WindowFromPoint(POINT Point);

GetWindowInfo

GetWindowInfo 関数 (winuser.h) - Win32 apps | Microsoft Learn

指定したウィンドウに関する情報を取得します。使用例はウィンドウ情報の取得を参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public extern static bool GetWindowInfo(IntPtr hWnd, ref WINDOWINFO pwi);
//WINDOWINFO構造体
[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWINFO
{
    public int cbSize;
    public RECT rcWindow;
    public RECT rcClient;
    public uint dwStyle;
    public uint dwExStyle;
    public uint dwWindowStatus;
    public uint cxWindowBorders;
    public uint cyWindowBorders;
    public ushort atomWindowType;
    public ushort wCreatorVersion;
}

[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

GetForegroundWindow

GetForegroundWindow 関数 (winuser.h) - Win32 apps | Microsoft Learn

フォアグラウンドウィンドウへのハンドルを取得します。使用例はウィンドウ領域の取得を参照。

[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();

GetWindowRect

GetWindowRect 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウの領域を取得します。使用例はウィンドウ領域の取得を参照。

C言語

BOOL GetWindowRect(
  [in]  HWND   hWnd,
  [out] LPRECT lpRect //LP始まり。RECTへのポインタ
);

C#

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

GetClientRect

GetClientRect 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウのクライアント領域の座標を取得します。使用例はウィンドウ領域の取得を参照。
引数のRECT構造体についてはGetWindowRectを参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

DwmGetWindowAttribute

DwmGetWindowAttribute 関数 (dwmapi.h) - Win32 apps | Microsoft Learn

ウィンドウに適用されている指定されたデスクトップ ウィンドウ マネージャー (DWM) 属性の現在の値を取得します。使用例はウィンドウ領域の取得を参照。

dwAttribute(第2引数) : 取得したい値を示すフラグ(DWMWINDOWATTRIBUTE列挙値)
pvAttribute(第3引数) : 取得値を受け取るポインタ

例えばウィンドウ可視領域(拡張フレーム境界の四角形)を取得したい場合は、

DWMWA_EXTENDED_FRAME_BOUNDS
DwmGetWindowAttribute で を使用します。 画面空間内の拡張フレーム境界の四角形を取得します。 取得された値は RECT 型です。

のためdwAttributeDWMWA_EXTENDED_FRAME_BOUNDSを設定しpvAttributeRECT型へのポインタを設定します。

//第2引数をDWMWA_EXTENDED_FRAME_BOUNDSとする場合
[DllImport("dwmapi.dll")]
public static extern int DwmGetWindowAttribute(IntPtr hWnd, uint dwAttribute, out RECT pvAttribute, int cbAttribute);

DwmSetWindowAttribute

DwmSetWindowAttribute 関数 (dwmapi.h) - Win32 apps | Microsoft Learn

ウィンドウに適用されている指定されたデスクトップ ウィンドウ マネージャー (DWM) 属性の現在の値を設定します。使用例はタイトルバーの色を変更を参照。

//第2引数をDWMWA_CAPTION_COLORとする場合
[DllImport("dwmapi.dll")]
public static extern int DwmSetWindowAttribute(IntPtr hWnd, uint dwAttribute, ref uint pvAttribute, int cbAttribute);

GetWindow

GetWindow 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウとのリレーションシップ (Z オーダー または所有者) を持つウィンドウへのハンドルを取得します。使用例は全ウィンドウの情報を表示を参照。

[DllImport("user32.dll")]
public extern static IntPtr GetWindow(IntPtr hWnd, uint uCmd);

IsWindowVisible

IsWindowVisible 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウの表示状態を取得します。使用例は全ウィンドウの情報を表示を参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public extern static bool IsWindowVisible(IntPtr hWnd);

GetWindowThreadProcessId

GetWindowThreadProcessId 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウを作成したスレッドIDとプロセスIDを取得します。使用例は全ウィンドウの情報を表示を参照。

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

GetModuleFileNameEx

GetModuleFileNameExW 関数 (psapi.h) - Win32 apps | Microsoft Learn

指定したモジュールを含むファイルのフルパスを取得します。使用例は全ウィンドウの情報を表示を参照。

[DllImport("Psapi.dll", CharSet = CharSet.Unicode)]
public static extern uint GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpFilename, uint nSize);

OpenProcess

OpenProcess 関数 (processthreadsapi.h) - Win32 apps | Microsoft Learn

プロセスオブジェクトを開きます。使用例は全ウィンドウの情報を表示を参照。

[DllImport("Kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);

CloseHandle

CloseHandle 関数 (handleapi.h) - Win32 apps | Microsoft Learn

オブジェクトハンドルを閉じます。使用例は全ウィンドウの情報を表示を参照。

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

EnumWindows

EnumWindows 関数 (winuser.h) - Win32 apps | Microsoft Learn

コールバック関数に渡すことによって、画面上のすべての最上位ウィンドウを列挙します。使用例は全ウィンドウの情報を表示を参照。

[return: MarshalAs(UnmanagedType.Bool)]
public delegate bool EnumWC(IntPtr hWnd, IntPtr lParam);

[DllImport("user32.dll")]
public static extern int EnumWindows(EnumWC lpEnumFunc, IntPtr lParam);

GetAncestor

GetAncestor 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウの親子階層を親側方向に遡ってウィンドウハンドル取得します。使用例は全ての子ウィンドウを表示を参照。

[DllImport("user32.dll")]
public extern static IntPtr GetAncestor(IntPtr hWnd, uint gaFlags);

GetParent

GetParent 関数 (winuser.h) - Win32 apps | Microsoft Learn

親ウィンドウハンドルを取得します。使用例は全ての子ウィンドウを表示を参照。

[DllImport("user32.dll")]
public extern static IntPtr GetParent(IntPtr hWnd);

EnumChildWindows

EnumChildWindows 関数 (winuser.h) - Win32 apps | Microsoft Learn

子ウィンドウを列挙します。使用例は全ての子ウィンドウを表示を参照。

[return: MarshalAs(UnmanagedType.Bool)]
public delegate bool EnumCW(IntPtr hWnd, IntPtr lParam);

[DllImport("user32.dll")]
public static extern int EnumChildWindows(IntPtr hWndParent, EnumCW lpEnumFunc, IntPtr lParam);

SetWindowPos

SetWindowPos 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウのサイズ、位置、Z の順序を変更します。使用例はウィンドウの最前面化、サイズや位置の変更を参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

FindWindowEx

FindWindowExW 関数 (winuser.h) - Win32 apps | Microsoft Learn

指定したクラス名とウィンドウ名と一致するウィンドウへのハンドルを取得します。 使用例は非アクティブウィンドウにキーを送信を参照。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

PostMessage

PostMessageW 関数 (winuser.h) - Win32 apps | Microsoft Learn

指定されたウィンドウを作成したスレッドに関連付けられたメッセージキューにメッセージを送ります。スレッドがメッセージを処理するのを待たずに戻ります(非同期)。使用例は非アクティブウィンドウにキーを送信を参照。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam);

MapVirtualKey

MapVirtualKeyW 関数 (winuser.h) - Win32 apps | Microsoft Learn

仮想キーコードをスキャンコードまたは文字値に変換、またはスキャンコードを仮想キーコードに変換します。使用例は非アクティブウィンドウにキーを送信を参照。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern uint MapVirtualKey(uint uCode, uint uMapType);

SendInput

SendInput 関数 (winuser.h) - Win32 apps | Microsoft Learn

キーストローク、マウスの動き、ボタンのクリックを合成します。使用例は非アクティブウィンドウにキーを送信を参照。

C言語

UINT SendInput(
  [in] UINT    cInputs,
  [in] LPINPUT pInputs,
  [in] int     cbSize
);

C#

[DllImport("user32.dll")]
public static extern uint SendInput(uint cInputs, [In, Out] INPUT[] pInputs, int cbSize);

構造体と共用体を使ってINPUT構造体を定義します。

[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
    public uint type;
    public DUMMYUNIONNAME input;
}

[StructLayout(LayoutKind.Explicit)]
public struct DUMMYUNIONNAME
{
    [FieldOffset(0)]
    public MOUSEINPUT mi;
    [FieldOffset(0)]
    public KEYBDINPUT ki;
    [FieldOffset(0)]
    public HARDWAREINPUT hi;
}

[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
    public int dx;
    public int dy;
    public uint mouseData;
    public uint dwFlags;
    public uint time;
    public UIntPtr dwExtraInfo;
}

[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT
{
    public ushort wVk;
    public ushort wScan;
    public uint dwFlags;
    public uint time;
    public UIntPtr dwExtraInfo;
}

[StructLayout(LayoutKind.Sequential)]
public struct HARDWAREINPUT
{
    public uint uMsg;
    public ushort wParamL;
    public ushort wParamH;
}
private const uint INPUT_MOUSE = 0;
private const uint INPUT_KEYBOARD = 1;
private const uint INPUT_HARDWARE = 2;

GetMessageExtraInfo

GetMessageExtraInfo 関数 (winuser.h) - Win32 apps | Microsoft Learn

現在のスレッドの追加のメッセージ情報を取得します。使用例は非アクティブウィンドウにキーを送信を参照。

[DllImport("user32.dll")]
public static extern IntPtr GetMessageExtraInfo();

keybd_event

keybd_event関数 (winuser.h) - Win32 apps | Microsoft Learn

キーストロークを合成します。現在はSendInputに置き換わっています。使用例は非アクティブウィンドウにキーを送信を参照。

[DllImport("user32.dll")]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);

IsWindow

IsWindow 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウが存在するかどうか。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public extern static bool IsWindow(IntPtr hWnd);

IsWindowEnabled

IsWindowEnabled 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウでマウス入力およびキーボード入力が有効かどうか。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public extern static bool IsWindowEnabled(IntPtr hWnd);

IsWindowUnicode

IsWindowUnicode 関数 (winuser.h) - Win32 apps | Microsoft Learn

指定したウィンドウがネイティブ Unicode ウィンドウかどうか。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public extern static bool IsWindowUnicode(IntPtr hWnd);

IsIconic

IsIconic 関数 (winuser.h) - Win32 apps | Microsoft Learn

指定したウィンドウが最小化状態かどうか。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public extern static bool IsIconic(IntPtr hWnd);

IsZoomed

IsZoomed 関数 (winuser.h) - Win32 apps | Microsoft Learn

指定したウィンドウが最大化状態かどうか。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public extern static bool IsZoomed(IntPtr hWnd);

ShowCursor

ShowCursor 関数 (winuser.h) - Win32 apps | Microsoft Learn

マウスカーソルを表示または非表示にします。使用例はマウスカーソルの表示切替を参照。

[DllImport("user32.dll")]
public static extern int ShowCursor([MarshalAs(UnmanagedType.Bool)] bool bShow);

GetDC

GetDC 関数 (winuser.h) - Win32 apps | Microsoft Learn

デバイス コンテキスを取得します。使用例はマウスカーソル位置の色を取得を参照。

[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);

GetPixel

GetPixel 関数 (wingdi.h) - Win32 apps | Microsoft Learn

指定した座標にあるピクセルの赤、緑、青 (RGB)を取得します。使用例はマウスカーソル位置の色を取得を参照。

[DllImport("Gdi32.dll")]
public static extern uint GetPixel(IntPtr hdc, int x, int y);

ReleaseDC

ReleaseDC 関数 (winuser.h) - Win32 apps | Microsoft Learn

デバイス コンテキストを解放します。使用例はマウスカーソル位置の色を取得を参照。

[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hdc);

SetProcessDPIAware

SetProcessDPIAware 関数 (winuser.h) - Win32 apps | Microsoft Learn

プロセスの既定のDPIをシステムDPI対応に設定します。使用例はマウスカーソル位置の色を取得を参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetProcessDPIAware();

GetCursorInfo

GetCursorInfo 関数 (winuser.h) - Win32 apps | Microsoft Learn

グローバル カーソルに関する情報を取得します。使用例はスクリーンキャプチャを参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetCursorInfo(ref CURSORINFO pci);
[StructLayout(LayoutKind.Sequential)]
public struct CURSORINFO
{
    public uint cbSize;
    public uint flags;
    public IntPtr hCursor;
    public POINT ptScreenPos;
}

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int x;
    public int y;
}

OpenClipboard

OpenClipboard 関数 (winuser.h) - Win32 apps | Microsoft Learn

クリップボードを開きます。使用例はクリップボードの中身をクリアを参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool OpenClipboard(IntPtr hWnd);

EmptyClipboard

EmptyClipboard 関数 (winuser.h) - Win32 apps | Microsoft Learn

クリップボードを空にします。使用例はクリップボードの中身をクリアを参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EmptyClipboard();

CloseClipboard

CloseClipboard 関数 (winuser.h) - Win32 apps | Microsoft Learn

クリップボードを閉じます。使用例はクリップボードの中身をクリアを参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseClipboard();

SHEmptyRecycleBin

SHEmptyRecycleBinW 関数 (shellapi.h) - Win32 apps | Microsoft Learn

指定したドライブのごみ箱を空にします。使用例はごみ箱の中身をクリアを参照。

[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
public static extern uint SHEmptyRecycleBin(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), In] string pszRootPath, uint dwFlags);

LoadLibrary

LoadLibraryW 関数 (libloaderapi.h) - Win32 apps | Microsoft Learn

指定したモジュールを呼び出し元プロセスのアドレス空間に読み込みます。使用例はDLLを動的に呼び出すを参照。

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPWStr)] string lpLibFileName);

GetProcAddress

GetProcAddress 関数 (libloaderapi.h) - Win32 apps | Microsoft Learn

エクスポートされた関数または変数のアドレスをDLLから取得します。引数の型はANSI文字列LPCSTRになります。使用例はDLLを動的に呼び出すを参照。

[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

FreeLibrary

FreeLibrary 関数 (libloaderapi.h) - Win32 apps | Microsoft Learn

DLL解放し、必要に応じて参照カウントをデクリメントします。使用例はDLLを動的に呼び出すを参照。

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FreeLibrary(IntPtr hLibModule);

SetWindowsHookEx

SetWindowsHookExW 関数 (winuser.h) - Win32 apps | Microsoft Learn

フック チェーンの先頭にフック プロシージャをインストールします。使用例はフックを参照。

public delegate IntPtr SetWindowsHookExCallback(int nCode, UIntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SetWindowsHookEx(int idHook, SetWindowsHookExCallback lpfn, IntPtr hmod, uint dwThreadId);

UnhookWindowsHookEx

UnhookWindowsHookEx 関数 (winuser.h) - Win32 apps | Microsoft Learn

SetWindowsHookEx関数によってフック チェーンにインストールされているフック プロシージャを削除します。使用例はフックを参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);

CallNextHookEx

CallNextHookEx 関数 (winuser.h) - Win32 apps | Microsoft Learn

フック情報を現在のフック チェーンの次のフック プロシージャに渡します。使用例はフックを参照。

[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);

GetCurrentThreadId

GetCurrentThreadId 関数 (processthreadsapi.h) - Win32 apps | Microsoft Learn

呼び出し元スレッドのスレッド識別子を取得します。使用例はフックを参照。

[DllImport("Kernel32.dll")]
public static extern uint GetCurrentThreadId();

GetWindowLong

GetWindowLongW 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウに関する情報を取得します。使用例はフックを参照。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

GetWindowLongPtr

GetWindowLongPtrW 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウに関する情報を取得します。使用例はフックを参照。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);

GetSystemMenu

GetSystemMenu 関数 (winuser.h) - Win32 apps | Microsoft Learn

ウィンドウメニュー(システム メニュー、コントロール メニュー)のコピーへのハンドルを取得します。使用例はウィンドウメニューに項目を追加を参照。

[DllImport("user32.dll")]
public static extern IntPtr GetSystemMenu(IntPtr hWnd, [MarshalAs(UnmanagedType.Bool)] bool bRevert);

InsertMenuItem

InsertMenuItemW 関数 (winuser.h) - Win32 apps | Microsoft Learn

メニュー内の指定した位置に新しいメニュー項目を挿入します。使用例はウィンドウメニューに項目を追加を参照。

[DllImport("user32.dll", ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool InsertMenuItemW(IntPtr hmenu, uint item, [MarshalAs(UnmanagedType.Bool)] bool fByPosition, ref MENUITEMINFO lpmi);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct MENUITEMINFO
{
    public uint cbSize;
    public uint fMask;
    public uint fType;
    public uint fState;
    public uint wID;
    public IntPtr hSubMenu;
    public IntPtr hbmpChecked;
    public IntPtr hbmpUnchecked;
    public UIntPtr dwItemData;
    public string dwTypeData;
    public uint cch;
    public IntPtr hbmpItem;
}

SetMenuItemInfo

SetMenuItemInfoW 関数 (winuser.h) - Win32 apps | Microsoft Learn

メニュー項目の設定を変更します。使用例はウィンドウメニューに項目を追加を参照。

[DllImport("user32.dll", ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetMenuItemInfoW(IntPtr hmenu, uint item, [MarshalAs(UnmanagedType.Bool)] bool fByPositon, ref MENUITEMINFO lpmii);

AddClipboardFormatListener

AddClipboardFormatListener 関数 (winuser.h) - Win32 apps | Microsoft Learn

指定したウィンドウ ハンドルをクリップボード形式のリスナーリストに配置します。使用例はクリップボードの監視を参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AddClipboardFormatListener(IntPtr hwnd);

RemoveClipboardFormatListener

RemoveClipboardFormatListener 関数 (winuser.h) - Win32 apps | Microsoft Learn

指定したウィンドウ ハンドルをクリップボード形式のリスナーリストから削除します。使用例はクリップボードの監視を参照。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RemoveClipboardFormatListener(IntPtr hwnd);

CountClipboardFormats

CountClipboardFormats 関数 (winuser.h) - Win32 apps | Microsoft Learn

クリップボードに現在にあるデータ形式の数を取得します。使用例はクリップボードの監視を参照。

[DllImport("user32.dll")]
public static extern int CountClipboardFormats();

EnumClipboardFormats

EnumClipboardFormats 関数 (winuser.h) - Win32 apps | Microsoft Learn

クリップボードで現在使用できるデータ形式を取得します。使用例はクリップボードの監視を参照。

[DllImport("user32.dll")]
public static extern uint EnumClipboardFormats(uint format);

GetClipboardFormatName

GetClipboardFormatNameW 関数 (winuser.h) - Win32 apps | Microsoft Learn

登録済みのデータ形式名をクリップボードから取得します。使用例はクリップボードの監視を参照。

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetClipboardFormatName(uint format, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpszFormatName, int cchMaxCount);

使用例

はじめに

ここでの使用例は、

  • 動作環境 Windows10 x64 + Visual Studio 2022
  • ソースコードの最初に//Windows フォーム アプリケーション(.NET Framewrok)と書かれているものは、
    1. Visual Studio 2022を起動し、「新しいプロジェクトの作成」
    2. 「Windows フォーム アプリケーション(.NET Framewrok)」を選択し次へ
    3. プロジェクト名「WindowsFormsApp1」、フレームワーク「.NET Framework 4.8」として次へ
    4. Form1.csを右クリックして「コードの表示」
    5. すべてのコードを記載のソースコードに置き換え
    6. x64指定のあるものは構成マネージャでプラットフォームをx64
    7. 「F5」でデバッグ(実行)

マウスカーソル下のウィンドウ

1秒ごとにマウスカーソル下のウィンドウのタイトルとクラス名を取得してテキストボックスに表示します。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //ウィンドウタイトルを取得
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpString, int nMaxCount);

            //クラスの名前を取得
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetClassName(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpClassName, int nMaxCount);

            //マウス カーソルの位置を取得
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GetCursorPos(out POINT lpPoint);

            //指定したポイントを含むウィンドウへのハンドルを取得
            [DllImport("user32.dll")]
            public static extern IntPtr WindowFromPoint(POINT Point);
        }

        //POINT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;
        }

        //---------- Win32API用定義終了 ----------

        //出力用テキストボックス
        TextBox textBox = new TextBox
        {
            Multiline = true,
            Dock = DockStyle.Fill,
            ScrollBars = ScrollBars.Both,
            WordWrap = false
        };

        //タイマー
        Timer timer = new Timer();

        public Form1()
        {
            InitializeComponent();

            TopMost = true;
            Controls.Add(textBox);

            //1秒毎に動作
            timer.Enabled = true;
            timer.Interval = 1000;
            timer.Tick += (sender, e) =>
            {
                //マウスカーソル座標取得
                NativeMethods.GetCursorPos(out POINT point);

                //マウスカーソル下のウィンドウ取得
                IntPtr hWnd;
                hWnd = NativeMethods.WindowFromPoint(point);

                if (hWnd != IntPtr.Zero)
                {
                    //ウィンドウタイトル取得
                    StringBuilder windowTitle = new StringBuilder(256);
                    NativeMethods.GetWindowText(hWnd, windowTitle, windowTitle.Capacity);

                    //クラス名取得
                    StringBuilder className = new StringBuilder(256);
                    NativeMethods.GetClassName(hWnd, className, className.Capacity);

                    //出力
                    textBox.Text = windowTitle.ToString() + Environment.NewLine + className.ToString();
                }
            };

            FormClosing += (sender, e) =>
            {
                timer.Stop();
                timer.Dispose();
            };
        }
    }
}

参考URL

ウィンドウ情報の取得

自分自身のウィンドウ情報を取得します。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //指定したウィンドウに関する情報を取得
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool GetWindowInfo(IntPtr hWnd, ref WINDOWINFO pwi);
        }

        //WINDOWINFO構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        internal struct WINDOWINFO
        {
            public int cbSize;
            public RECT rcWindow;
            public RECT rcClient;
            public uint dwStyle;
            public uint dwExStyle;
            public uint dwWindowStatus;
            public uint cxWindowBorders;
            public uint cyWindowBorders;
            public ushort atomWindowType;
            public ushort wCreatorVersion;
        }

        //RECT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        internal struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }

        //定数の定義
        public const uint WS_ICONIC = 0x20000000;
        public const uint WS_MAXIMIZE = 0x01000000;
        public const uint WS_VISIBLE = 0x10000000;
        public const uint WS_EX_TOPMOST = 0x00000008;

        //---------- Win32API用定義終了 ----------

        public Form1()
        {
            InitializeComponent();

            //最前面、最小化で起動
            TopMost = true;
            this.WindowState = FormWindowState.Minimized;

            Shown += (sender, e) =>
            {
                //自分自身のハンドル取得
                IntPtr hWnd = Handle;

                //ウィンドウ情報取得
                WINDOWINFO windowinfo = new WINDOWINFO();
                windowinfo.cbSize = Marshal.SizeOf(windowinfo);
                NativeMethods.GetWindowInfo(hWnd, ref windowinfo);

                //情報抽出
                bool winmin = (windowinfo.dwStyle & WS_ICONIC) == WS_ICONIC;//最小化
                bool winmax = (windowinfo.dwStyle & WS_MAXIMIZE) == WS_MAXIMIZE;//最大化
                bool winvisible = (windowinfo.dwStyle & WS_VISIBLE) == WS_VISIBLE;//表示
                bool wintop = (windowinfo.dwExStyle & WS_EX_TOPMOST) == WS_EX_TOPMOST;//最前面化

                //表示用
                StringBuilder output = new StringBuilder();
                output.AppendLine($"最小化:{winmin}");
                output.AppendLine($"最大化:{winmax}");
                output.AppendLine($"表示:{winvisible}");
                output.AppendLine($"最前面化:{wintop}");

                //表示
                MessageBox.Show(output.ToString());
            };
        }
    }
}

ウィンドウ領域の取得

フォアグラウンドウィンドウのウィンドウ領域、ウィンドウ領域(可視領域)、クライアント領域のサイズを表示。ウィンドウの可視領域のサイズの取得のためDwmGetWindowAttributeを使用しています。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //フォアグラウンドウィンドウのウィンドウハンドルを取得
            [DllImport("user32.dll")]
            public static extern IntPtr GetForegroundWindow();

            //ウィンドウの領域を取得
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

            //ウィンドウのクライアント領域を取得
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

            //デスクトップ ウィンドウ マネージャー (DWM) 属性を取得
            //ウィンドウ可視領域を取得するために使用
            [DllImport("dwmapi.dll")]
            public static extern int DwmGetWindowAttribute(IntPtr hWnd, uint dwAttribute, out RECT pvAttribute, int cbAttribute);
        }

        //RECT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }

        //定数の定義
        private const uint DWMWA_EXTENDED_FRAME_BOUNDS = 9;

        //---------- Win32API用定義終了 ----------

        public Form1()
        {
            InitializeComponent();

            Shown += (sender, e) =>
            {
                //フォアグラウンドウィンドウのハンドル取得
                IntPtr hWnd = NativeMethods.GetForegroundWindow();

                //取得できた場合
                if (hWnd != IntPtr.Zero)
                {
                    //サイズ取得
                    NativeMethods.GetWindowRect(hWnd, out RECT windowSize);
                    NativeMethods.GetClientRect(hWnd, out RECT clientSize);
                    NativeMethods.DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, out RECT windowVisibleSize, Marshal.SizeOf(typeof(RECT)));

                    //表示用
                    StringBuilder sb = new StringBuilder();

                    sb.Append("ウィンドウサイズ:");
                    sb.Append($"{windowSize.right - windowSize.left}");
                    sb.Append(" x ");
                    sb.AppendLine($"{windowSize.bottom - windowSize.top}");

                    sb.Append("ウィンドウサイズ(可視領域):");
                    sb.Append($"{windowVisibleSize.right - windowVisibleSize.left}");
                    sb.Append(" x ");
                    sb.AppendLine($"{windowVisibleSize.bottom - windowVisibleSize.top}");

                    sb.Append("クライアントサイズ:");
                    sb.Append($"{clientSize.right - clientSize.left}");
                    sb.Append(" x ");
                    sb.AppendLine($"{clientSize.bottom - clientSize.top}");

                    //表示
                    MessageBox.Show(sb.ToString());
                }
            };
        }
    }
}

参考URL

7pixelの隙間ができる

タイトルバーの色を変更

自身のタイトルバー(キャプション)とそのテキストの色を変更(Windows 11 ビルド 22000 以降)

Hyper-V上のWindows11で動作確認をしています。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //デスクトップ ウィンドウ マネージャー (DWM) 属性を設定
            [DllImport("dwmapi.dll")]
            public static extern int DwmSetWindowAttribute(IntPtr hWnd, uint dwAttribute, ref uint pvAttribute, int cbAttribute);
        }

        //定数の定義
        private uint DWMWA_CAPTION_COLOR = 35;
        private uint DWMWA_TEXT_COLOR = 36;

        //---------- Win32API用定義終了 ----------

        public Form1()
        {
            InitializeComponent();

            Shown += (sender, e) =>
            {
                uint color;

                //タイトルバー(キャプション)の色を変更
                color = 0x00277FFF;//BGR
                NativeMethods.DwmSetWindowAttribute(Handle, DWMWA_CAPTION_COLOR, ref color, Marshal.SizeOf(typeof(uint)));

                //タイトルバーのテキスト(キャプションテキスト)の色を変更
                color = 0x00FF0000;//BGR
                NativeMethods.DwmSetWindowAttribute(Handle, DWMWA_TEXT_COLOR, ref color, Marshal.SizeOf(typeof(uint)));
            };
        }
    }
}

全ウィンドウの情報を表示

表示状態にある全ウィンドウの情報を表示します。

EnumWindowsを使用することで画面上のすべての最上位ウィンドウを列挙できます。EnumWindowsは、各ウィンドウハンドルを順番に、EnumWindowsの第1引数の関数、下記の例ではEnumWindowsProcに渡します。そのためEnumWindowsProc内に各ウィンドウハンドルの処理内容を記載することで、すべての最上位ウィンドウを順番に処理できます。なお、EnumWindowsProcfalseを戻した場合はその時点で処理が終了します。

下記の例では、タイトルがあってかつ表示されているオーナーウィンドウだけを抽出し、ウィンドウハンドル、プロセスID、スレッドID、タイトル、クラス名、ファイルパスを表示しています。

  1. IsWindowVisibleを使用してウィンドウが表示されているか確認します。
  2. GetWindowを使用してオーナーウィンドウか確認します。
  3. 表示されていてかつオーナーウィンドウの場合は、GetWindowThreadProcessIdでプロセスID、スレッドIDを、GetWindowTextでタイトルを取得します。
  4. タイトルがある場合は、更にGetClassNameでクラス名を取得します。
  5. 取得した情報から出力文字列を作成してテキストボックスに出力します。
  6. このときファイルパスも取得しています。取得する処理はユーザ定義関数FilePathFromProcessにまとめています。
    1. OpenProcessを使用してプロセスオブジェクトを開きます。この後GetModuleFileNameExでファイルパスを取得しますがPROCESS_QUERY_LIMITED_INFORMATIONアクセス権のみが必要なので、そのアクセス権のみ指定します。
    2. GetModuleFileNameExでファイルパスを取得します。
    3. CloseHandleでプロセスオブジェクトを閉じます。
//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //EnumWindowsのコールバック関数用のデリゲート
            [return: MarshalAs(UnmanagedType.Bool)]
            public delegate bool EnumWC(IntPtr hWnd, IntPtr lParam);

            //画面上のすべての最上位ウィンドウを列挙
            [DllImport("user32.dll")]
            public static extern int EnumWindows(EnumWC lpEnumFunc, IntPtr lParam);

            //ウィンドウとのリレーションシップ (Z オーダー または所有者) を持つウィンドウへのハンドルを取得
            //オーナーウィンドウの判定に使用
            [DllImport("user32.dll")]
            public extern static IntPtr GetWindow(IntPtr hWnd, uint uCmd);

            //ウィンドウの表示状態を取得
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public extern static bool IsWindowVisible(IntPtr hWnd);

            //ウィンドウタイトルを取得
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpString, int nMaxCount);

            //クラスの名前を取得
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetClassName(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpClassName, int nMaxCount);

            //ウィンドウを作成したスレッドIDとプロセスIDを取得
            [DllImport("user32.dll")]
            public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

            //指定したモジュールを含むファイルのフルパスを取得
            [DllImport("Psapi.dll", CharSet = CharSet.Unicode)]
            public static extern uint GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpFilename, uint nSize);

            //プロセスオブジェクトを開く
            [DllImport("Kernel32.dll", SetLastError = true)]
            public static extern IntPtr OpenProcess(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);

            //プロセスオブジェクトを閉じる
            [DllImport("Kernel32.dll")]
            public static extern int CloseHandle(IntPtr hObject);
        }

        //定数の定義
        private const uint GW_OWNER = 4;
        private const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;

        //---------- Win32API用定義終了 ----------

        //プロセスIDからファイルパスを取得
        private string FilePathFromProcessId(uint processId)
        {
            string ret = "";

            //プロセスオブジェクトを開く
            IntPtr hProcess = NativeMethods.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, processId);

            if (hProcess != IntPtr.Zero)
            {
                //ファイルパスを取得
                StringBuilder lpFilename = new StringBuilder(256);
                NativeMethods.GetModuleFileNameEx(hProcess, IntPtr.Zero, lpFilename, (uint)lpFilename.Capacity);

                ret = lpFilename.ToString();

                //オブジェクトハンドルを閉じる
                NativeMethods.CloseHandle(hProcess);
            }

            return ret;
        }

        //EnumWindowsのコールバック関数
        public bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam)
        {
            bool winvisible = NativeMethods.IsWindowVisible(hWnd);
            bool owner = NativeMethods.GetWindow(hWnd, GW_OWNER) == IntPtr.Zero;
            if (winvisible && owner)
            {
                //スレッドIDとプロセスID取得
                uint threadId = NativeMethods.GetWindowThreadProcessId(hWnd, out uint processId);

                //タイトル取得
                StringBuilder winTitle = new StringBuilder(256);
                NativeMethods.GetWindowText(hWnd, winTitle, winTitle.Capacity);

                if (winTitle.ToString() != "")
                {
                    //クラス名取得
                    StringBuilder className = new StringBuilder(256);
                    NativeMethods.GetClassName(hWnd, className, className.Capacity);

                    //出力情報作成
                    StringBuilder output = new StringBuilder();
                    output.AppendLine($"hWnd:0x{hWnd.ToString("X8")}");//ウィンドウハンドル
                    output.AppendLine($"PID:{processId}");//プロセスID
                    output.AppendLine($"ThreadID:{threadId}");//スレッドID
                    output.AppendLine(winTitle.ToString());//タイトル
                    output.AppendLine(className.ToString());//クラス名
                    output.AppendLine(FilePathFromProcessId(processId));//ファイルパス
                    output.AppendLine("");

                    //出力
                    textBox.AppendText(output.ToString());
                }
            }

            return true;
        }

        //出力用テキストボックス
        TextBox textBox = new TextBox
        {
            Multiline = true,
            Dock = DockStyle.Fill,
            ScrollBars = ScrollBars.Both,
            WordWrap = false
        };

        public Form1()
        {
            InitializeComponent();

            Controls.Add(textBox);

            NativeMethods.EnumWindows(EnumWindowsProc, IntPtr.Zero);
        }
    }
}

System.Diagnostics.Processを使用する場合は、

32ビットプロセスは、64ビットプロセスのモジュールにアクセスできません - kk_AtakaのScrapbox

//Windows フォーム アプリケーション(.NET Framewrok)
//ビルドで「32 ビットを選ぶ」のチェックをオフ
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        internal static class NativeMethods
        {
            //クラスの名前を取得
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetClassName(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpClassName, int nMaxCount);

            //ウィンドウを作成したスレッドIDとプロセスIDを取得
            [DllImport("user32.dll")]
            public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
        }

        TextBox textBox;

        public Form1()
        {
            InitializeComponent();

            textBox = new TextBox
            {
                Multiline = true,
                Dock = DockStyle.Fill,
                ScrollBars = ScrollBars.Both,
                WordWrap = false
            };
            Controls.Add(textBox);

            foreach (Process process in Process.GetProcesses())
            {
                if (process.MainWindowTitle.Length != 0 && process.MainWindowHandle != IntPtr.Zero)
                {
                    IntPtr hWnd = process.MainWindowHandle;
                    uint threadId = NativeMethods.GetWindowThreadProcessId(hWnd, out int processId);

                    //クラス名取得
                    StringBuilder className = new StringBuilder(256);
                    NativeMethods.GetClassName(hWnd, className, className.Capacity);

                    StringBuilder output = new StringBuilder();

                    output.AppendLine($"hWnd:0x{hWnd.ToString("X8")}");//ウィンドウハンドル
                    output.AppendLine($"PID:{processId}");//プロセスID
                    output.AppendLine($"ThreadID:{threadId}");//スレッドID
                    output.AppendLine(process.MainWindowTitle);//タイトル
                    output.AppendLine(className.ToString());//クラス名
                    output.AppendLine(process.MainModule.FileName);//ファイルパス
                    output.AppendLine("");

                    textBox.AppendText(output.ToString());
                }
            }
        }
    }
}

全ての子ウィンドウを表示

1秒ごとにマウスカーソル下のアプリケーションのウィンドウ階層を表示します。また、現在その階層のどこにマウスカーソルがあるかも表示します。例えばペイントブラシでリボンの上にマウスカーソルがある場合は、

無題 - ペイント/MSPaintApp
   UIRibbonDockLeft/UIRibbonCommandBarDock
   UIRibbonDockRight/UIRibbonCommandBarDock
   UIRibbonDockTop/UIRibbonCommandBarDock
      Ribbon/UIRibbonCommandBar
         Ribbon/UIRibbonWorkPane
            /NUIPane
               /NetUIHWND ← ■マウスカーソル下のウィンドウ■
   UIRibbonDockBottom/UIRibbonCommandBarDockaaa
...

といった形で表示します。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //マウス カーソルの位置を取得
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GetCursorPos(out POINT lpPoint);

            //指定したポイントを含むウィンドウへのハンドルを取得
            [DllImport("user32.dll")]
            public static extern IntPtr WindowFromPoint(POINT Point);

            //指定したウィンドウの先祖へのハンドルを取得
            [DllImport("user32.dll")]
            public extern static IntPtr GetAncestor(IntPtr hWnd, uint gaFlags);

            //EnumChildWindowsのコールバック関数用のデリゲート
            [return: MarshalAs(UnmanagedType.Bool)]
            public delegate bool EnumCW(IntPtr hWnd, IntPtr lParam);

            //すべての子ウィンドウを列挙
            [DllImport("user32.dll")]
            public static extern int EnumChildWindows(IntPtr hWndParent, EnumCW lpEnumFunc, IntPtr lParam);

            //親ウィンドウハンドルを取得
            [DllImport("user32.dll")]
            public extern static IntPtr GetParent(IntPtr hWnd);

            //ウィンドウタイトルを取得
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetWindowText(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpString, int nMaxCount);

            //クラスの名前を取得
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetClassName(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpClassName, int nMaxCount);
        }

        //POINT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;
        }

        //定数の定義
        private const uint GA_ROOT = 2;

        //---------- Win32API用定義終了 ----------

        //出力用テキストボックス
        TextBox textBox = new TextBox
        {
            Multiline = true,
            Dock = DockStyle.Fill,
            ScrollBars = ScrollBars.Both,
            WordWrap = false
        };

        //タイマー
        Timer timer = new Timer();

        //マウスカーソル下のウィンドウ
        IntPtr hWnd;

        //すべての子ウィンドウ
        List<IntPtr> childrenhWnd;

        //すべての子ウィンドウを取得
        public bool EnumChildProc(IntPtr hWnd, IntPtr lParam)
        {
            childrenhWnd.Add(hWnd);
            return true;
        }

        //階層化用クラス
        class stratify
        {
            public IntPtr Parent { get; set; }//親ウィンドウ
            public List<stratify> Children { get; set; }//子ウィンドウ

            public stratify(IntPtr parent)
            {
                Parent = parent;
                Children = new List<stratify>();
            }
        }

        //ウィンドウの階層化
        void SetChildren(stratify window)
        {
            //直下の子ウィンドウを取得
            foreach (IntPtr hWnd in childrenhWnd)
            {
                if (NativeMethods.GetParent(hWnd) == window.Parent)
                {
                    window.Children.Add(new stratify(hWnd));
                }
            }

            //直下の子ウィンドウの子ウィンドウを取得
            foreach (stratify child in window.Children)
            {
                SetChildren(child);
            }
        }

        //ウィンドウ階層を表示
        void ViewChildren(stratify window, int level)
        {
            //ウィンドウタイトル取得
            StringBuilder windowTitle = new StringBuilder(256);
            NativeMethods.GetWindowText(window.Parent, windowTitle, windowTitle.Capacity);

            //クラス名取得
            StringBuilder className = new StringBuilder(256);
            NativeMethods.GetClassName(window.Parent, className, className.Capacity);

            //表示
            textBox.Text += "".PadLeft(level * 3, ' ')//階層に応じて文頭にスペース追加
                + windowTitle.ToString() + "/" + className.ToString()
                + (hWnd == window.Parent ? " ← ■マウスカーソル下のウィンドウ■" : "")
                + Environment.NewLine;

            //子ウィンドウ情報も表示
            foreach (var child in window.Children)
            {
                ViewChildren(child, level + 1);
            }
        }

        public Form1()
        {
            InitializeComponent();

            TopMost = true;
            Controls.Add(textBox);

            //1秒毎に動作
            timer.Enabled = true;
            timer.Interval = 1000;
            timer.Tick += (sender, e) =>
            {
                //初期化
                hWnd = IntPtr.Zero;
                textBox.Text = "";
                childrenhWnd = new List<IntPtr>();

                //マウスカーソル座標取得
                NativeMethods.GetCursorPos(out POINT point);

                //マウスカーソル下のウィンドウ取得
                hWnd = NativeMethods.WindowFromPoint(point);

                if (hWnd != IntPtr.Zero)
                {
                    //ルートウィンドウ取得
                    IntPtr roothWnd = NativeMethods.GetAncestor(hWnd, GA_ROOT);
                    if (roothWnd != IntPtr.Zero)
                    {
                        //すべての子ウィンドウ取得
                        NativeMethods.EnumChildWindows(roothWnd, EnumChildProc, IntPtr.Zero);

                        //ウィンドウの階層化
                        stratify kaisou = new stratify(roothWnd);
                        SetChildren(kaisou);

                        //ウィンドウ階層を表示
                        ViewChildren(kaisou, 0);
                    }
                }
            };

            FormClosing += (sender, e) =>
            {
                timer.Stop();
                timer.Dispose();
            };
        }
    }
}

ウィンドウの最前面化、サイズや位置の変更

起動したメモ帳を最前面化します。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //ウィンドウのサイズ、位置、Zオーダーを変更
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
        }

        //定数の定義
        private const int SWP_NOMOVE = 0x0002;//現在位置を保持(X,Yパラメーターを無視)。
        private const int SWP_NOSIZE = 0x0001;//現在のサイズを保持(cx,cyパラメーターを無視)。
        private const int HWND_NOTOPMOST = -2;//最前面の背後
        private const int HWND_TOPMOST = -1;//最前面

        //---------- Win32API用定義終了 ----------

        public Form1()
        {
            InitializeComponent();

            using (Process process = new Process())
            {
                //メモ帳を起動
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.FileName = @"C:\Windows\System32\notepad.exe";
                process.Start();
                process.WaitForInputIdle(3000);

                //最前面化する
                NativeMethods.SetWindowPos(process.MainWindowHandle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

                //位置指定
                //NativeMethods.SetWindowPos(process.MainWindowHandle, HWND_NOTOPMOST, 100, 300, 0, 0, SWP_NOSIZE);

                //サイズ指定
                //NativeMethods.SetWindowPos(process.MainWindowHandle, HWND_NOTOPMOST, 0, 0, 200, 300, SWP_NOMOVE);
            }
        }
    }
}

参考URL

エクスプローラではうまくいかないようです。

非アクティブウィンドウにキーを送信

ボタンを押したらメモ帳にメッセージを送信(POST)します。

AキーDOWNメッセージを送信し、文字「a」を入力

キーストロークをウインドウに送信 | C#

  1. FindWindowExで子ウィンドウを検索します。第1引数がNULLの場合はデスクトップウィンドウを親ウィンドウとして検索します。NULL(IntPtr.Zero)を設定することで、デスクトップ上のメモ帳を探します。
  2. 更にメモ帳の文字を入力するところ(Edit子ウィンドウ)を検索します。
  3. PostMessageでAキーDOWNメッセージを送信します。
    1. 第1引数hWndには対象のウィンドウハンドル(Edit子ウィンドウ)を指定します。
    2. 第2引数Msgには投稿するメッセージを指定します。今回はAキーDOWNメッセージを送るためWM_KEYDOWNを指定します。WM_KEYDOWNによるとwParam仮想キーコードlParamキーストローク メッセージ フラグを指定します。
    3. 第3引数wParamにはKeys列挙型を利用してAキーの仮想キーコードを設定します。
    4. 第4引数lParamの情報は通常必要ないため0を指定します。
  4. AキーDOWNメッセージだけでは、実際にキーが押されたままになるわけではないため、AキーUPメッセージは送信しません。
//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //指定したクラス名とウィンドウ名と一致するウィンドウハンドルを取得
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

            //メッセージを送信(非同期)
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool PostMessage(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam);
        }

        //定数の定義
        private const uint WM_KEYDOWN = 0x0100;

        //---------- Win32API用定義終了 ----------

        public Form1()
        {
            InitializeComponent();

            Width = Height = 200;

            //メモ帳を起動しておく
            using (Process process = new Process())
            {
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.FileName = @"C:\Windows\System32\notepad.exe";
                process.Start();
                process.WaitForInputIdle(3000);
            }

            //ボタン
            Button button = new Button { Text = "Send key" };
            Controls.Add(button);

            //ボタンを押したとき
            button.Click += (sender, e) =>
            {
                IntPtr hWnd = IntPtr.Zero;

                //メモ帳を探す
                hWnd = NativeMethods.FindWindowEx(hWnd, IntPtr.Zero, "Notepad", null);
                if (hWnd == IntPtr.Zero) return;

                //Editクラス名を探す
                hWnd = NativeMethods.FindWindowEx(hWnd, IntPtr.Zero, "Edit", null);
                if (hWnd == IntPtr.Zero) return;

                //キー送信
                //AキーDOWNメッセージを送信
                NativeMethods.PostMessage(hWnd, WM_KEYDOWN, (UIntPtr)Keys.A, (IntPtr)0);
            };
        }
    }
}

第4引数lParamの情報をきちんと作成して送信する場合は、WM_KEYDOWNを見て、

bit 内容
31 0 0固定
30 0 前のキーの状態。メッセージが送信される前にキーが
・ダウンしている場合1
・アップしている場合0
29 0 0固定
25-28(4bit) 0 未使用
24 0 拡張キーでない場合は0
16-23(8bit) 送信するキーのスキャンコード(1バイト)
MapVirtualKeyを使用して取得
0-15 1 メッセージの繰り返し数なので1

MapVirtualKeyと定数を定義して、

//Windows APIの定義に追加
//仮想キーコードをスキャンコードに変換
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern uint MapVirtualKey(uint uCode, uint uMapType);

//定数の定義に追加
private const uint MAPVK_VK_TO_VSC = 0;

lParamを作成して送信します。

//キー送信
uint keycode, scancode, flag;

//仮想キーコード
keycode = (uint)Keys.A;

//仮想キーコードからスキャンコードを取得
scancode = NativeMethods.MapVirtualKey(keycode, MAPVK_VK_TO_VSC);

//キーストローク メッセージ フラグ
flag = 1;//繰り返し数
flag += scancode << 16;//スキャンコード 下位が16bit

//AキーDOWNメッセージを送信
NativeMethods.PostMessage(hWnd, WM_KEYDOWN, (UIntPtr)keycode, (IntPtr)flag);

Alt+Hメッセージを送信し、ヘルプメニューを表示

WM_KEYDOWNではなくWM_SYSKEYDOWNを使用します。Altを押しながらHを押したいので、lParamのbit29を1にします。

//定数の定義に追加
private const uint WM_SYSKEYDOWN = 0x0104;

//キー送信
//Alt+Hメッセージを送信
NativeMethods.PostMessage(hWnd, WM_SYSKEYDOWN, (UIntPtr)Keys.H, (IntPtr)(1 << 29));

Ctrl+Oを送信し、ファイルを開くダイアログを表示(keybd_event)

  1. keybd_eventを使って送信前にCtrlを押します。
  2. WM_KEYDOWNOを送信します。
  3. 送信後Ctrlを離します。
//Windows APIの定義に追加
//キーストロークを合成
[DllImport("user32.dll")]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);

//定数の定義に追加
private const uint KEYEVENTF_KEYUP = 2;

//キー送信
//Ctrlキーを押す
NativeMethods.keybd_event((byte)Keys.ControlKey, 0, 0, (UIntPtr)0);

//OキーDOWNメッセージを送信
NativeMethods.PostMessage(hWnd, WM_KEYDOWN, (UIntPtr)Keys.O, (IntPtr)0);

//Ctrlキーを離す
NativeMethods.keybd_event((byte)Keys.ControlKey, 0, KEYEVENTF_KEYUP, (UIntPtr)0);

Ctrl+Oを送信し、ファイルを開くダイアログを表示(SendInput)

keybd_event関数は現在はSendInputに置き換わっています。SendInputを使用する場合は、

  1. SendInputの引数作成のときに使用する以下の関数を定義します。
    • GetMessageExtraInfo : 現在のスレッドの追加のメッセージ情報を取得。
    • MapVirtualKey : 仮想キーコードからスキャンコードに変換
  2. SendInputの引数であるINPUT構造体を定義します。
    コード上のINPUT構造体の定義(ここから)からINPUT構造体の定義(ここまで)
  3. 必要な定数を定義します。
  4. あとはSendInputのページを見て引数を作成して使用します。
//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //指定したクラス名とウィンドウ名と一致するウィンドウハンドルを取得
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

            //メッセージを送信(非同期)
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool PostMessage(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam);

            //キーストローク、マウスの動き、ボタンのクリックを合成
            [DllImport("user32.dll")]
            public static extern uint SendInput(uint cInputs, [In, Out] INPUT[] pInputs, int cbSize);

            //現在のスレッドの追加のメッセージ情報を取得
            [DllImport("user32.dll")]
            public static extern IntPtr GetMessageExtraInfo();

            //仮想キーコードをスキャンコードに変換
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern uint MapVirtualKey(uint uCode, uint uMapType);
        }

        //INPUT構造体の定義(ここから)
        [StructLayout(LayoutKind.Sequential)]
        public struct INPUT
        {
            public uint type;
            public DUMMYUNIONNAME input;
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct DUMMYUNIONNAME
        {
            [FieldOffset(0)]
            public MOUSEINPUT mi;
            [FieldOffset(0)]
            public KEYBDINPUT ki;
            [FieldOffset(0)]
            public HARDWAREINPUT hi;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MOUSEINPUT
        {
            public int dx;
            public int dy;
            public uint mouseData;
            public uint dwFlags;
            public uint time;
            public UIntPtr dwExtraInfo;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct KEYBDINPUT
        {
            public ushort wVk;
            public ushort wScan;
            public uint dwFlags;
            public uint time;
            public UIntPtr dwExtraInfo;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct HARDWAREINPUT
        {
            public uint uMsg;
            public ushort wParamL;
            public ushort wParamH;
        }
        //INPUT構造体の定義(ここまで)

        //定数の定義
        private const uint INPUT_KEYBOARD = 1;
        private const uint MAPVK_VK_TO_VSC = 0;
        private const uint WM_KEYDOWN = 0x0100;
        private const uint KEYEVENTF_KEYUP = 2;

        //---------- Win32API用定義終了 ----------

        public Form1()
        {
            InitializeComponent();

            Width = Height = 200;

            //メモ帳を起動しておく
            using (Process process = new Process())
            {
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.FileName = @"C:\Windows\System32\notepad.exe";
                process.Start();
                process.WaitForInputIdle(3000);
            }

            //ボタン
            Button button = new Button { Text = "Send key" };
            Controls.Add(button);

            //ボタンを押したとき
            button.Click += (sender, e) =>
            {
                IntPtr hWnd = IntPtr.Zero;

                //メモ帳を探す
                hWnd = NativeMethods.FindWindowEx(hWnd, IntPtr.Zero, "Notepad", null);
                if (hWnd == IntPtr.Zero) return;

                //キー送信

                //Ctrlキー
                INPUT input = new INPUT { type = INPUT_KEYBOARD };
                input.input.ki.wVk = (ushort)Keys.ControlKey;
                input.input.ki.wScan = (ushort)NativeMethods.MapVirtualKey((uint)Keys.ControlKey, MAPVK_VK_TO_VSC);
                input.input.ki.time = 0;
                input.input.ki.dwExtraInfo = (UIntPtr)NativeMethods.GetMessageExtraInfo().ToInt32();

                //Ctrlキーを押す
                input.input.ki.dwFlags = 0;
                NativeMethods.SendInput(1, new[] { input }, Marshal.SizeOf(typeof(INPUT)));

                //OキーDOWNメッセージを送信
                NativeMethods.PostMessage(hWnd, WM_KEYDOWN, (UIntPtr)Keys.O, (IntPtr)0);

                //Ctrlキーを離す
                input.input.ki.dwFlags = KEYEVENTF_KEYUP;
                NativeMethods.SendInput(1, new[] { input }, Marshal.SizeOf(typeof(INPUT)));
            };
        }
    }
}

参考URL

マウスカーソルの表示切替

Alt+Bでボタンを押すたびにマウスカーソルの表示を切り替えます。

マウスカーソルを表示するかどうかを決定する内部表示カウントは、

  • 初期値は0
  • ShowCursor(true)で+1
  • ShowCursor(false)で-1
  • 0以上の場合に、マウスカーソルを表示
//Windows フォーム アプリケーション(.NET Framewrok)
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //マウスカーソルの表示切替
            [DllImport("user32.dll")]
            public static extern int ShowCursor([MarshalAs(UnmanagedType.Bool)] bool bShow);
        }

        //---------- Win32API用定義終了 ----------

        bool show = true;//初期は表示状態

        public Form1()
        {
            InitializeComponent();

            //ボタン
            Button button = new Button { Text = "&B" };
            Controls.Add(button);

            //ボタンを押す(Alt+B)たびにマウスカーソルの表示切替
            button.Click += (sender, e) =>
            {
                show = !show;
                NativeMethods.ShowCursor(show);
            };
        }
    }
}

マウスカーソル位置の色を取得

1秒ごとにマウスカーソル位置の色を取得し、フォームの背景色に設定します。

  1. Windowsの「拡大と縮小のレイアウト」の設定が「100%」でない場合、正しく動作しません。そのため、ウィンドウ作成前にSetProcessDPIAwareを使用してプロセス既定のDPI認識をシステムDPI対応にします。以下のURLに記載の通り、この方法は推奨されませんが、使用例としての使用だけですので使用しています。
  2. GetDCの引数にNULLIntPtr.Zero)を指定し、画面全体のデバイス コンテキストを取得します。
  3. GetCursorPosGetPixelを使用して、デバイス コンテキストからマウスカーソル位置の色情報を取得します。GetPixelの戻り値が色情報です。
  4. GetDCで取得したデバイス コンテキストをReleaseDCで解放します。
  5. GetPixelの戻り値は4バイトのCOLORREFで上位から順に0固定 B G Rとなっているため、1つずつ抽出しています。
//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //デバイス コンテキスを取得
            [DllImport("user32.dll")]
            public static extern IntPtr GetDC(IntPtr hWnd);

            //デバイス コンテキストを解放
            [DllImport("user32.dll")]
            public static extern int ReleaseDC(IntPtr hWnd, IntPtr hdc);

            //マウス カーソルの位置を取得
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GetCursorPos(out POINT lpPoint);

            //指定した座標にあるピクセルの赤、緑、青 (RGB)を取得
            [DllImport("Gdi32.dll")]
            public static extern uint GetPixel(IntPtr hdc, int x, int y);

            //システムDPI対応に設定
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool SetProcessDPIAware();
        }

        //POINT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;
        }

        //---------- Win32API用定義終了 ----------

        Timer timer = new Timer();
        IntPtr hdc = IntPtr.Zero;

        public Form1()
        {
            InitializeComponent();

            //フォームのサイズ
            Height = 100;
            Width = 400;

            //システムDPI対応
            NativeMethods.SetProcessDPIAware();

            //1秒毎に動作
            Shown += (sender, e) =>
            {
                timer.Enabled = true;
                timer.Interval = 1000;
            };
            timer.Tick += (sender, e) =>
            {
                //画面全体のデバイス コンテキストを取得
                hdc = NativeMethods.GetDC(IntPtr.Zero);

                //マウスカーソル座標取得
                NativeMethods.GetCursorPos(out POINT point);

                //マウスカーソル位置の色を取得
                int color = (int)NativeMethods.GetPixel(hdc, point.x, point.y);

                //デバイス コンテキストを解放
                if (NativeMethods.ReleaseDC(IntPtr.Zero, hdc) == 1)
                {
                    hdc = IntPtr.Zero;
                }

                //RGBを取得
                byte blue = (byte)((color >> 16) & 0xFF);
                byte green = (byte)((color >> 8) & 0xFF);
                byte red = (byte)(color & 0xFF);

                //背景色とタイトル変更
                BackColor = Color.FromArgb(red, green, blue);
                Text = $"R{red},G{green},B{blue}";
            };

            FormClosing += (sender, e) =>
            {
                timer.Stop();
                timer.Dispose();
                //デバイス コンテキストが解放されていない場合は解放
                if (hdc != IntPtr.Zero)
                {
                    NativeMethods.ReleaseDC(IntPtr.Zero, hdc);
                }
            };
        }
    }
}

参考URL

スクリーンキャプチャ

1秒ごとにプライマリモニタのスクリーンをキャプチャし、実行ファイルと同じフォルダに保存します。マウスカーソルもあわせてキャプチャします。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //グローバルカーソルに関する情報を取得
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GetCursorInfo(ref CURSORINFO pci);
        }

        //CURSORINFO構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct CURSORINFO
        {
            public uint cbSize;
            public uint flags;
            public IntPtr hCursor;
            public POINT ptScreenPos;
        }

        //POINT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;
        }

        //---------- Win32API用定義終了 ----------

        //タイマー
        Timer timer = new Timer();

        public Form1()
        {
            InitializeComponent();

            //保存先
            string exefilepath = System.Reflection.Assembly.GetExecutingAssembly().Location;
            string directory = Path.GetDirectoryName(exefilepath) + @"\";

            //1秒毎に動作
            timer.Enabled = true;
            timer.Interval = 1000;
            timer.Tick += (sender, e) =>
            {
                string filepath = directory + DateTime.Now.ToString("yyyyMMdd_HHmmss_fff") + ".png";
                CaptureRectangle(Screen.PrimaryScreen.Bounds, filepath);
            };

            FormClosing += (sender, e) =>
            {
                timer.Stop();
                timer.Dispose();
            };
        }

        //領域キャプチャと保存
        private void CaptureRectangle(Rectangle rectangle, string filepath)
        {
            using (Bitmap bitmap = new Bitmap(rectangle.Width, rectangle.Height))
            {
                using (Graphics graphics = Graphics.FromImage(bitmap))
                {
                    //領域キャプチャ
                    graphics.CopyFromScreen(rectangle.Location, new Point(0, 0), bitmap.Size);

                    //マウスカーソル取得
                    CURSORINFO cursorinfo = new CURSORINFO();
                    cursorinfo.cbSize = (uint)Marshal.SizeOf(cursorinfo);
                    if (NativeMethods.GetCursorInfo(ref cursorinfo))
                    {
                        Cursor cursor = new Cursor(cursorinfo.hCursor);
                        Point hotSpot = cursor.HotSpot;
                        Point cursorScreenPosition = Cursor.Position;

                        //マウスカーソルを描画
                        graphics.DrawIcon(Icon.FromHandle(cursorinfo.hCursor), cursorScreenPosition.X - hotSpot.X - rectangle.X, cursorScreenPosition.Y - hotSpot.Y - rectangle.Y);
                    }

                    //保存
                    bitmap.Save(filepath, System.Drawing.Imaging.ImageFormat.Png);
                }
            }
        }
    }
}

参考URL

クリップボードの中身をクリア

クリップボードの中身をクリアします。

クリップボードのデータをクリア - プログラミングのメモ帳(C/C++/HSP)

internal static class NativeMethods
{
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool OpenClipboard(IntPtr hWnd);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool EmptyClipboard();

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool CloseClipboard();
}

private void ClearClip()
{
    if (NativeMethods.OpenClipboard(IntPtr.Zero))
    {
        if (NativeMethods.EmptyClipboard())
        {
            MessageBox.Show("クリップボードをクリアしました。");
        }
        else
        {
            MessageBox.Show("クリップボードのクリアに失敗しました。");
        }
        NativeMethods.CloseClipboard();
    }
}

ごみ箱の中身をクリア

ごみ箱を空にします。

ごみ箱のクリア - プログラミングのメモ帳(C/C++/HSP)

internal static class NativeMethods
{
    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    public static extern uint SHEmptyRecycleBin(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr), In] string pszRootPath, uint dwFlags);
}

private const uint SHERB_NOCONFIRMATION = 0x00000001;//オブジェクトの削除を確認するダイアログ ボックスは表示されません。
private const uint SHERB_NOPROGRESSUI = 0x00000002;//進行状況を示すダイアログ ボックスは表示されません。
private const uint SHERB_NOSOUND = 0x00000004;//操作が完了すると、サウンドは再生されません。
private const uint S_OK = 0;

private void ClearDust()
{
    uint result = NativeMethods.SHEmptyRecycleBin(IntPtr.Zero, "", SHERB_NOCONFIRMATION | SHERB_NOPROGRESSUI | SHERB_NOSOUND);

    if (result == S_OK)
    {
        MessageBox.Show("ごみ箱を空にしました。");
    }
    else
    {
        MessageBox.Show("ごみ箱を空にできませんでした。");
    }
}

DLLを動的に呼び出す

C#でLoadLibraryを使用してアンマネージDLLを使用する - 閑古鳥

User32.dll内のMessageBoxWを動的に呼び出して使用します。

  1. LoadLibraryを使用してDLLを読み込みます。(ダイナミック リンク ライブラリの検索順序を参照)
  2. GetProcAddressを使用してDLL内の関数へのポインタを取得します。この関数の引数はLPCSTRなのでANSI文字列になります。
  3. Marshal.GetDelegateForFunctionPointerを使用して関数ポインタをデリゲートに変換します。
  4. 変換したデリゲートを使って関数を呼びます。
  5. FreeLibraryでDLLを解放します。
//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //DLL読み込み
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPWStr)] string lpLibFileName);

            //DLL解放
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool FreeLibrary(IntPtr hLibModule);

            //DLL内の関数を取得
            [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
            public static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);
        }

        //---------- Win32API用定義終了 ----------

        //MessageBoxW用のdelegate
        public delegate int delegateMessageBox(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string lpText, [MarshalAs(UnmanagedType.LPWStr)] string lpCaption, uint uType);

        public Form1()
        {
            InitializeComponent();

            //メッセージ表示用ボタン
            Button button = new Button { Text = "button" };

            //ボタンが押されたときにMessageBoxWを使ってメッセージを表示
            button.Click += (sender, e) =>
            {
                //DLL読み込み
                IntPtr hModule = NativeMethods.LoadLibrary("user32");
                if (hModule == IntPtr.Zero) return;

                //MessageBoxW関数へのポインタ
                IntPtr funcPtr = NativeMethods.GetProcAddress(hModule, "MessageBoxW");
                if (hModule != IntPtr.Zero)
                {
                    //アンマネージ関数ポインタをデリゲートに変換
                    delegateMessageBox MessageBox = (delegateMessageBox)Marshal.GetDelegateForFunctionPointer(funcPtr, typeof(delegateMessageBox));

                    //関数の実行
                    MessageBox(IntPtr.Zero, "message", "title", 0);
                }

                //DLL解放
                NativeMethods.FreeLibrary(hModule);
            };

            Controls.Add(button);
        }
    }
}

自作したDLLをアプリケーションと同じ場所において呼び出す

呼ばれる側のDLLをC言語で作成します。ここでは単純に引数の2つの数を足したものを返す関数を作ります。まずはaddfunc.cppを以下のように作成し、addfunc.dllを作成します。

MinGW+C言語でWindows用Dllを作成する #Windows - Qiita

/*
g++ -shared -o addfunc.dll addfunc.cpp
*/
#include <windows.h>

extern "C" __declspec(dllexport) int __stdcall AddXY(short x, short y)
{
	return x + y;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
	return TRUE;
}

C#で呼び出します。DLLと同じx64でビルドします。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //DLL読み込み
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPWStr)] string lpLibFileName);

            //DLL解放
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool FreeLibrary(IntPtr hLibModule);

            //DLL内の関数を取得
            [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
            public static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);
        }

        //---------- Win32API用定義終了 ----------

        //AddXY用のdelegate
        public delegate int delegateAddFunc(short x, short y);

        public Form1()
        {
            InitializeComponent();

            //結果表示用ボタン
            Button button = new Button { Text = "button" };

            //ボタンが押されたときにAddXYの結果を表示
            button.Click += (sender, e) =>
            {
                //実行ファイルのパス
                string filepath = System.Reflection.Assembly.GetExecutingAssembly().Location;

                //DLLファイルのパス
                string dllpath = System.IO.Path.GetDirectoryName(filepath) + @"\addfunc.dll";

                //DLLの存在確認
                if (!System.IO.File.Exists(dllpath)) return;

                //DLL読み込み
                IntPtr hModule = NativeMethods.LoadLibrary(dllpath);
                if (hModule == IntPtr.Zero) return;

                //AddXY関数へのポインタ
                IntPtr funcPtr = NativeMethods.GetProcAddress(hModule, "AddXY");
                if (hModule != IntPtr.Zero)
                {
                    //アンマネージ関数ポインタをデリゲートに変換
                    delegateAddFunc AddXY = (delegateAddFunc)Marshal.GetDelegateForFunctionPointer(funcPtr, typeof(delegateAddFunc));

                    //関数の実行
                    int ret = AddXY(3, 5);

                    //結果の表示
                    MessageBox.Show(ret.ToString());
                }

                //DLL解放
                NativeMethods.FreeLibrary(hModule);
            };

            Controls.Add(button);
        }
    }
}

項目の趣旨からは外れますが、もちろんDllImportを使用しても構いません。

//Windows フォーム アプリケーション(.NET Framewrok)
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        internal static class NativeMethods
        {
            [DllImport("addfunc.dll", EntryPoint = "AddXY")]
            public static extern int AddXY(short x, short y);
        }

        public Form1()
        {
            InitializeComponent();
            MessageBox.Show(NativeMethods.AddXY(3, 5).ToString());
        }
    }
}

参考URL

フック

キーボードフック(固有スレッド)

フックの概要 - Win32 apps | Microsoft Learn

SetWindowsHookExを使用して、キーボードフックを実装します。正確性よりもイメージを優先すると、

SetWindowsHookEx(idHook, lpfn, hmod, dwThreadId)

パターン idHook lpfn hmod dwThreadId
フックの種類 フック関数 フック関数を含む DLL へのハンドル フックを関連付けるスレッド
#1 種類を指定 DLL内の関数 DLL へのハンドル 0 ※下記参照
#2 種類を指定 DLL内の関数 DLL へのハンドル 別のスレッド
#3 種類を指定 現在のコード内の関数で可 NULL(IntPtr.Zero) 現在のスレッド

※0の場合、呼び出し元のスレッドと同じデスクトップで実行されているすべての既存のスレッド

となります。今回はDLLの作成はしませんので、現在のスレッド(自分自身)に対するフック(#3)をしてみます。

フックの種類 スコープ
WH_CALLWNDPROC WH_CALLWNDPROCRET WH_CBT WH_DEBUG WH_FOREGROUNDIDLE WH_GETMESSAGE WH_KEYBOARD WH_MOUSE WH_MSGFILTER WH_SHELL スレッドまたはグローバル
WH_KEYBOARD_LL WH_MOUSE_LL WH_SYSMSGFILTER グローバルのみ

ここではWH_KEYBOARDを例にして作成します。

WH_KEYBOARD 2
キーストローク メッセージを監視するフック プロシージャをインストールします。 詳細については、 KeyboardProc フックの手順に関するページを参照してください。

  1. SetWindowsHookExを使用してフック プロシージャをインストールします。
    1. 第1引数にはインストールするフック プロシージャの種類であるWH_KEYBOARDを指定します。
    2. 第2引数にはフック プロシージャへのポインタ(メッセージを処理する関数)を指定します。下記の例ではHookProcになります。
    3. 第3引数にはNULL(IntPtr.Zero)を設定します。
    4. 第4引数にはGetCurrentThreadIdで取得した、呼び出し元のスレッドID(=自身のスレッドID)を設定します。
  2. メッセージを受け取ったときに処理する関数、下記の場合だとHookProcを実装します。ここでは単純に受け取ったキーメッセージを表示するだけとしています。KeyboardProcのページを確認し、
    1. nCodeが0未満の場合は、何もせずCallNextHookExを使用して次のフックプロシージャにイベントを渡します。nCode=0の場合のみ処理をします。
    2. wParamは仮想キーコードになるのでKeys 列挙型を使って文字列に変換しています。
    3. lParamキーストローク メッセージ フラグになります。WM_KEYDOWNWM_KEYUPなどを確認すれば分かりますが項目ごとにbitの区分がありますので、-で区切って0-0-0-0000-0-00011110-0000000000000001のように変換します。
    4. 今回は単純にメッセージを表示するだけなので、次のフックチェーンにメッセージを渡すために、CallNextHookExを呼んで戻します。
  3. 終了時にUnhookWindowsHookExを使用してインストールしたフック プロシージャを削除しています。

キーを押すたびに「キー文字列」と「キーストローク メッセージ フラグ」を表示します。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //フックコールバック関数
            public delegate IntPtr SetWindowsHookExCallback(int nCode, UIntPtr wParam, IntPtr lParam);

            //フックの設定
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr SetWindowsHookEx(int idHook, SetWindowsHookExCallback lpfn, IntPtr hmod, uint dwThreadId);

            //フックの解除
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool UnhookWindowsHookEx(IntPtr hhk);

            //フック情報を次のフックプロシージャに渡す
            [DllImport("user32.dll")]
            public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);

            //自身のスレッドID取得
            [DllImport("Kernel32.dll")]
            public static extern uint GetCurrentThreadId();
        }

        //定数の定義
        private const int WH_KEYBOARD = 2;//キーボードフック
        private const int HC_ACTION = 0;

        //---------- Win32API用定義終了 ----------

        //フック プロシージャ
        IntPtr HookProc(int nCode, UIntPtr wParam, IntPtr lParam)
        {
            if (nCode == HC_ACTION)
            {
                //wParam 仮想キーコード
                textBox.AppendText(((Keys)wParam.ToUInt32()).ToString().PadRight(12, ' '));

                //lParam キーストローク メッセージ フラグ
                textBox.AppendText(KeyMsgFlgFromUInt32(lParam) + Environment.NewLine);
            }

            return NativeMethods.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
        }

        //キーストローク メッセージ フラグを2進数にして区切りを入れる
        private string KeyMsgFlgFromUInt32(IntPtr lParam)
        {
            string ret = "";

            //2進数に変換
            int num = (int)(IntPtr.Size == 4 ? lParam.ToInt32() : lParam.ToInt64());
            string strbin = Convert.ToString(num, 2).PadLeft(32, '0');

            //区切りの挿入
            int[] split = { 1, 2, 3, 7, 8, 16 };
            for (int i = 0; i < strbin.Length; i++)
            {
                ret += (split.Contains(i) ? "-" : "") + strbin[i];
            }

            return ret;
        }

        IntPtr hookPtr = IntPtr.Zero;

        //メッセージ表示用テキストボックス
        TextBox textBox = new TextBox
        {
            Multiline = true,
            WordWrap = false,
            ScrollBars = ScrollBars.Both,
            Top = 30,
            Left = 10,
            Width = 500,
            Height = 300,
            Font = new System.Drawing.Font("MS ゴシック", 9F)//等幅
        };

        public Form1()
        {
            InitializeComponent();

            //キー入力用テキストボックス
            TextBox txt = new TextBox { Top = 5, Left = 10, Width = 100 };
            Controls.Add(txt);

            //メッセージ表示用テキストボックス
            Controls.Add(textBox);

            //起動時にフック開始
            Shown += (sender, e) =>
            {
                hookPtr = NativeMethods.SetWindowsHookEx(WH_KEYBOARD, HookProc, IntPtr.Zero, NativeMethods.GetCurrentThreadId());
            };

            //終了時にフック解除
            FormClosing += (sender, e) =>
            {
                NativeMethods.UnhookWindowsHookEx(hookPtr);
                hookPtr = IntPtr.Zero;
            };
        }
    }
}

マウスフック(固有スレッド)

基本的にキーボードフック(固有スレッド)と同じです。

lParamMOUSEHOOKSTRUCT構造体へのポインタになるのでMarshal.PtrToStructureを使用してポインタから構造体に変換しています。

マウス操作のたびに「操作内容」と「マウスカーソルの位置」を表示します。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //フックコールバック関数
            public delegate IntPtr SetWindowsHookExCallback(int nCode, UIntPtr wParam, IntPtr lParam);

            //フックの設定
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr SetWindowsHookEx(int idHook, SetWindowsHookExCallback lpfn, IntPtr hmod, uint dwThreadId);

            //フックの解除
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool UnhookWindowsHookEx(IntPtr hhk);

            //フック情報を次のフックプロシージャに渡す
            [DllImport("user32.dll")]
            public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);

            //自身のスレッドID取得
            [DllImport("Kernel32.dll")]
            public static extern uint GetCurrentThreadId();
        }

        //MOUSEHOOKSTRUCT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct MOUSEHOOKSTRUCT
        {
            public POINT pt;
            public IntPtr hwnd;
            public uint wHitTestCode;
            public UIntPtr dwExtraInfo;
        }

        //POINT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;
        }

        //マウス メッセージの識別子(の一部)
        public enum WM
        {
            MOUSEMOVE = 0x0200,
            LBUTTONDOWN = 0x0201,
            LBUTTONUP = 0x0202,
            LBUTTONDBLCLK = 0x0203,
            RBUTTONDOWN = 0x0204,
            RBUTTONUP = 0x0205,
            RBUTTONDBLCLK = 0x0206,
            MBUTTONDOWN = 0x0207,
            MBUTTONUP = 0x0208,
            MBUTTONDBLCLK = 0x0209,
            MOUSEWHEEL = 0x020A,
            NCMOUSEMOVE = 0x00A0,
            NCLBUTTONDOWN = 0x00A1,
            NCLBUTTONUP = 0x00A2,
            NCLBUTTONDBLCLK = 0x00A3,
            NCRBUTTONDOWN = 0x00A4,
            NCRBUTTONUP = 0x00A5,
            NCRBUTTONDBLCLK = 0x00A6,
            NCMBUTTONDOWN = 0x00A7,
            NCMBUTTONUP = 0x00A8,
            NCMBUTTONDBLCLK = 0x00A9
        }

        //定数の定義
        private const int WH_MOUSE = 7;//マウスフック
        private const int HC_ACTION = 0;

        //---------- Win32API用定義終了 ----------

        //フック プロシージャ
        IntPtr HookProc(int nCode, UIntPtr wParam, IntPtr lParam)
        {
            if (nCode == HC_ACTION)
            {
                //wParam マウス メッセージの識別子
                textBox.AppendText(((WM)wParam.ToUInt32()).ToString().PadRight(16, ' '));

                //lParam MOUSEHOOKSTRUCT 構造体へのポインタ
                MOUSEHOOKSTRUCT mouse = (MOUSEHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MOUSEHOOKSTRUCT));
                textBox.AppendText(mouse.pt.x.ToString() + "," + mouse.pt.y.ToString() + Environment.NewLine);
            }

            return NativeMethods.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
        }

        IntPtr hookPtr = IntPtr.Zero;

        //メッセージ表示用テキストボックス
        TextBox textBox = new TextBox
        {
            Multiline = true,
            WordWrap = false,
            ScrollBars = ScrollBars.Both,
            Top = 10,
            Left = 10,
            Width = 500,
            Height = 300,
            Font = new System.Drawing.Font("MS ゴシック", 9F)//等幅
        };

        public Form1()
        {
            InitializeComponent();

            //メッセージ表示用テキストボックス
            Controls.Add(textBox);

            //起動時にフック開始
            Shown += (sender, e) =>
            {
                hookPtr = NativeMethods.SetWindowsHookEx(WH_MOUSE, HookProc, IntPtr.Zero, NativeMethods.GetCurrentThreadId());
            };

            //終了時にフック解除
            FormClosing += (sender, e) =>
            {
                NativeMethods.UnhookWindowsHookEx(hookPtr);
                hookPtr = IntPtr.Zero;
            };
        }
    }
}

参考URL

キーボードフック(グローバル)

グローバルフックを使用するために、フックプロシージャを含むDLLを作成します。ここでは単純にキーボード操作を無効にするフックプロシージャを作成します。

Win32 API キーストロークメッセージをフックする - s-kita’s blog

フックプロシージャのインストール方法としてはフックプロシージャのインストールと解放によると

  1. アプリケーション側がSetWindowsHookExを呼び出す方法
    1. LoadLibraryを使用してDLLモジュールのハンドルを取得します。
    2. GetProcAddressを使用してフックプロシージャへのポインター取得します。
    3. 最後に、SetWindowsHookExを使用して、フックチェーンにフックプロシージャをインストールします。
  2. DLL内にインストール関数を作成し、それを呼び出す方法

の2つがあると記載されていますが、参考にしたサイトでは2個目の方法を使用していますので、ここでもその方法で作成します。そのためC#で記載していたSetWindowsHookEx UnhookWindowsHookExなどのコードをC言語で記載し、DLLを作成します。フック中のキーボード操作を無効にするようにフック関数の戻り値を1(0以外)にしています。DLLの作成については以下を参照願います。

MinGW+C言語でWindows用Dllを作成する #Windows - Qiita

/*
Hook.cpp
g++ -shared -o Hook.dll Hook.cpp -luser32
cl /LD Hook.cpp User32.lib
*/

#include <windows.h>

// グローバル変数をプロセス間で共有
#ifdef __MINGW64__
// MinGW用
HHOOK g_hHook __attribute__((section("shared"), shared)) = NULL;
#else
// VC用
#pragma data_seg("Shared")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/Section:Shared,rws")
#endif

HINSTANCE g_hInst;

// フックプロシージャ
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode == HC_ACTION)
    {
        return 1; // キー全無効
    }
    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

// フックの開始
extern "C" __declspec(dllexport) BOOL __stdcall StartHook()
{
    g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)HookProc, g_hInst, 0);
    return g_hHook != NULL;
}

// フックの終了
extern "C" __declspec(dllexport) BOOL __stdcall EndHook()
{
    return UnhookWindowsHookEx(g_hHook);
}

BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD ul_reason_for_call,
                      LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        g_hInst = (HINSTANCE)hModule;
        break;
    }
    return TRUE;
}

C#で呼び出します。DLLと同じx64でビルドします。

startボタンを押すとすべてのキーボード操作が無効になります。endボタンを押すと、通常に戻ります。

//Windows フォーム アプリケーション(.NET Framewrok)
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        internal static class NativeMethods
        {
            //アプリケーションと同じ場所にHook.dllを置く
            [DllImport("Hook.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool StartHook();

            [DllImport("Hook.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool EndHook();
        }

        //フック中かどうか
        bool processing = false;

        //フック開始/終了ボタン
        Button button_start, button_end;

        public Form1()
        {
            InitializeComponent();

            //フック開始ボタン(キー操作を無効化)
            button_start = new Button { Text = "start", Top = 5, Left = 5 };
            button_start.Click += (sender, e) =>
            {
                if (!processing)
                {
                    //フック開始
                    bool ret = NativeMethods.StartHook();

                    //フック開始が成功した場合はボタンの有効無効切り替え
                    if (ret)
                    {
                        processing = true;
                        button_start.Enabled = !processing;
                        button_end.Enabled = processing;
                    }

                    //フック開始の成功失敗を表示
                    Text = "Hook Start " + (ret ? "OK" : "NG");
                }
            };

            //フック終了ボタン(キー操作を通常に戻す)
            button_end = new Button { Text = "end", Top = 35, Left = 5, Enabled = false };
            button_end.Click += (sender, e) =>
            {
                if (processing)
                {
                    //フック解除
                    bool ret = NativeMethods.EndHook();

                    //フック解除が成功した場合はボタンの有効無効切り替え
                    if (ret)
                    {
                        processing = false;
                        button_start.Enabled = !processing;
                        button_end.Enabled = processing;
                    }

                    //フック解除の成功失敗を表示
                    Text = "Hook End " + (ret ? "OK" : "NG");
                }
            };

            //終了時にフック中であればフック解除
            FormClosing += (sender, e) => { if (processing) NativeMethods.EndHook(); };

            //適当な入力用テキストボックス
            TextBox textBox = new TextBox { Top = 65, Left = 5, };

            Controls.Add(button_start);
            Controls.Add(button_end);
            Controls.Add(textBox);
        }
    }
}

参考URL

マウスフック(グローバル)

キーボードフック(グローバル)と同様です。本来はグローバルフックではなく対象のスレッドに限定すべきですが、使用例としてグローバルフックとしています。

フック中にエクスプローラの余白をダブルクリックしたときに、上の階層に行くようにします。

  1. 簡易的なアプリケーションのため同じ場所で2回クリックされた場合、クリックの時間間隔によらずダブルクリックと判定しています。マウス メッセージの識別子を確認し、マウスの右ボタンのダウンWM_LBUTTONDOWNとアップWM_LBUTTONUPが2回繰り返されたときにダブルクリックと判定します。但しマウスカーソルの移動WM_MOUSEMOVEがほぼない状態に限っています。
  2. ダブルクリック判定をしたときに
  3. 上記が確認できた場合、Alt+↑を送信し、上の階層に行くようにします(非アクティブウィンドウにキーを送信参照)。

呼び出す側のC#ソースコードはキーボードフック(グローバル)と同一です。startボタンでフック開始、endボタンでフック終了です。

/*
Hook.cpp
g++ -shared -o Hook.dll Hook.cpp -luser32 -lgdi32
cl /LD Hook.cpp User32.lib Gdi32.lib
*/

#ifndef UNICODE
#define UNICODE
#endif
#define PSAPI_VERSION 2
#include <windows.h>
#include <psapi.h>

// グローバル変数をプロセス間で共有
#ifdef __MINGW64__
// MinGW用
HHOOK g_hHook __attribute__((section("shared"), shared)) = NULL;
#else
// VC用
#pragma data_seg("Shared")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/Section:Shared,rws")
#endif

HINSTANCE g_hInst;

// マウスカーソル位置の色
COLORREF CursorPointColor()
{
    // マウスカーソル位置
    POINT point;
    GetCursorPos(&point);

    // デバイス コンテキストを取得
    HDC hdc = GetDC(NULL);

    // マウスカーソル位置の色を取得
    COLORREF color = GetPixel(hdc, point.x, point.y);

    // デバイス コンテキストを解放
    ReleaseDC(NULL, hdc);

    return color;
}

void DCAction()
{
    // マウスカーソル位置
    POINT point;
    GetCursorPos(&point);

    // マウスカーソル位置のウィンドウ
    HWND hWnd = WindowFromPoint(point);

    // クラス名
    wchar_t className[MAX_PATH];
    GetClassName(hWnd, className, _countof(className));

    // エクスプローラのファイルビュー
    if (wcscmp(className, L"DirectUIHWND") == 0)
    {
        // プロセスIDを取得
        DWORD processId;
        GetWindowThreadProcessId(hWnd, &processId);

        // プロセスオブジェクトを開く
        HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, processId);

        if (hProcess != NULL)
        {
            // 実行ファイルパスを取得
            wchar_t lpFilename[MAX_PATH];
            DWORD lpdwSize = 0;
            GetModuleFileNameEx(hProcess, NULL, lpFilename, _countof(lpFilename));

            // 実行ファイル名を取得
            wchar_t drive[_MAX_DRIVE];
            wchar_t dir[_MAX_DIR];
            wchar_t fname[_MAX_FNAME];
            wchar_t ext[_MAX_EXT];
            _wsplitpath_s(lpFilename, drive, dir, fname, ext);

            // explorer.exeの場合
            if (wcscmp(fname, L"explorer") == 0 && wcscmp(ext, L".exe") == 0)
            {
                // マウスカーソル位置の色が白の場合(余白の場合)
                if (CursorPointColor() == 0x00FFFFFF)
                {
                    // Alt+↑を送信
                    keybd_event(VK_MENU, 0, 0, 0);
                    keybd_event(VK_UP, 0, 0, 0);
                    keybd_event(VK_UP, 0, KEYEVENTF_KEYUP, 0);
                    keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0);
                }
            }

            // オブジェクトハンドルを閉じる
            CloseHandle(hProcess);
        }
    }
}

LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    // マウス移動回数
    static int counter = 0;

    // down:1->up:2->down:3->up:4
    static int mode = 0;

    if (nCode == HC_ACTION)
    {
        if (wParam == WM_MOUSEMOVE)
        {
            // マウス移動をカウントしていく
            if (counter <= 255 && mode > 0)
            {
                counter++;
            }

            // 移動5回以上でmodeリセット
            if (counter >= 5)
            {
                mode = 0;
            }
        }
        if (wParam == WM_LBUTTONDOWN)
        {
            if (mode == 0)
            {
                // シングルクリックダウン
                mode = 1;
                counter = 0;
            }
            else if (mode == 2 && counter < 5)
            {
                // マウスカーソル位置の色が白の場合
                if (CursorPointColor() == 0x00FFFFFF)
                {
                    // シングルクリックアップ
                    mode = 3;
                }
            }
        }
        if (wParam == WM_LBUTTONUP)
        {
            if (mode == 1 && counter < 5)
            {
                // ダブルクリックダウン
                mode = 2;
            }
            else if (mode == 3 && counter < 5)
            {
                // ダブルクリックアップ->ダブルクリック判定
                DCAction();
                mode = 0;
            }
        }
    }
    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

extern "C" __declspec(dllexport) BOOL __stdcall StartHook()
{
    g_hHook = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)HookProc, g_hInst, 0);
    return g_hHook != NULL;
}

extern "C" __declspec(dllexport) BOOL __stdcall EndHook()
{
    return UnhookWindowsHookEx(g_hHook);
}

BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD ul_reason_for_call,
                      LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        g_hInst = (HINSTANCE)hModule;
        break;
    }
    return TRUE;
}

参考URL

ローレベルキーボード/マウスフック(グローバル)

グローバルフックの場合、グローバルフックプロシージャは任意のアプリケーションのコンテキストで呼び出されるため、DLLモジュール内にある必要があります。下記のサイトにそのように記載されています。

Hooks Overview - Win32 apps | Microsoft Learn

A global hook procedure can be called in the context of any application in the same desktop as the calling thread, so the procedure must be in a separate DLL module.

但しWH_KEYBOARD_LLWH_MOUSE_LLの場合は、グローバフルックでありながら、フックをインストールしたスレッドのコンテキストで呼び出されると書かれています。

LowLevelMouseProc callback function - Win32 apps | Microsoft Learn
LowLevelKeyboardProc callback function - Win32 apps | Microsoft Learn

This hook is called in the context of the thread that installed it.

実際に確認するために、まずはWH_KEYBOARD_LLではなくWH_KEYBOARDを利用したキーボードフック(グローバル)のDLLを以下のコードから作成し直します。「キーボード操作を無効にする」という動作に変更はありません。但し、DLLがロード/アンロードされたときやフックプロシージャが呼ばれたときにメッセージボックスを表示することで、いつ何が行われたかが分かるようにしています。

第161章
DllMain エントリ ポイント (Process.h) - Win32 apps | Microsoft Learn

/*
Hook.cpp
g++ -shared -o Hook.dll Hook.cpp -luser32
cl /LD Hook.cpp User32.lib
*/

#ifndef UINCODE
#define UNICODE
#endif

#include <windows.h>
#include <stdio.h>

// グローバル変数をプロセス間で共有
#ifdef __MINGW64__
// MinGW用
HHOOK g_hHook __attribute__((section("shared"), shared)) = NULL;
int counter __attribute__((section("shared"), shared)) = 0;
#define MOJI_ENV L"MinGW"
#else
// VC用
#pragma data_seg("Shared")
HHOOK g_hHook = NULL;
int counter = 0;
#pragma data_seg()
#pragma comment(linker, "/Section:Shared,rws")
#define MOJI_ENV L"MSVC"
#endif

// デバッグ用
#define OUTPUT_FOR_DEBUG
#ifdef __MINGW64__
// MinGW用
#define MOJI_ENV L"MinGW"
#else
// VC用
#define MOJI_ENV L"MSVC"
#endif

HINSTANCE g_hInst;

// フックプロシージャ
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode == HC_ACTION)
    {
#ifdef OUTPUT_FOR_DEBUG
        if (g_hHook == NULL)
        {
            MessageBox(NULL, L"NG", MOJI_ENV, MB_OK);
        }
        else
        {
            MessageBox(NULL, L"OK", MOJI_ENV, MB_OK);
        }
#endif
        return 1; // キー全無効
    }
    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

// フックの開始
extern "C" __declspec(dllexport) BOOL __stdcall StartHook()
{
    g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)HookProc, g_hInst, 0);
    return g_hHook != NULL;
}

// フックの終了
extern "C" __declspec(dllexport) BOOL __stdcall EndHook()
{
    return UnhookWindowsHookEx(g_hHook);
}

BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD ul_reason_for_call,
                      LPVOID lpReserved)
{
#ifndef OUTPUT_FOR_DEBUG
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        g_hInst = (HINSTANCE)hModule;
        break;
    }
#else
    counter++;
    wchar_t str_counter[256];
    swprintf_s(str_counter, L"%d", counter);
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        g_hInst = (HINSTANCE)hModule;
        if (lpReserved == NULL)
        {
            MessageBox(NULL, L"DLLが仮想アドレス空間にロードされた(動的読み込み)", str_counter, MB_OK);
        }
        else
        {
            MessageBox(NULL, L"DLLが仮想アドレス空間にロードされた(静的読み込み)", str_counter, MB_OK);
        }
        break;
    case DLL_PROCESS_DETACH:
        if (lpReserved == NULL)
        {
            MessageBox(NULL, L"DLLが仮想アドレス空間からアンロードされた", str_counter, MB_OK);
        }
        else
        {
            MessageBox(NULL, L"DLLが仮想アドレス空間からアンロードされた(プロセス終了)", str_counter, MB_OK);
        }
        break;
    }
#endif
    return TRUE;
}

作成したDLLを利用した場合、

  • アプリケーションのstartボタンを押すとDLLがロードされることが分かります。
  • メモ帳を起動してキーを入力すると、入力した時点でDLLがロードされることが分かります。
  • 別のメモ帳を起動してキーを入力すると、入力した時点で同様にDLLがロードされることが分かります。
  • アプリケーションのendボタンを押してもDLLがアンロードされないことが分かります。
  • メモ帳を閉じた場合やアプリケーションが終了したときにDLLがアンロードされることが分かります。
  • 今回の検証からは外れますが、g_hHookをプロセス間で共有せず、単にHINSTANCE g_hInst;と同じ箇所にHHOOK g_hHook = NULL;と宣言した場合、メモ帳がDLLをロードしたとき、g_hHookNULLとなっていることが分かります。

フックプロシージャのインストールと解放に書かれている通り、すべてのアプリケーションのコンテキストでグローバルフックプロシージャが呼び出され、それらのすべてのプロセスに対してLoadLibrary関数が暗黙的に呼び出されています。また、endボタンを押し、UnhookWindowsHookExを呼び出すことでグローバル フック プロシージャを解放できますが、フックプロシージャを含むDLLは解放されません。DLLを読み込んだすべてのプロセスが終了した場合、解放されます。

今度はWH_KEYBOARD_LLで確認してみます。上記コードの

g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)HookProc, g_hInst, 0);

の箇所を、

g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)HookProc, g_hInst, 0);

に変更してDLLを作成し直します。作成したDLLを利用した場合、今度はメモ帳を起動してキー入力をしてもDLLがロードされていないことが分かります。

確認した結果、実際にインストールしたスレッドのコンテキストでのみ呼び出されていると思われるので、DLLは使用せずに、C#のコードだけで実装してみます。なお「DLLが呼ばれないのであれば、グローバルフックの場合でもDLLにしなくてよい」のかどうかは理解できていません。フックの概要の注記に

フックは、各メッセージに対してシステムが実行する必要がある処理の量が増えるので、システムの速度が低下する傾向があります。

グローバル フックは、デバッグ目的でのみ使用する必要があります。それ以外の場合は、それらを避ける必要があります。

とありますので、仮にDLLにしなくてよい、が正しかったとしても、単にエラーがでないから問題ない、ではなく、それ以外の関連する事柄についてきちんと理解したうえで使用したほうがよいと思います。

ローレベルキーボードフック(グローバル)

キーボード操作のたびに「操作内容」と「仮想キーコード」を表示します。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //フックコールバック関数
            public delegate IntPtr SetWindowsHookExCallback(int nCode, UIntPtr wParam, IntPtr lParam);

            //フックの設定
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr SetWindowsHookEx(int idHook, SetWindowsHookExCallback lpfn, IntPtr hmod, uint dwThreadId);

            //フックの解除
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool UnhookWindowsHookEx(IntPtr hhk);

            //フック情報を次のフックプロシージャに渡す
            [DllImport("user32.dll")]
            public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);

            //ウィンドウに関する情報を取得
            //32bit
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
            //64bit
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
        }

        //キーボードメッセージの識別子
        public enum WM
        {
            WM_KEYDOWN = 0x0100,
            WM_KEYUP = 0x0101,
            WM_SYSKEYDOWN = 0x0104,
            WM_SYSKEYUP = 0x0105
        }

        //KBDLLHOOKSTRUCT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct KBDLLHOOKSTRUCT
        {
            public uint vkCode;
            public uint scanCode;
            public uint flags;
            public uint time;
            public UIntPtr dwExtraInfo;
        }

        //定数の定義
        private const int WH_KEYBOARD_LL = 13;//ローレベルキーボードフック
        private const int HC_ACTION = 0;
        private const int GWLP_HINSTANCE = -6;
        private const int GWL_HINSTANCE = -6;

        //---------- Win32API用定義終了 ----------

        //フック プロシージャ
        IntPtr HookProc(int nCode, UIntPtr wParam, IntPtr lParam)
        {
            if (nCode == HC_ACTION)
            {
                //wParam キーボード メッセージの識別子
                textBox.AppendText(((WM)wParam.ToUInt32()).ToString().PadRight(16, ' '));

                //lParam MOUSEHOOKSTRUCT 構造体へのポインタ
                KBDLLHOOKSTRUCT keystruct = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
                textBox.AppendText(((Keys)keystruct.vkCode).ToString() + Environment.NewLine);
            }

            return NativeMethods.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
        }

        IntPtr hookPtr = IntPtr.Zero;

        //メッセージ表示用テキストボックス
        TextBox textBox = new TextBox
        {
            Multiline = true,
            WordWrap = false,
            ScrollBars = ScrollBars.Both,
            Top = 30,
            Left = 10,
            Width = 500,
            Height = 300,
            Font = new System.Drawing.Font("MS ゴシック", 9F)//等幅
        };

        public Form1()
        {
            InitializeComponent();

            //キー入力用テキストボックス
            TextBox txt = new TextBox { Top = 5, Left = 10, Width = 100 };
            Controls.Add(txt);

            //メッセージ表示用テキストボックス
            Controls.Add(textBox);

            //起動時にフック開始
            Shown += (sender, e) =>
            {
                //インスタンスハンドルの取得
                IntPtr hInstance;
                if (IntPtr.Size == 4)//32bit
                {
                    hInstance = (IntPtr)NativeMethods.GetWindowLong(Handle, GWL_HINSTANCE);
                }
                else//64bit
                {
                    hInstance = NativeMethods.GetWindowLongPtr(Handle, GWLP_HINSTANCE);
                }

                hookPtr = NativeMethods.SetWindowsHookEx(WH_KEYBOARD_LL, HookProc, hInstance, 0);
            };

            //終了時にフック解除
            FormClosing += (sender, e) =>
            {
                NativeMethods.UnhookWindowsHookEx(hookPtr);
                hookPtr = IntPtr.Zero;
            };
        }
    }
}

ローレベルマウスフック(グローバル)

マウス操作のたびに「操作内容」と「マウスカーソルの位置」を表示します。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //フックコールバック関数
            public delegate IntPtr SetWindowsHookExCallback(int nCode, UIntPtr wParam, IntPtr lParam);

            //フックの設定
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr SetWindowsHookEx(int idHook, SetWindowsHookExCallback lpfn, IntPtr hmod, uint dwThreadId);

            //フックの解除
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool UnhookWindowsHookEx(IntPtr hhk);

            //フック情報を次のフックプロシージャに渡す
            [DllImport("user32.dll")]
            public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);

            //ウィンドウに関する情報を取得
            //32bit
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
            //64bit
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
        }

        //マウス メッセージの識別子
        public enum WM
        {
            LBUTTONDOWN = 0x0201,
            LBUTTONUP = 0x0202,
            RBUTTONDOWN = 0x0204,
            RBUTTONUP = 0x0205,
            MOUSEMOVE = 0x0200,
            MOUSEWHEEL = 0x020A,
        }

        //MSLLHOOKSTRUCT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct MSLLHOOKSTRUCT
        {
            public POINT pt;
            public uint mouseData;
            public uint flags;
            public uint time;
            public UIntPtr dwExtraInfo;
        }

        //POINT構造体の定義
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;
        }

        //定数の定義
        private const int WH_MOUSE_LL = 14;//ローレベルマウスフック
        private const int HC_ACTION = 0;
        private const int GWLP_HINSTANCE = -6;
        private const int GWL_HINSTANCE = -6;

        //---------- Win32API用定義終了 ----------

        //フック プロシージャ
        IntPtr HookProc(int nCode, UIntPtr wParam, IntPtr lParam)
        {
            if (nCode == HC_ACTION)
            {
                //wParam マウス メッセージの識別子
                textBox.AppendText(((WM)wParam.ToUInt32()).ToString().PadRight(16, ' '));

                //lParam MSLLHOOKSTRUCT 構造体へのポインタ
                MSLLHOOKSTRUCT mousestruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
                textBox.AppendText(mousestruct.pt.x.ToString() + "," + mousestruct.pt.y.ToString() + Environment.NewLine);
            }

            return NativeMethods.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
        }

        IntPtr hookPtr = IntPtr.Zero;

        //メッセージ表示用テキストボックス
        TextBox textBox = new TextBox
        {
            Multiline = true,
            WordWrap = false,
            ScrollBars = ScrollBars.Both,
            Top = 30,
            Left = 10,
            Width = 500,
            Height = 300,
            Font = new System.Drawing.Font("MS ゴシック", 9F)//等幅
        };

        public Form1()
        {
            InitializeComponent();

            //キー入力用テキストボックス
            TextBox txt = new TextBox { Top = 5, Left = 10, Width = 100 };
            Controls.Add(txt);

            //メッセージ表示用テキストボックス
            Controls.Add(textBox);

            //起動時にフック開始
            Shown += (sender, e) =>
            {
                //インスタンスハンドルの取得
                IntPtr hInstance;
                if (IntPtr.Size == 4)//32bit
                {
                    hInstance = (IntPtr)NativeMethods.GetWindowLong(Handle, GWL_HINSTANCE);
                }
                else//64bit
                {
                    hInstance = NativeMethods.GetWindowLongPtr(Handle, GWLP_HINSTANCE);
                }

                hookPtr = NativeMethods.SetWindowsHookEx(WH_MOUSE_LL, HookProc, hInstance, 0);
            };

            //終了時にフック解除
            FormClosing += (sender, e) =>
            {
                NativeMethods.UnhookWindowsHookEx(hookPtr);
                hookPtr = IntPtr.Zero;
            };
        }
    }
}

ウィンドウメニューに項目を追加

システムメニューにメニューを追加する - C#プログラミング | iPentec

Alt+Spaceで出てくるウィンドウメニューに「最前面に表示」メニューを追加します。

//ウィンドウメニュー(システムメニュー)
元のサイズに戻す//position:0
移動//1
サイズ変更//2
最小化//3
最大化//4
---//5
最前面に表示//追加
---//追加
閉じる//6
  1. GetSystemMenuを使用してウィンドウメニューのコピーへのハンドルを取得します。
  2. InsertMenuItemを使用して「区切り線」を追加します。
    1. 第1引数にはGetSystemMenuで取得したメニューへのハンドルを指定します。
    2. 第3引数をtrueにすることで、第2引数でメニュー挿入位置を指定できます。第2引数で指定したメニューの位置の前に挿入されます。今回は「閉じる」の前に区切り線を入れたいので「6」を指定します。
    3. 第4引数にはMENUITEMINFO構造体へのポインタを指定します。MENUITEMINFO構造体にはメニュー項目に対する設定値が入っています。リンク先を見て必要な部分のみ設定します。今回は区切り設定をしたいのでその設定のみを実施します。
  3. InsertMenuItemを使用して「最前面に表示」を追加します。
    1. 第1引数は上記と同様です。
    2. 第2,3引数も上記と同様です。「区切り線」を入れたことで「区切り線」が新たに「6」の位置になっています。この前に入れたいので「6」を指定します。
    3. 第4引数も同様です。今回は「メニュー項目の識別子」と「メニュー項目の文字列」を設定します。注釈に「定義済みのウィンドウ メニュー項目はすべて、0xF000より大きい識別子番号を持っています。」と書かれているので0xF000未満の値で識別子を設定します。今回はMENU_ID_01(0x0001)としています。
  4. 以上でメニュー項目は追加されますが、メニュー表示時や選択時の実装がまだです。ウィンドウプロシージャを利用してメッセージ受信時の処理を記載します。ウィンドウプロシージャはウィンドウに送信されたメッセージを処理するコールバック関数のことです。
  5. WndProc関数は注釈に記載の通り、WindowProc関数に正確に対応していますのでこれをオーバーライドして実装します。
    1. メニュー表示前にWM_INITMENUメッセージが送信されます。このメッセージ受信時にSetMenuItemInfoを使って、現在のTopMost設定から「最前面に表示」のチェック状態を設定します。
    2. メニュー選択時にWM_SYSCOMMANDメッセージが送信されます。メニュー識別子がMENU_ID_01(0x0001)の場合TopMost設定を切り替えます。
//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //ウィンドウメニューへのハンドルを取得
            [DllImport("user32.dll")]
            public static extern IntPtr GetSystemMenu(IntPtr hWnd, [MarshalAs(UnmanagedType.Bool)] bool bRevert);

            //新しいメニュー項目を挿入
            [DllImport("user32.dll", ExactSpelling = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool InsertMenuItemW(IntPtr hmenu, uint item, [MarshalAs(UnmanagedType.Bool)] bool fByPosition, ref MENUITEMINFO lpmi);

            //メニュー項目の設定を変更
            [DllImport("user32.dll", ExactSpelling = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool SetMenuItemInfoW(IntPtr hmenu, uint item, [MarshalAs(UnmanagedType.Bool)] bool fByPositon, ref MENUITEMINFO lpmii);
        }

        //MENUITEMINFO構造体の定義
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct MENUITEMINFO
        {
            public uint cbSize;//この構造体のサイズ
            public uint fMask;//取得または設定する項目
            public uint fType;//メニュー項目の種類
            public uint fState;//メニュー項目の状態
            public uint wID;//メニュー項目の識別子
            public IntPtr hSubMenu;
            public IntPtr hbmpChecked;
            public IntPtr hbmpUnchecked;
            public UIntPtr dwItemData;
            public string dwTypeData;//メニュー項目の内容
            public uint cch;
            public IntPtr hbmpItem;
        }

        //定数の定義
        //fMask
        private const uint MIIM_FTYPE = 0x00000100;//fTypeメンバーを取得または設定
        private const uint MIIM_ID = 0x00000002;//wID メンバーを取得または設定
        private const uint MIIM_STATE = 0x00000001;//fState メンバーを取得または設定
        private const uint MIIM_STRING = 0x00000040;//dwTypeData メンバーを取得または設定

        //fType
        private const uint MFT_SEPARATOR = 0x00000800;//区切り線

        //fState
        private const uint MFS_CHECKED = 0x00000008;//チェックあり
        private const uint MFS_UNCHECKED = 0x000000000;//チェックなし

        //メッセージ
        private const int WM_SYSCOMMAND = 0x0112;//コマンド選択時、ボタン選択時
        private const int WM_INITMENU = 0x0116;//メニュー表示前

        //---------- Win32API用定義終了 ----------

        private const uint MENU_ID_01 = 0x0001;//追加するメニューの識別子
        IntPtr hSysMenu;//ウィンドウ メニューへのハンドル

        public Form1()
        {
            InitializeComponent();

            //ウィンドウ メニューへのハンドルを取得
            hSysMenu = NativeMethods.GetSystemMenu(Handle, false);

            //区切りメニューを追加
            MENUITEMINFO item_sep = new MENUITEMINFO
            {
                cbSize = (uint)Marshal.SizeOf(typeof(MENUITEMINFO)),
                fMask = MIIM_FTYPE,
                fType = MFT_SEPARATOR
            };
            NativeMethods.InsertMenuItemW(hSysMenu, 6, true, ref item_sep);

            //最前面表示メニューを追加
            MENUITEMINFO item = new MENUITEMINFO
            {
                cbSize = (uint)Marshal.SizeOf(typeof(MENUITEMINFO)),
                fMask = MIIM_ID | MIIM_STRING,
                wID = MENU_ID_01,
                dwTypeData = "最前面に表示"
            };
            NativeMethods.InsertMenuItemW(hSysMenu, 6, true, ref item);
        }

        //WindowProcをオーバーライド
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                //メニュー表示前にチェック状態を確定
                case WM_INITMENU:
                    MENUITEMINFO item = new MENUITEMINFO
                    {
                        cbSize = (uint)Marshal.SizeOf(typeof(MENUITEMINFO)),
                        fMask = MIIM_STATE,
                        fState = TopMost ? MFS_CHECKED : MFS_UNCHECKED
                    };
                    NativeMethods.SetMenuItemInfoW(hSysMenu, MENU_ID_01, false, ref item);
                    break;

                //メニュー選択時
                case WM_SYSCOMMAND:
                    //メニュー項目の識別子を取得
                    uint menuid = (uint)(m.WParam.ToInt32() & 0xFFFF);
                    //「最前面に表示」メニューの場合、最前面状態を切り替え
                    if (menuid == MENU_ID_01)
                    {
                        TopMost = !TopMost;
                    }
                    break;
            }

            //通常のウィンドウプロシージャ処理
            base.WndProc(ref m);
        }
    }
}

参考URL

クリップボードの監視

クリップボードの使用 - Win32 apps | Microsoft Learn

ウィンドウハンドルをクリップボード形式リスナーに登録するとクリップボードの内容が変更されたときにWM_CLIPBOARDUPDATEメッセージが投稿されます。

下記の例ではクリップボード内の文字列が変更されたときに、その文字列を表示するようにしています。終了するときはタスクトレイアイコンを右クリックして「終了」を選択します。なお、フォームを表示させないようにするためにProgram.csも変更しています。

  1. 起動時にAddClipboardFormatListenerを使用してクリップボード形式リスナーに登録します。

  2. 動作中、ウィンドウプロシージャ(ウィンドウメニューに項目を追加を参照)を利用してWM_CLIPBOARDUPDATEメッセージを確認しています。メッセージが来たとき、クリップボードに含まれている内容が文字列であれば、その文字列をShowBalloonTipメソッドを使って表示するようにしています。このとき、クリップボード内にあるデータ形式(下記参照)の数が0ではないことと現在のテキストとは異なることを確認したうえで表示しています。これは例えば以下のようにコピーされるとこととメッセージが送られることが必ずしも一致しないためです。

    • Excelの場合、1回のコピー操作のときにWM_CLIPBOARDUPDATEメッセージが連続して複数送られます。また、貼り付けのときにも送られます。Excelを閉じたときにも送られることがあります。
    • その他のソフトウェアでもコピー以外の操作のときにメッセージが送られることを確認しています。

    何がトリガーとなってメッセージが送られるのかは未確認ですが、少なくとも単にOpenClipboardCloseClipboardを呼び出した場合でも、CloseClipboardが呼ばれた時点でメッセージが送られていました。

  3. 終了時にRemoveClipboardFormatListenerを使用してクリップボード形式リスナーから登録を削除します。

//Program.csも以下のように変更
using System;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    internal static class Program
    {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Form1 form1 = new Form1();
            Application.Run();
        }
    }
}
//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //クリップボード形式リスナーリストに追加
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool AddClipboardFormatListener(IntPtr hwnd);

            //クリップボード形式リスナーリストから削除
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool RemoveClipboardFormatListener(IntPtr hwnd);

            //クリップボード内にあるデータ形式の数
            [DllImport("user32.dll")]
            public static extern int CountClipboardFormats();
        }

        //定数の定義
        private const int WM_CLIPBOARDUPDATE = 0x031D;

        //---------- Win32API用定義終了 ----------

        //トレイアイコン
        private NotifyIcon notifyIcon;

        //クリップボード形式リスナーとして登録済みかどうか
        bool monitoring = false;

        //クリップボード内の文字列
        string cliptext = "";

        public Form1()
        {
            InitializeComponent();

            //終了メニュー
            ToolStripMenuItem ExitToolStripMenuItem = new ToolStripMenuItem { Text = "終了" };
            ExitToolStripMenuItem.Click += (sender, e) =>
            {
                //トレイアイコンを非表示にする
                notifyIcon.Visible = false;

                //クリップボード形式リスナー一覧から削除
                if (monitoring)
                {
                    NativeMethods.RemoveClipboardFormatListener(Handle);
                }

                //終了
                Application.Exit();
            };

            //コンテキストメニュー
            ContextMenuStrip contextMenuStrip = new ContextMenuStrip();
            contextMenuStrip.Items.Add(ExitToolStripMenuItem);

            //トレイアイコンの作成
            notifyIcon = new NotifyIcon(components)
            {
                ContextMenuStrip = contextMenuStrip,
                Icon = Icon,
                Text = "Clipboard Format Listener",
                Visible = true
            };

            //クリップボード形式リスナー一覧に追加
            monitoring = NativeMethods.AddClipboardFormatListener(Handle);
        }

        //WindowProcをオーバーライド
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_CLIPBOARDUPDATE:
                    //クリップボード内にあるデータ形式の数がゼロでなくかつテキストが含まれている場合
                    if (NativeMethods.CountClipboardFormats() != 0 && Clipboard.ContainsText())
                    {
                        //クリップボード内のテキスト取得
                        string ret = Clipboard.GetText();

                        //現在のテキストと異なる場合
                        if (ret != cliptext)
                        {
                            cliptext = ret;
                            if (cliptext != "")
                            {
                                notifyIcon.ShowBalloonTip(1000, "", Clipboard.GetText(), ToolTipIcon.None);
                            }
                        }
                    }
                    break;
            }

            //通常のウィンドウプロシージャ処理
            base.WndProc(ref m);
        }
    }
}

クリップボードのデータ形式について

例えばExcelでセルのコピーをしたとき、クリップボードにはコピーされた情報が複数のデータ形式で格納されます。例えば単純なテキストデータ、CSV形式のデータ、セル範囲の画像データなどです。クリップボードのフォーマットに記載の通り、同じ情報を異なる複数の形式で表しています。この形式にはシステムで定義されている標準クリップボード形式 だけでなく、アプリケーションが独自で作成した登録済みクリップボード形式などもあります。

以下はクリップボードの内容が変わるたびにクリップボードに含まれるデータ形式の一覧を表示します。今回はフォームを表示しますので、上記でProgram.csを変更した場合は元に戻すか、新しくプロジェクトを作成しなおします。

//Windows フォーム アプリケーション(.NET Framewrok)
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //---------- Win32API用定義開始 ----------

        //Windows APIの定義
        internal static class NativeMethods
        {
            //クリップボード形式リスナーリストに追加
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool AddClipboardFormatListener(IntPtr hwnd);

            //クリップボード形式リスナーリストから削除
            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool RemoveClipboardFormatListener(IntPtr hwnd);

            //クリップボード内にあるデータ形式の数
            [DllImport("user32.dll")]
            public static extern int CountClipboardFormats();

            //クリップボードで現在使用できるデータ形式を取得
            [DllImport("user32.dll")]
            public static extern uint EnumClipboardFormats(uint format);

            //指定した登録済み形式の名前をクリップボードから取得
            [DllImport("user32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetClipboardFormatName(uint format, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder lpszFormatName, int cchMaxCount);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool OpenClipboard(IntPtr hWnd);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool CloseClipboard();
        }

        //定数の定義
        private const int WM_CLIPBOARDUPDATE = 0x031D;

        //標準クリップボード形式
        public enum CF
        {
            CF_TEXT = 1,
            CF_BITMAP = 2,
            CF_METAFILEPICT = 3,
            CF_SYLK = 4,
            CF_DIF = 5,
            CF_TIFF = 6,
            CF_OEMTEXT = 7,
            CF_DIB = 8,
            CF_PALETTE = 9,
            CF_PENDATA = 10,
            CF_RIFF = 11,
            CF_WAVE = 12,
            CF_UNICODETEXT = 13,
            CF_ENHMETAFILE = 14,
            CF_HDROP = 15,
            CF_LOCALE = 16,
            CF_DIBV5 = 17,
            CF_OWNERDISPLAY = 0x0080,
            CF_DSPTEXT = 0x0081,
            CF_DSPBITMAP = 0x0082,
            CF_DSPMETAFILEPICT = 0x0083,
            CF_DSPENHMETAFILE = 0x008E
        }

        //---------- Win32API用定義終了 ----------

        //出力用テキストボックス
        TextBox textBox = new TextBox
        {
            Multiline = true,
            Dock = DockStyle.Fill,
            ScrollBars = ScrollBars.Both,
            WordWrap = false
        };

        //クリップボード形式リスナーとして登録済みかどうか
        bool monitoring = false;

        public Form1()
        {
            InitializeComponent();

            TopMost = true;
            Controls.Add(textBox);

            //クリップボード形式リスナー一覧に追加
            monitoring = NativeMethods.AddClipboardFormatListener(Handle);

            //終了時にクリップボード形式リスナー一覧から削除
            FormClosing += (sender, e) =>
            {
                if (monitoring)
                {
                    NativeMethods.RemoveClipboardFormatListener(Handle);
                }
            };
        }

        //WindowProcをオーバーライド
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_CLIPBOARDUPDATE:
                    StringBuilder sb = new StringBuilder();
                    sb.Append("--------------------------------------- ");
                    sb.Append(DateTime.Now.ToString("HH:mm:ss.fff "));
                    sb.Append(" / データ形式の数");
                    sb.Append(NativeMethods.CountClipboardFormats().ToString());
                    sb.Append(Environment.NewLine);

                    if (NativeMethods.OpenClipboard(IntPtr.Zero))
                    {
                        //データ形式の数
                        int counter = 0;

                        //データ形式(を数値で表したもの)
                        uint ret = 0;

                        //現在クリップボード内にあるデータ形式を取得
                        for (; ; )
                        {
                            //使用可能なデータ形式を取得(初回の引数は0)
                            ret = NativeMethods.EnumClipboardFormats(ret);

                            //失敗または全取得完了で終了
                            if (ret == 0)
                            {
                                break;
                            }

                            //データ形式の数のカウントアップと出力
                            counter++;
                            sb.Append(counter.ToString("D3"));
                            sb.Append(" : ");

                            //データ形式の名称の取得
                            var buffer = new StringBuilder(256);
                            int length = NativeMethods.GetClipboardFormatName(ret, buffer, buffer.Capacity);

                            //データ形式の名称の出力
                            if (length == 0)
                            {
                                if (Enum.IsDefined(typeof(CF), (int)ret))//標準クリップボード形式
                                {
                                    sb.Append(((CF)ret).ToString());
                                }
                                else//その他の形式
                                {
                                    sb.Append(ret.ToString());
                                }
                            }
                            else//登録済クリップボード形式
                            {
                                sb.Append(buffer.ToString());
                            }
                            sb.Append(Environment.NewLine);
                        }
                        NativeMethods.CloseClipboard();
                    }

                    //テキストボックスに表示
                    textBox.AppendText(sb.ToString());
                    break;
            }

            //通常のウィンドウプロシージャ処理
            base.WndProc(ref m);
        }
    }
}
149
200
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
149
200

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?