| ページ一覧 | ブログ | twitter |  書式 | 書式(表) |

MyMemoWiki

C Sharp Win32 API および DLL の利用

提供: MyMemoWiki
2022年7月31日 (日) 04:09時点におけるPiroto (トーク | 投稿記録)による版 (→‎まず、C#では、DLLImportを行う。)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
ナビゲーションに移動 検索に移動

C# Win32 API および DLL の利用

C Sharp | Visual Studio | このページからメモ

非常にわかりやすく実践的な良書。

概要

どのようにして DLL や Win32 API の関数を C# から呼び出すか

やりたいことは・・・
  • (char*) が戻り値の場合、どのように取得する?
  • GetWindowRectやEnumWindowsで、構造体や、コールバック関数をどのように指定する?
方法の概要
  • .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);
}
  • 起動時の状態

0295 set window text01.jpg

  • button1 押下後

0296 set window text02.jpg

なぜこのようなことができるのか? - デフォルトのマーシャリング型

  • コンパイラは user32.dll を探して、SetWindwText を認識しており呼び出し前に自動的に string を LPTSTR(TCHAR*)に変換する。
  • すべての C# 型は、デフォルトのマーシャリング型を持っている(string は LPTSTR)ためこのようなことが可能。
しかし string はそのままでは出力パラメータとして使用できない
  • 入力パラメータとしては、上記のようにstringを使用できるが、出力パラメータとしてこれは使用できない(GetWindowTextで試してみるとよい)。
  • なぜなら string は 変更不可(インミュータブル)だから。

<blockquote>出力パラメータとして LPTSTR を使用する場合、StringBuilder を利用する</blockquote>

例(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());
}
  • 呼び出したところ

0294 get window text01.jpg

<blockquote>StringBuilderのデフォルトのマーシャリング型も LPTSTR だが、GetWindowsText は内容を変更できる</blockquote>

フォルトのマーシャリング型が希望の型でない場合

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);
}

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);
}
結果

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 = "";
}
結果

0292 enum windows02.jpg


Tips