はじめに
対象とする読者について
本記事の対象者としては以下のような人を想定しています。
- C#でこれからWin32APIを使ってみたい。
- C言語のことがあまりよく分かっていない。
- 今までは適当に使っていたので一度きちんと理解したい。
自分が同じような状況であったため、一から調べて整理してみました。自分が理解した順番や内容で記載することで、また、具体的な使用例によってできることの広さや動作を感じ取ってもらうことで、理解の助けになればと思っています。
但し、分かっている人からすると冗長な説明になっている部分や好ましくない内容、正確性に欠ける内容などもあると思います。実際に使用する場合はその点にご留意願います。
Win32APIについて
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リンク一覧
- アンマネージ コードとの相互運用 - .NET Framework | Microsoft Learn
- アンマネージ DLL 関数の処理 - .NET Framework | Microsoft Learn
- 相互運用マーシャリング - .NET Framework | Microsoft Learn
- ネイティブ相互運用性 - .NET | Microsoft Learn
- 言語間の相互運用性 | Microsoft Learn
- System.Runtime.InteropServices 名前空間 | Microsoft Learn
Microsoft Learn 日本語⇔英語切り替え
Microsoft Learnを見ているとき、日本語と英語を切り替えながら読みたいときがあります。以下のブラウザ拡張機能を使うとページ右下に言語を切り替えるボタン(リンク)が追加されます。
- 「en-us と他の言語の切り替え」( Chrome/Edge🡭 Firefox🡭 )をインストールします。
- オプション画面を開いて「ja-jp」を選択してSaveします。
- 適当なMicrosoft Learnのページを開くとページ右下に
en-us
ボタンが表示されます。 - ボタンをクリックするか、右クリックしてリンクを新しいタブで開くを選択して英語サイトを開きます。
- 英語サイトの場合でも同様です。
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);
}
-
System.Runtime.InteropServices
をusing
します。-
System.Runtime.InteropServices
はCOM 相互運用機能とプラットフォーム呼び出しサービスをサポートするさまざまなメンバーを提供します。
-
-
internal
キーワードを付けたNativeMethods
クラス内に定義します。-
public
にしてはいけません。
CA1401: P/Invoke を参照可能にしない (コード分析) - .NET | Microsoft Learn -
NativeMethods
クラス内に定義します。
CA1060: P-Invoke を NativeMethods クラスに移動します (コード分析) - .NET | Microsoft Learn
-
-
DllImport
を使用してDLLと関数を指定します。
今回の場合は、SetWindowTextページの要件(最下部)の箇所を見てuser32.dll
であることを確認します。 -
関数名を記載します(末尾のA,W参照)。
-
関数を
static
、extern
修飾子でマークします。
アンマネージ DLL 関数の処理 - .NET Framework | Microsoft Learn -
引数を設定します。
関数を定義する手順
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
関数名にはSetWindowTextA
やSetWindowTextW
のように末尾がA,Wで終わるものがあります。
A
: ANSI版(つまり古い)
W
: Unicode版
-
FunctionA
とFunctionW
がある場合は末尾からA
,W
を取ったFunction
を使用します。 - 実際には
Function
は存在しません。マクロ定義により、プラットフォームに応じてFunctionA
とFunctionW
のいずれかが呼び出されます。 - 使用する場合はUnicodeであることを明示します。
- パフォーマンスを少しでも上げたい場合は、
ExactSpelling = true
と組み合わせて、直接FunctionW
を指定します。(CharSet/ExactSpelling参照)
例)SetWindowTextAとSetWindowTextWの場合
- 末尾から
A
,W
を取ったSetWindowText
を使用します。 - 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
のように記載を変える必要があります。
- 変数名についてはそのまま流用します。
- 型については対応するC#での記載に変更します。詳細は型を参照。
定数の設定
関数で使用する定数(ごみ箱の中身をクリアのSHERB_NOCONFIRMATION
など)を定義する必要があります。
定数値を調べたい場合は、
-
Microsoft Learnで検索して確認します。
-
HWND_TOPMOST -1
のように直接記載があるものはその値を使用します。 - 列挙型(
enum
)の場合は、明示がない限りゼロ始まりです。例えばDWMWINDOWATTRIBUTEであればDWMWA_NCRENDERING_ENABLED
が0、DWMWA_NCRENDERING_POLICY
が1になります。DWMWA_CAPTION_COLOR
はDWMWA_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, //... } ;
-
- 見つからない場合は、MinGW-w64を開きます。
-
MingW-W64-builds
をdownloadします。 - includeフォルダ(
mingw32\i686-w64-mingw32\include
)のみ解凍します。 - 解凍先フォルダをVSCodeで開いて調べたい値を検索します。
型
整数など / ブーリアン / 文字列
ポインタ、配列 / コールバック関数へのポインタ / 任意の型へのポインタ
構造体、共用体
型の調べ方
-
型の定義(範囲)を確認します。
Windows Data Types (BaseTsd.h) - Win32 apps | Microsoft Learn
例えば、DWORD
であれば、-
DWORD
を検索します。 -
typedef unsigned long DWORD;
のためunsigned long
と同義。 - Data Type Ranges | Microsoft Learnを確認します。
-
unsigned long
は4バイトで 0 ~ 4,294,967,295
例えば、
WPARAM
であれば、-
WPARAM
を検索します。 -
typedef UINT_PTR WPARAM;
のためUINT_PTR
と同義。 -
UINT_PTR
を検索します。 - 32ビットか64ビットかによって定義が変わります。
34bitであればunsigned int
なので0 ~ 4,294,967,295
64bitであればunsigned __int64
なので0 ~ 18,446,744,073,709,551,615
-
-
対応するC#の型を確認します。
整数数値型 - C# リファレンス - C# | Microsoft Learn
プラットフォーム呼び出しのデータ型
例えば、DWORD
であれば、- 0 ~ 4,294,967,295なので、
uint
例えば、
WPARAM
であれば、- 34bitであれば0 ~ 4,294,967,295なので
uint
、64bitであれば0 ~ 18,446,744,073,709,551,615なのでulong
- つまりポインターの有効桁数の型なので
UIntPtr
。
- 0 ~ 4,294,967,295なので、
整数など
データ型 | 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 orunsigned __int64
|
nuint |
UIntPtr |
4(32) or 8(64) |
SIZE_T ULONG_PTR
|
unsigned long orunsigned __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があるため、BOOLEAN
、BOOL
いずれであっても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++の相互運用のための属性の設定方法 | C# プログラミング解説
- ブール値フィールドのマーシャリングのカスタマイズ
- 一般的な Windows のデータ型
- レガシ コード分析のルール セットを構成する - Visual Studio (Windows) | Microsoft Learn
文字列
データ型 | 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文字列の使用例」において、
-
CharSet = CharSet.Unicode
をCharSet = CharSet.Ansi
に変更 -
[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
- .NET TIPS Win32 APIやDLL関数に文字列や文字列バッファを渡すには? - C# - @IT
- 文字列に対する既定のマーシャリング - .NET Framework | Microsoft Learn
- 文字列の操作 - Win32 apps | Microsoft Learn
- NuGet Gallery | System.Buffers 4.5.1
ポインタ、配列
- ほとんどのポインター型名は、プレフィックス
P
またはLP
で始まります。これらは値渡しではなく、参照渡しとなります。入出力で使う場合はref
、出力を受け取る場合はout
とします。 - 参照型の配列は、既定では In パラメーターとして渡されます。呼び出し元が結果を受け取るためには、明示的にInAttributeとOutAttributeを適用する必要があります。
-
PWSTR
(P
ointer toW
ideSTR
ing)などの文字列の場合は文字列を参照して設定します。
例 | 説明 | 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
- さまざまな型の配列のマーシャリング - .NET Framework | Microsoft Learn
- farポインタとポインタの違い - simotin13's message
- farポインタを理解する(目指せガンダムエンジニア) - 少ないリソースを酷使する
- 最近プログラムの勉強をしています「far」[near]の意味がよく... - Yahoo!知恵袋
- メモリ モデル
- C言語ポインタの far と near の違い。 (FarNear - MemoWiki v5)
- 3.C/C言語
コールバック関数へのポインタ
例えば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
- デリゲート - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
- コールバック メソッドとしてのデリゲートのマーシャ リング - .NET Framework | Microsoft Learn
- プラットフォーム呼び出し (P/Invoke) - .NET | Microsoft Learn
- 【C#】UIスレッド以外からUIのコントロールを操作する │ FPGA完全に理解した
- 【C#】delegate, Action, Funcについて 多分一番易しい解説 │ FPGA完全に理解した
任意の型へのポインタ
ウィンドウハンドル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
- ポインタ | C/C++ の関数における値渡し、ポインタ渡しおよび参照渡しについて
- 【C言語/C++】データ型のサイズ・範囲の一覧【32bit/64bit環境】 | MaryCore
- あなたホントに64ビットを説明できますか?:64ビットコンピューティング最前線(1/2 ページ) - ITmedia エンタープライズ
- 64ビットになると何が変わる?――64ビットプログラミングのデータモデル:64ビットコンピューティング最前線(1/2 ページ) - ITmedia エンタープライズ
- C#でポインタを使用する方法 | C# プログラミング解説
構造体、共用体
-
ここでは一番簡単な例で説明しますが、
SendInput
のINPUT構造体のように、構造体のメンバとして更に構造体や共用体があるような入れ子の場合も同様です。(SendInputを参照) -
詳細は下記のURLを参照。
クラス、構造体、および共用体のマーシャリング - .NET Framework | Microsoft Learn
構造体のマーシャリングのカスタマイズ - .NET | Microsoft Learn
構造体
StructLayoutAttribute
でLayoutKind
をSequential
に設定します。これにより各メンバーが出現する順番でメモリ内に順次配列されます。メンバーを定義する場合は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;
}
共用体
構造体として定義したうえで、StructLayoutAttribute
でLayoutKind
をExplicit
に設定し、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
指定ですので末尾がW
のSetWindowTextW
が呼ばれますが、直接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 型です。
のためdwAttribute
にDWMWA_EXTENDED_FRAME_BOUNDS
を設定しpvAttribute
にRECT
型へのポインタを設定します。
//第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);
[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)
と書かれているものは、- Visual Studio 2022を起動し、「新しいプロジェクトの作成」
- 「Windows フォーム アプリケーション(.NET Framewrok)」を選択し次へ
- プロジェクト名「WindowsFormsApp1」、フレームワーク「.NET Framework 4.8」として次へ
- Form1.csを右クリックして「コードの表示」
- すべてのコードを記載のソースコードに置き換え
-
x64
指定のあるものは構成マネージャでプラットフォームをx64
に - 「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の隙間ができる
-
Re1: フォームの表示がおかしいです。
回答:その隙間はほぼ透明ではあるがフォームのボーダー的な扱いで、例えばフォームのサイズ変更ができます。また下に見えているウィンドウへのクリックになりません。 - フォームを並べて動かしています。Windows10では幅が変わってフォームの間隔が空いてしまいます。
- Retrieving size of Windows 10 shadowing border - Stack Overflow
タイトルバーの色を変更
自身のタイトルバー(キャプション)とそのテキストの色を変更(Windows 11 ビルド 22000 以降)
Hyper-V上のWindows11で動作確認をしています。
- Windows10 Pro で Hyper-V を有効にする方法 | GENESIS BLOG
- Download a Windows virtual machine - Windows app development | Microsoft Developer
- WindowsでHyper-Vの仮想マシンを作成する:Tech TIPS - @IT
//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
内に各ウィンドウハンドルの処理内容を記載することで、すべての最上位ウィンドウを順番に処理できます。なお、EnumWindowsProc
がfalse
を戻した場合はその時点で処理が終了します。
下記の例では、タイトルがあってかつ表示されているオーナーウィンドウだけを抽出し、ウィンドウハンドル、プロセスID、スレッドID、タイトル、クラス名、ファイルパスを表示しています。
-
IsWindowVisible
を使用してウィンドウが表示されているか確認します。 -
GetWindow
を使用してオーナーウィンドウか確認します。 - 表示されていてかつオーナーウィンドウの場合は、
GetWindowThreadProcessId
でプロセスID、スレッドIDを、GetWindowText
でタイトルを取得します。 - タイトルがある場合は、更に
GetClassName
でクラス名を取得します。 - 取得した情報から出力文字列を作成してテキストボックスに出力します。
- このときファイルパスも取得しています。取得する処理はユーザ定義関数
FilePathFromProcess
にまとめています。-
OpenProcess
を使用してプロセスオブジェクトを開きます。この後GetModuleFileNameEx
でファイルパスを取得しますがPROCESS_QUERY_LIMITED_INFORMATION
アクセス権のみが必要なので、そのアクセス権のみ指定します。 -
GetModuleFileNameEx
でファイルパスを取得します。 -
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
エクスプローラではうまくいかないようです。
- [Delphi:91190] Explorer.exe を CreateProcess した場合の、Explorer.exe のウインドウハンドルの取得
- Windowsスマートチューニング(413) Win 10編: エクスプローラーにプロセスIDを示す文字列を追加する | マイナビニュース
- ツールを使わずに、アプリの起動位置を指定したい - Microsoft コミュニティ
非アクティブウィンドウにキーを送信
ボタンを押したらメモ帳にメッセージを送信(POST)します。
AキーDOWNメッセージを送信し、文字「a」を入力
-
FindWindowExで子ウィンドウを検索します。第1引数が
NULL
の場合はデスクトップウィンドウを親ウィンドウとして検索します。NULL
(IntPtr.Zero
)を設定することで、デスクトップ上のメモ帳を探します。 - 更にメモ帳の文字を入力するところ(
Edit
子ウィンドウ)を検索します。 -
PostMessage
でAキーDOWNメッセージを送信します。- 第1引数
hWnd
には対象のウィンドウハンドル(Edit
子ウィンドウ)を指定します。 - 第2引数
Msg
には投稿するメッセージを指定します。今回はAキーDOWNメッセージを送るためWM_KEYDOWN
を指定します。WM_KEYDOWN
によるとwParam
に仮想キーコード、lParam
にキーストローク メッセージ フラグを指定します。 - 第3引数
wParam
にはKeys列挙型を利用してA
キーの仮想キーコードを設定します。 - 第4引数
lParam
の情報は通常必要ないため0
を指定します。
- 第1引数
- 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)
-
keybd_event
を使って送信前にCtrl
を押します。 -
WM_KEYDOWN
でO
を送信します。 - 送信後
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
を使用する場合は、
-
SendInput
の引数作成のときに使用する以下の関数を定義します。-
GetMessageExtraInfo
: 現在のスレッドの追加のメッセージ情報を取得。 -
MapVirtualKey
: 仮想キーコードからスキャンコードに変換
-
-
SendInput
の引数であるINPUT
構造体を定義します。
コード上のINPUT構造体の定義(ここから)
からINPUT構造体の定義(ここまで)
- 必要な定数を定義します。
- あとは
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秒ごとにマウスカーソル位置の色を取得し、フォームの背景色に設定します。
- Windowsの「拡大と縮小のレイアウト」の設定が「100%」でない場合、正しく動作しません。そのため、ウィンドウ作成前に
SetProcessDPIAware
を使用してプロセス既定のDPI認識をシステムDPI対応にします。以下のURLに記載の通り、この方法は推奨されませんが、使用例としての使用だけですので使用しています。 -
GetDC
の引数にNULL
(IntPtr.Zero
)を指定し、画面全体のデバイス コンテキストを取得します。 -
GetCursorPos
、GetPixel
を使用して、デバイス コンテキストからマウスカーソル位置の色情報を取得します。GetPixel
の戻り値が色情報です。 -
GetDC
で取得したデバイス コンテキストをReleaseDC
で解放します。 -
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
を動的に呼び出して使用します。
-
LoadLibrary
を使用してDLLを読み込みます。(ダイナミック リンク ライブラリの検索順序を参照) -
GetProcAddress
を使用してDLL内の関数へのポインタを取得します。この関数の引数はLPCSTR
なのでANSI文字列になります。 -
Marshal.GetDelegateForFunctionPointer
を使用して関数ポインタをデリゲートに変換します。 - 変換したデリゲートを使って関数を呼びます。
-
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 フックの手順に関するページを参照してください。
-
SetWindowsHookEx
を使用してフック プロシージャをインストールします。- 第1引数にはインストールするフック プロシージャの種類である
WH_KEYBOARD
を指定します。 - 第2引数にはフック プロシージャへのポインタ(メッセージを処理する関数)を指定します。下記の例では
HookProc
になります。 - 第3引数には
NULL
(IntPtr.Zero
)を設定します。 - 第4引数には
GetCurrentThreadId
で取得した、呼び出し元のスレッドID(=自身のスレッドID)を設定します。
- 第1引数にはインストールするフック プロシージャの種類である
- メッセージを受け取ったときに処理する関数、下記の場合だと
HookProc
を実装します。ここでは単純に受け取ったキーメッセージを表示するだけとしています。KeyboardProc
のページを確認し、-
nCode
が0未満の場合は、何もせずCallNextHookEx
を使用して次のフックプロシージャにイベントを渡します。nCode=0
の場合のみ処理をします。 -
wParam
は仮想キーコードになるのでKeys 列挙型
を使って文字列に変換しています。 -
lParam
はキーストローク メッセージ フラグ
になります。WM_KEYDOWN
やWM_KEYUP
などを確認すれば分かりますが項目ごとにbitの区分がありますので、-
で区切って0-0-0-0000-0-00011110-0000000000000001
のように変換します。 - 今回は単純にメッセージを表示するだけなので、次のフックチェーンにメッセージを渡すために、
CallNextHookEx
を呼んで戻します。
-
- 終了時に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;
};
}
}
}
マウスフック(固有スレッド)
基本的にキーボードフック(固有スレッド)と同じです。
lParam
がMOUSEHOOKSTRUCT構造体へのポインタになるので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
- MouseProc コールバック関数 - Win32 apps | Microsoft Learn
- MOUSEHOOKSTRUCT (winuser.h) - Win32 apps | Microsoft Learn
- マウス入力通知 - Win32 apps | Microsoft Learn
- Marshal.PtrToStructure メソッド (System.Runtime.InteropServices) | Microsoft Learn
- C#のためのC++の配列、構造体、ポインタの変換処理 | TomoSoft
キーボードフック(グローバル)
グローバルフックを使用するために、フックプロシージャを含むDLLを作成します。ここでは単純にキーボード操作を無効にするフックプロシージャを作成します。
Win32 API キーストロークメッセージをフックする - s-kita’s blog
フックプロシージャのインストール方法としてはフックプロシージャのインストールと解放によると
- アプリケーション側がSetWindowsHookExを呼び出す方法
- LoadLibraryを使用してDLLモジュールのハンドルを取得します。
- GetProcAddressを使用してフックプロシージャへのポインター取得します。
- 最後に、SetWindowsHookExを使用して、フックチェーンにフックプロシージャをインストールします。
- 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
マウスフック(グローバル)
キーボードフック(グローバル)と同様です。本来はグローバルフックではなく対象のスレッドに限定すべきですが、使用例としてグローバルフックとしています。
フック中にエクスプローラの余白をダブルクリックしたときに、上の階層に行くようにします。
- 簡易的なアプリケーションのため同じ場所で2回クリックされた場合、クリックの時間間隔によらずダブルクリックと判定しています。マウス メッセージの識別子を確認し、マウスの右ボタンのダウン
WM_LBUTTONDOWN
とアップWM_LBUTTONUP
が2回繰り返されたときにダブルクリックと判定します。但しマウスカーソルの移動WM_MOUSEMOVE
がほぼない状態に限っています。 - ダブルクリック判定をしたときに
- クラス名が
DirectUIHWND
。(事前にマウスカーソル下のウィンドウによってエクスプローラのファイルビューのクラス名がDirectUIHWND
であることを確認) - 実行ファイルが
explorer.exe
。(全ウィンドウの情報を表示参照) - 色がファイル/フォルダ未選択時のエクスプローラ背景色である白。(マウスカーソル位置の色を取得参照)
- クラス名が
- 上記が確認できた場合、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
- winapi - Error using GetModuleFileNameExA function in Qt - Stack Overflow
- GetModuleFileNameExW 関数 (psapi.h) - Win32 apps | Microsoft Learn
- _countof Macro | Microsoft Learn
- strcmp、wcscmp、_mbscmp、_mbscmp_l | Microsoft Learn
- 文字列の操作 - Win32 apps | Microsoft Learn
- _splitpath_s、_wsplitpath_s | Microsoft Learn
ローレベルキーボード/マウスフック(グローバル)
グローバルフックの場合、グローバルフックプロシージャは任意のアプリケーションのコンテキストで呼び出されるため、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_LL
とWH_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_hHook
がNULL
となっていることが分かります。
フックプロシージャのインストールと解放に書かれている通り、すべてのアプリケーションのコンテキストでグローバルフックプロシージャが呼び出され、それらのすべてのプロセスに対して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
- GetSystemMenuを使用してウィンドウメニューのコピーへのハンドルを取得します。
-
InsertMenuItemを使用して「区切り線」を追加します。
- 第1引数にはGetSystemMenuで取得したメニューへのハンドルを指定します。
- 第3引数を
true
にすることで、第2引数でメニュー挿入位置を指定できます。第2引数で指定したメニューの位置の前に挿入されます。今回は「閉じる」の前に区切り線を入れたいので「6」を指定します。 - 第4引数にはMENUITEMINFO構造体へのポインタを指定します。MENUITEMINFO構造体にはメニュー項目に対する設定値が入っています。リンク先を見て必要な部分のみ設定します。今回は区切り設定をしたいのでその設定のみを実施します。
-
InsertMenuItemを使用して「最前面に表示」を追加します。
- 第1引数は上記と同様です。
- 第2,3引数も上記と同様です。「区切り線」を入れたことで「区切り線」が新たに「6」の位置になっています。この前に入れたいので「6」を指定します。
- 第4引数も同様です。今回は「メニュー項目の識別子」と「メニュー項目の文字列」を設定します。注釈に「定義済みのウィンドウ メニュー項目はすべて、
0xF000
より大きい識別子番号を持っています。」と書かれているので0xF000
未満の値で識別子を設定します。今回はMENU_ID_01(0x0001)
としています。
- 以上でメニュー項目は追加されますが、メニュー表示時や選択時の実装がまだです。ウィンドウプロシージャを利用してメッセージ受信時の処理を記載します。ウィンドウプロシージャはウィンドウに送信されたメッセージを処理するコールバック関数のことです。
-
WndProc関数は注釈に記載の通り、WindowProc関数に正確に対応していますのでこれをオーバーライドして実装します。
- メニュー表示前にWM_INITMENUメッセージが送信されます。このメッセージ受信時にSetMenuItemInfoを使って、現在のTopMost設定から「最前面に表示」のチェック状態を設定します。
- メニュー選択時に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
- WindowsフォームでWindowsメッセージを直接処理するには?:.NET TIPS - @IT
- メニュー (メニューとその他のリソース) - Win32 apps | Microsoft Learn
- メニューについて - Win32 apps | Microsoft Learn
クリップボードの監視
クリップボードの使用 - Win32 apps | Microsoft Learn
ウィンドウハンドルをクリップボード形式リスナーに登録するとクリップボードの内容が変更されたときにWM_CLIPBOARDUPDATEメッセージが投稿されます。
下記の例ではクリップボード内の文字列が変更されたときに、その文字列を表示するようにしています。終了するときはタスクトレイアイコンを右クリックして「終了」を選択します。なお、フォームを表示させないようにするためにProgram.cs
も変更しています。
-
起動時にAddClipboardFormatListenerを使用してクリップボード形式リスナーに登録します。
-
動作中、ウィンドウプロシージャ(ウィンドウメニューに項目を追加を参照)を利用してWM_CLIPBOARDUPDATEメッセージを確認しています。メッセージが来たとき、クリップボードに含まれている内容が文字列であれば、その文字列をShowBalloonTipメソッドを使って表示するようにしています。このとき、クリップボード内にあるデータ形式(下記参照)の数が0ではないことと現在のテキストとは異なることを確認したうえで表示しています。これは例えば以下のようにコピーされるとこととメッセージが送られることが必ずしも一致しないためです。
- Excelの場合、1回のコピー操作のときにWM_CLIPBOARDUPDATEメッセージが連続して複数送られます。また、貼り付けのときにも送られます。Excelを閉じたときにも送られることがあります。
- その他のソフトウェアでもコピー以外の操作のときにメッセージが送られることを確認しています。
何がトリガーとなってメッセージが送られるのかは未確認ですが、少なくとも単にOpenClipboardとCloseClipboardを呼び出した場合でも、CloseClipboardが呼ばれた時点でメッセージが送られていました。
-
終了時に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);
}
}
}