C#プログラムから非管理DLsを呼び出す方法
前言:
周知のように、.NETはすでに技術的なファッションになっており、C#も自然にプログラミングファッションになっています.煙の海のようなWin 32 APIをどのように利用するか、以前に作成されたWin 32コードは、ますます多くのC#プログラマーが注目している問題となっている.この文書では、C#コードから非管理DLsを呼び出す方法について説明します.関数がシリアルタイプ(char*)出力パラメータを持つWin 32 APIまたはDLL出力関数である場合、C#からどのように呼び出されますか?パラメータを入力する場合は問題は大きくありませんが、パラメータから返される列をどのように取得しますか?さらに、GetWindowsRectやEnumWindowsなどのパラメータとして構造(struct)とコールバック(callback)がある関数を呼び出すにはどうすればいいですか?では、私たちはどのようにしてパラメータをC++とMFCからC#の必要なタイプに変換しますか?次はこれらの問題を一つ一つ解決しましょう.
マイクロソフト.NETの最も主要な利点は、言語に関係のない開発システムを提供することです.Visual Basic、C++、C#などの言語でクラスを記述し、他の言語で使用することができます.また、異なる言語でクラスを派生させることもできます.しかし、以前開発された非管理DLLをどのように呼び出すのでしょうか.メソッドは、.NETオブジェクトを構造、char*、およびC言語のポインタに変換する必要があります.行語で言えば、パラメータはカラムセット(marshal)されなければならない.列集といえば,一言や二言でははっきり言えない.幸いなことに、列集を実現するには、私たちがあまり知らないものはありません.
C#からDLL関数を呼び出すには、まず、長期にわたってVisual Basicを使用してきたプログラマーが行ったように、C#でDllImportキーワードを使用しているにすぎないという宣言が必要です.
using System.Runtime.InteropServices; // DllImport
public class Win32 {
[DllImport("User32.Dll")]
public static extern void SetWindowText(int h, String s);
}
C#では、DllImportキーワードの役割は、コンパイラのエントリポイントがどこにあるかを教え、パッケージ関数をクラスにバンドルすることです.このような名前を付けることができます.ここでは、クラス名をWin 32として使用してもいいです.次のコードのように、このクラスを名前空間に配置することもできます.
Win 32 API.csソースコード
// Win32API: , Win32 API
// :
// csc /t:library /out:Win32API.dll Win32API.cs
//
using System;
using System.Drawing;
using System.Text;
using System.Runtime.InteropServices;
/////////////////////////////////////////////////////////////////
// Win32 API 。 Win32 API, 。
//
namespace Win32API {
[StructLayout(LayoutKind.Sequential)]
public struct POINT {
public POINT(int xx, int yy) { x=xx; y=yy; }
public int x;
public int y;
public override string ToString() {
String s = String.Format("({0},{1})", x, y);
return s;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct SIZE {
public SIZE(int cxx, int cyy) { cx=cxx; cy=cyy; }
public int cx;
public int cy;
public override string ToString() {
String s = String.Format("({0},{1})", cx, cy);
return s;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int left;
public int top;
public int right;
public int bottom;
public int Width() { return right - left; }
public int Height() { return bottom - top; }
public POINT TopLeft() { return new POINT(left,top); }
public SIZE Size() { return new SIZE(Width(), Height()); }
public override string ToString() {
String s = String.Format("{0}x{1}", TopLeft(), Size());
return s;
}
}
public class Win32 {
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(int hwnd);
[DllImport("user32.dll")]
public static extern int GetWindowText(int hwnd,
StringBuilder buf, int nMaxCount);
[DllImport("user32.dll")]
public static extern int GetClassName(int hwnd,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder buf,
int nMaxCount);
[DllImport("user32.dll")]
public static extern int GetWindowRect(int hwnd, ref RECT rc);
[DllImport("user32.dll")]
// ,
public static extern int GetWindowRect(int hwnd, ref Rectangle rc);
}
}
このコードは、csc/t:library/out:Win 32 API.dll Win 32 API.csのコマンドラインでコンパイルできます. コンパイルに成功すると、C#エンジニアリングで使用できるダイナミックライブラリ(Win 32 API.dll)が作成されます.
using Win32API;
int hwnd = // get it
String s = "I''''m so cute." ;
Win32.SetWindowText(hwnd, s);
コンパイラは、user 32.dllでSetWindowTextを見つけ、呼び出す前にシリアルを自動的にLPTSTR(TCHAR*)に変換することを知っています.本当に不思議ですね.のNETはどのように実現したのでしょうか.実際には、各C#タイプにはデフォルトのカラムセットタイプがあります.列の場合、その列セットタイプはLPTSTRです.しかし、GetWindowTextが呼び出されている場合、そのシリアルパラメータは入力パラメータではなく出力パラメータです.シリアルは変わらないので、上記のように処理することはできません.いつストリングを処理しても、新しいストリングが作成されることには少しも気づいていないかもしれません.この列を変更するには、StringBuilderを使用する必要があります.
using System.Text; // StringBuilder
public class Win32 {
[DllImport("user32.dll")]
public static extern int GetWindowText(int hwnd,
StringBuilder buf, int nMaxCount);
}
StringBuilderのデフォルトのカラムセットタイプはLPTSTRですが、GetWindowTextでは実際のカラムを変更できます.
int hwnd = // get it
StringBuilder sb = new StringBuilder(256);
Win32.GetWindowText(hwnd, sb, sb.Capacity);
最初の質問の答えはStringBuilderを使用することです前に説明した方法は通じるが、デフォルトのカラムセットタイプがあなたが望んでいるタイプでない場合はどうするかということを考えていない場合があります.たとえばGetClassNameを呼び出すには、Windowsのプログラミングの達人は、GetClassNameのパラメータが他の多くのAPI関数のパラメータとは異なり、その列パラメータはLPSTR(char*)、さらにはUnicode列であることを知っています.シリアルを渡すと、共通言語ランタイム(CLR)がTCHArsに変換されます.悪いのではないでしょうか.恐れずにMarshalAsでデフォルトの処理を書き換えることができます.
[DllImport("user32.dll")]
public static extern int GetClassName(int hwnd,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder buf,
int nMaxCount);
GetClassNameを呼び出します..NETは、ワイド文字ではなくANSI文字として列を渡します.以上、関数荷重パラメータで返される文字列を取得する方法について説明しました.次に、構造パラメータとコールバックパラメータの状況を見てみましょう.言うまでもなく、.NETはそれらを処理する方法があるに違いない.GetWindowRectを例に挙げます.この関数はウィンドウのスクリーン座標でRECTを埋めます.
C/C++で
RECT rc;
HWND hwnd = FindWindow("foo",NULL);
::GetWindowRect(hwnd, &rc);
C# ? RECT ? C# , : StructLayout:
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int left;
public int top;
public int right;
public int bottom;
}
構造定義があれば、次のように実装をパッケージ化できます.
[DllImport("user32.dll")]
public static extern int
GetWindowRect(int hwnd, ref RECT rc);
ここでrefが使用されていることに注意してください.CLRはRECTを参照として渡し、名前のないスタックコピーではなく、関数が私たちのオブジェクトを修正できるようにします.GetWindowRectを定義したら、次のように呼び出すことができます.
RECT rc = new RECT();
int hwnd = // get it
Win32.GetWindowRect(hwnd, ref rc);
ここでref――くどくど宣言して使わなければならないことに注意してください.C#構造のデフォルトのカラムセットタイプは何ですか――LPStructなので、MarshalAsを使う必要はありません.しかし、RECTが構造ではなくクラスであれば、以下のようにパッケージを実現する必要があります.
// RECT ,
[DllImport("user32.dll")]
public static extern int
GetWindowRect(int hwnd,
[MarshalAs(UnmanagedType.LPStruct)] RECT rc);
C#はC++と似ていて、多くのことが同じように帰ることができます.System.Drawingには矩形を処理するためのRectangle構造があります.なぜ車輪を再発明したのですか.
[DllImport("user32.dll")]
public static extern int GetWindowRect(int hwnd, ref Rectangle rc);
運転時にRectangleをWin 32 RECTとして列集する方法がわかっている以上.Windows.Forms.controlクラスには、Control.DisplayRectangleでウィンドウの長方形を取得し、Control.Textでコントロールテキストを設定/取得する属性があるため、実際のコードでGetWindowRect(Get/SetWindowText)を呼び出す必要はありません.
Rectangle r = mywnd.DisplayRectangle;
mywnd.Text = "I''''m so cute";
コントロール派生オブジェクトではなくHWNDが何らかの理由で知られている場合、APIは例示のようにパッケージ化されるだけである.以上、串や構造、矩形を見てみましたが...他に何がありますか?ところで、コールバックもあります.コールバックをC#から非管理コードに渡すにはどうすればいいですか?依頼(delegate)を使えばいいことを覚えておいてください:delegate bool EnumWindowsCB(int hwnd, int lparam); 依頼/コールバックタイプが宣言されると、次のようにパッケージ化できます.
[DllImport("user32")]
public static extern int
EnumWindows(EnumWindowsCB cb, int lparam);
上のdelegateは、委任タイプを宣言しただけで、クラスに実際の委任実装を提供する必要があります.
//
public static bool MyEWP(int hwnd, int lparam) {
// do something
return true;
}
次に、パッケージ処理を行います.
EnumWindowsCB cb = new EnumWindowsCB(MyEWP);
Win32.EnumWindows(cb, 0);
賢い読者は私たちがlparamの問題を隠していることに気づいて、Cの中で、もしあなたがEnumWindowsにLPARAMをあげたら、Windowsはそれを使ってコールバック関数を通知します.一般的な典型的なlparamは、必要なコンテキスト情報を含む構造またはクラスポインタです.しかし、.NETでは絶対に「ポインタ」に言及してはいけないことを覚えておいてください.では、どうすればいいのでしょうか.これはlparamをIntPtrとして宣言し、GCHandleでパッケージ化できます.
// lparam IntPtr
delegate bool EnumWindowsCB(int hwnd, IntPtr lparam);
// GCHandle
MyClass obj = new MyClass();
GCHandle gch = GCHandle.Alloc(obj);
EnumWindowsCB cb = new EnumWindowsCB(MyEWP);
Win32.EnumWindows(cb, (IntPtr)gch);
gch.Free();
最後にFreeを呼び出すのを忘れないで!C#では、以前と同じように、使用したメモリを自分で解放する必要がある場合があります.キャリア列挙器のlparam「ポインタ」にアクセスするには、
GCHandle.Target。 public static bool MyEWP(int hwnd, IntPtr param) {
GCHandle gch = (GCHandle)param;
MyClass c = (MyClass)gch.Target;
// use it
return true;
}
次に、ウィンドウ配列クラスを示します.
WinArray.cs
// WinArray: EnumWindows ArrayList
//
using System;
using System.Collections;
using System.Runtime.InteropServices;
namespace WinArray {
public class WindowArray : ArrayList {
private delegate bool EnumWindowsCB(int hwnd, IntPtr param);
// private , , 。
[DllImport("user32")]
private static extern int EnumWindows(EnumWindowsCB cb,
IntPtr param);
private static bool MyEnumWindowsCB(int hwnd, IntPtr param) {
GCHandle gch = (GCHandle)param;
WindowArray itw = (WindowArray)gch.Target;
itw.Add(hwnd);
return true;
}
// public ,
public WindowArray() {
GCHandle gch = GCHandle.Alloc(this);
EnumWindowsCB ewcb = new EnumWindowsCB(MyEnumWindowsCB);
EnumWindows(ewcb, (IntPtr)gch);
gch.Free();
}
}
}
このクラスはEnumWindowsを1つの配列にカプセル化し、煩雑な依頼やコールバックを行う必要はありません.次のように簡単に使用できます.
WindowArray wins = new WindowArray();
foreach (int hwnd in wins) {
// do something
}
かっこいいじゃないか!管理C++でDllImportスタイルのパッケージクラスを使用することもできます..NET環境では,対応する変換さえできれば,受管と非受管の世界の間で自由に採用できるが,多くの場合の変換は自動的であり,あまり多くのことに関心を持つ必要はない.MarshalAsまたはGCHandleのパッケージ化が必要な場合は少ない.C#と非管理C++間のプラットフォーム呼び出しの詳細については、.NETの関連ドキュメントを参照してください.以下に、本明細書で提供されるスイッチ付きコンソールウィジェットListWinを示す.その機能はすべてのトップレベルのウィンドウをリストし、出力はHWNDs、ウィンドウクラス名、ウィンドウタイトル、およびウィンドウ矩形を表示し、RECTまたはRectangleを使用することができます.これらのコンテンツの表示はスイッチで制御できます.
本稿で述べたことが皆さんのC#プログラム設計に役立つことを願っています.