目次
C# Win32 API および DLL の利用
[C#][Visual Studio][Win32API]
このページからメモ
非常にわかりやすく実践的な良書。
概要
どのようにして DLL や Win32 API の関数を C# から呼び出すか
- やりたいことは・・・
- 方法の概要
- .NETの利点の一つに、言語非依存があり、書いたクラスを他の言語でも使用できることがある。
- しかし、アンマネージの DLL へは .NET のオブジェクトを 構造体や char* や関数ポインタへ変換(マーシャリング)する必要がある
- マーシャリングは大きなトピックだが、それほど多くを学ぶ必要はない。
C# から DLL 関数を呼び出すには
まず、C#では、DLLImportを行う。
using System.Runtime.InteropServices; // DLL Import class Win32Api { [DllImport("User32.Dll", EntryPoint="SetWindowText")] public static extern void SetWindowText(int hwnd, String text); }
- C# では、DLLImportを利用して、コンパイラにどこにラッパー関数とバンドルするエントリーポイントが存在するのかを伝える
- 例ではWin32としているが、クラス名は任意であり、ネームスペースに置くことも可能
ライブラリとして作成しておけば、任意の C# プロジェクトから利用可能
LPTSTRに対応する
- 上記 SetWindwText を呼び出してみる
- 呼び出し側
private void button1_Click(object sender, EventArgs e) { int hwnd = (int)this.Handle; string s = "SetWindowText Test."; Win32Api.SetWindowText(hwnd, s); }
- 起動時の状態
- button1 押下後
なぜこのようなことができるのか? − デフォルトのマーシャリング型
- コンパイラは user32.dll を探して、SetWindwText を認識しており呼び出し前に自動的に string を LPTSTR(TCHAR*)に変換する。
- すべての C# 型は、デフォルトのマーシャリング型を持っている(string は LPTSTR)ためこのようなことが可能。
- しかし string はそのままでは出力パラメータとして使用できない
- 入力パラメータとしては、上記のようにstringを使用できるが、出力パラメータとしてこれは使用できない(GetWindowTextで試してみるとよい)。
- なぜなら string は 変更不可(インミュータブル)だから。
出力パラメータとして LPTSTR を使用する場合、StringBuilder を利用する
- 例(GetWindowText)
using System.Text; // String Builder using System.Runtime.InteropServices; // DLL Import class Win32Api { [DllImport("User32.Dll", EntryPoint="GetWindowText")] public static extern int GetWindowText(int hwnd, StringBuilder buf, int nMaxCount); }
- 呼び出し側
private void button2_Click(object sender, EventArgs e) { int hwnd = (int)this.Handle; StringBuilder buf = new StringBuilder(256); Win32Api.GetWindowText(hwnd, buf, buf.Capacity); MessageBox.Show(buf.ToString()); }
- 呼び出したところ
StringBuilderのデフォルトのマーシャリング型も LPTSTR だが、GetWindowsText は内容を変更できる
フォルトのマーシャリング型が希望の型でない場合
- デフォルトのマーシャリング型をオーバーライドすることができる
[DllImport("User32.Dll", EntryPoint = "GetClassName")] public static extern int GetClassName(int hwnd, [MarshalAs(UnmanagedType.LPStr)] StringBuilder buf, int nMaxCount);
構造体を定義
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int left; public int top; public int right; public int bottom; } class Win32Api { [DllImport("User32.Dll", EntryPoint="GetWindowRect")] public static extern int GetWindowRect(int hwnd, ref RECT rc); }
- 呼び出し元
private void button4_Click(object sender, EventArgs e) { RECT rc = new RECT(); int hwnd = (int)this.Handle; Win32Api.GetWindowRect(hwnd, ref rc); string msg = String.Format("top={0}, left={1}, right={2}, bottom={3}." , rc.top, rc.left, rc.right, rc.bottom); MessageBox.Show(msg); }
ref を使うとCLRは、関数がオブジェクトを変更できるように参照として渡す(無名のスタックコピーではなく)。
クラスを構造体として渡す
- 上記のRECTが構造体ではなく、クラスの場合、以下のようにすればよい
[DllImport("user32.dll")] public static extern int GetWindowRect(int hwnd, [MarshalAs(UnmanagedType.LPStruct)] RECT rc);
C# に構造体が用意されている場合もある
- C# では、ひとつのことを実現するのに多数のやり方がある
- System.Drawing に既に Rectangle 構造体が用意されており、Wn32 API のRECTにマーシャリングするすべを心得ている
using System.Drawing; // Rectangle class Win32Api { [DllImport("User32.Dll", EntryPoint = "GetWindowRect")] public static extern int GetWindowRect(int hwnd, ref Rectangle rc); }
そもそもWin32 APIを利用する必要もない
- 上記のAPIは、コントロールのプロパティを利用すれば実現できる
- GetWindowRect
Rectangle r = mywnd.DisplayRectangle;
- Get/SetWindowText
mywnd.Text = "Window Text";
どのような時にWin32 API ラッパーを利用するのか
- コントロールの派生クラスではなく、HWND しか取得できない場合
- コールバック関数
コールバック関数の使い方
- アンマネージのコールバック関数を使うには、デリゲートを使うだけ
- デリゲートは単なる宣言のため、実装は自分のクラスで行う。
- ラッパークラス側
class Win32Api { public delegate bool EnumWindowCB(int hwnd, int lparm); [DllImport("User32.Dll")] public static extern int EnumWindows(EnumWindowCB cb, int lparm); }
- 呼び出し側
// デリゲートの実装 public static bool MyEnumWindowCB(int hwnd, int lparam) { System.Diagnostics.Debug.WriteLine(String.Format("{0:X}", hwnd)); return true; } private void button6_Click(object sender, EventArgs e) { Win32Api.EnumWindowCB cb = new Win32Api.EnumWindowCB(MyEnumWindowCB); Win32Api.EnumWindows(cb, 0); }
- 結果
ポインタを使う
- EnumWindows においては、lparam で渡したポインターをコールバック関数で得ることができた。
- .Netでは、IntPtr 、GCHandle を使ってラップする。
Free メソッドを呼び出すのを忘れないこと! C#では、時々自分でメモリを開放しなければならない!
- ラッパークラス側
using System.Runtime.InteropServices; public delegate bool EnumWindowCB2(int hwnd, IntPtr lparm); [DllImport("User32.Dll", EntryPoint = "EnumWindows")] public static extern int EnumWindows2(EnumWindowCB2 cb, IntPtr lparm);
- 呼び出し側
public partial class Form1 : Form { static bool MyEnumWindowCB2(int hwnd, IntPtr lparam) { GCHandle gch = (GCHandle)lparam; EnumWindowInfo ewi = (EnumWindowInfo)gch.Target; System.Diagnostics.Debug.WriteLine(String.Format("{0:X}:{1}", hwnd, ewi.msg)); return true; } private void button7_Click(object sender, EventArgs e) { EnumWindowInfo ewi = new EnumWindowInfo(); ewi.msg = "this is IntPtr Test."; GCHandle gch = GCHandle.Alloc(ewi); Win32Api.EnumWindowCB2 cb = new Win32Api.EnumWindowCB2(MyEnumWindowCB2); Win32Api.EnumWindows2(cb, (IntPtr)gch); gch.Free(); } } public class EnumWindowInfo { public string msg = ""; }
- 結果
test.txt(1239)
YAGI Hiroto (piroto@a-net.email.ne.jp)
twitter http://twitter.com/pppiroto
Copyright© 矢木 浩人 All Rights Reserved.