{{include_html redirect_html, "!WIN32DLL"}} !!!C# Win32 API および DLL の利用 [C#][Visual Studio]{{category Win32API}} このページからメモ *http://msdn.microsoft.com/en-us/magazine/cc301501.aspx {{amazon 4839930422}} 非常にわかりやすく実践的な良書。 !!概要 !どのようにして DLL や Win32 API の関数を C# から呼び出すか ::やりたいことは・・・ *(char*) が戻り値の場合、どのように取得する? *GetWindowRectやEnumWindowsで、構造体や、コールバック関数をどのように指定する? ::方法の概要 *.NETの利点の一つに、言語非依存があり、書いたクラスを他の言語でも使用できることがある。 *しかし、アンマネージの DLL へは .NET のオブジェクトを 構造体や char* や関数ポインタへ変換(マーシャリング)する必要がある *マーシャリングは大きなトピックだが、それほど多くを学ぶ必要はない。 !!C# から DLL 関数を呼び出すには !まず、C#では、DLLImportを行う。 *[SetWindowText|http://msdn.microsoft.com/en-us/library/ms633546%28VS.85%29.aspx] 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); } *起動時の状態 {{ref_image set_window_text01.jpg}} *button1 押下後 {{ref_image set_window_text02.jpg}} !なぜこのようなことができるのか? − デフォルトのマーシャリング型 *コンパイラは user32.dll を探して、SetWindwText を認識しており呼び出し前に自動的に string を LPTSTR(TCHAR*)に変換する。 *すべての C# 型は、デフォルトのマーシャリング型を持っている(string は LPTSTR)ためこのようなことが可能。 ::しかし string はそのままでは出力パラメータとして使用できない *入力パラメータとしては、上記のようにstringを使用できるが、出力パラメータとしてこれは使用できない(GetWindowTextで試してみるとよい)。 *なぜなら string は 変更不可(インミュータブル)だから。 ""出力パラメータとして LPTSTR を使用する場合、StringBuilder を利用する ::例(GetWindowText) *[GetWindowText|http://msdn.microsoft.com/en-us/library/ms633520%28VS.85%29.aspx] 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()); } *呼び出したところ {{ref_image get_window_text01.jpg}} ""StringBuilderのデフォルトのマーシャリング型も LPTSTR だが、GetWindowsText は内容を変更できる !フォルトのマーシャリング型が希望の型でない場合 ::[GetClassName|http://msdn.microsoft.com/en-us/library/ms633582%28VS.85%29.aspx] *デフォルトのマーシャリング型をオーバーライドすることができる [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_image get_window_rect01.jpg}} ""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); } ::結果 {{ref_image enum_windows01.jpg}} !ポインタを使う *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 = ""; } ::結果 {{ref_image enum_windows02.jpg}} {{ref test.txt}}