8,808 バイト追加
、 2020年2月15日 (土) 07:30
==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を行う。====
*[http://msdn.microsoft.com/en-us/library/ms633546%28VS.85%29.aspx SetWindowText]
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としているが、クラス名は任意であり、ネームスペースに置くことも可能
<blockquote>ライブラリとして作成しておけば、任意の C# プロジェクトから利用可能</blockquote>
====LPTSTRに対応する====
*上記 SetWindwText を呼び出してみる
=====呼び出し側=====
private void button1_Click(object sender, EventArgs e)
{
int hwnd = (int)this.Handle;
string s = "SetWindowText Test.";
Win32Api.SetWindowText(hwnd, s);
}
*起動時の状態
[[File:0295_set_window_text01.jpg]]
*button1 押下後
[[File:0296_set_window_text02.jpg]]
====なぜこのようなことができるのか? - デフォルトのマーシャリング型====
*コンパイラは user32.dll を探して、SetWindwText を認識しており呼び出し前に自動的に string を LPTSTR(TCHAR*)に変換する。
*すべての C# 型は、デフォルトのマーシャリング型を持っている(string は LPTSTR)ためこのようなことが可能。
=====しかし string はそのままでは出力パラメータとして使用できない=====
*入力パラメータとしては、上記のようにstringを使用できるが、出力パラメータとしてこれは使用できない(GetWindowTextで試してみるとよい)。
*なぜなら string は 変更不可(インミュータブル)だから。
<blockquote>出力パラメータとして LPTSTR を使用する場合、StringBuilder を利用する</blockquote>
=====例(GetWindowText)=====
*[http://msdn.microsoft.com/en-us/library/ms633520%28VS.85%29.aspx 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());
}
*呼び出したところ
[[File:0294_get_window_text01.jpg]]
<blockquote>StringBuilderのデフォルトのマーシャリング型も LPTSTR だが、GetWindowsText は内容を変更できる</blockquote>
====フォルトのマーシャリング型が希望の型でない場合====
=====[http://msdn.microsoft.com/en-us/library/ms633582%28VS.85%29.aspx GetClassName]=====
*デフォルトのマーシャリング型をオーバーライドすることができる
[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);
}
[[File:0293_get_window_rect01.jpg]]
<blockquote>ref を使うとCLRは、関数がオブジェクトを変更できるように参照として渡す(無名のスタックコピーではなく)。</blockquote>
====クラスを構造体として渡す====
*上記の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);
}
=====結果=====
[[File:0291_enum_windows01.jpg]]
====ポインタを使う====
*EnumWindows においては、lparam で渡したポインターをコールバック関数で得ることができた。
*.Netでは、IntPtr 、GCHandle を使ってラップする。
<blockquote>Free メソッドを呼び出すのを忘れないこと! C#では、時々自分でメモリを開放しなければならない!</blockquote>
=====ラッパークラス側=====
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 = "";
}
=====結果=====
[[File:0292_enum_windows02.jpg]]
{{ref test.txt}}