# アンマネージ C++ DLL から関数をインポート
アンマネージ C++ DLL で定義されている関数をインポートする方法の例を次に示します。 「myDLL.dll」の C++ ソース コードでは、関数 add
が定義されています:
extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
return a + b;
}
その後、次のように C# プログラムに含めることができます:
class Program
{
// This line will import the C++ method.
// The name specified in the DllImport attribute must be the DLL name.
// The names of parameters are unimportant, but the types must be correct.
[DllImport("myDLL.dll")]
private static extern int add(int left, int right);
static void Main(string[] args)
{
//The extern method can be called just as any other C# method.
Console.WriteLine(add(1, 2));
}
}
extern "C"
の理由については、呼び出し規則と C++ 名マングリングを参照してください。 そして __stdcall
# 動的ライブラリの検索
extern メソッドが最初に呼び出されると、C# プログラムは適切な DLL を検索してロードします。 DLL を見つけるために が検索される場所、および検索場所に影響を与える方法の詳細については、このスタックオーバーフローの質問を参照してください。
# 呼び出し規約
関数の呼び出しにはいくつかの規則があり、誰 (呼び出し元または呼び出し先) がスタックから引数をポップするか、引数がどのように、どのような順序で渡されるかを指定します。 C++ は Cdecl
を使用します デフォルトでは呼び出し規約ですが、C# では StdCall
が必要です 、通常は Windows API で使用されます。どちらかを変更する必要があります:
extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
Cdecl
で関数を使いたい場合 呼び出し規約とマングル名を使用すると、コードは次のようになります:
__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?add@@YAHHH@Z")]
# C++ 名マングリング
C++ コンパイラは、エクスポートされた関数の名前 (引数の型など) に追加情報をエンコードして、さまざまな引数を使用したオーバーロードを可能にします。このプロセスは名前マングリングと呼ばれます。 int add(int a, int b)
の名前のように、これにより、C# での関数のインポート (および一般的に他の言語との相互運用) で問題が発生します。 関数はもはや add
ではありません 、 ?add@@YAHHH@Z
の場合があります 、 _add@8
コンパイラと呼び出し規約によって異なります。
名前マングリングの問題を解決するには、いくつかの方法があります:
extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
関数名は引き続きマングルされます (_add@8
)、しかし StdCall
+extern "C"
名前マングリングは C# コンパイラによって認識されます。
myDLL.def
で指定する モジュール定義ファイル:
EXPORTS
add
int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
関数名は純粋な add
になります
__declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
# アンマネージ DLL の動的ロードおよびアンロード
DllImport
を使用する場合 コンパイル時に正しい dll とメソッド名を知っている必要があります。 . 実行時でより柔軟に決定したい場合 どの dll とメソッドを読み込むか、Windows API メソッド LoadLibrary()
を使用できます 、 GetProcAddress()
と FreeLibrary()
.これは、使用するライブラリが実行時の条件に依存する場合に役立ちます。
GetProcAddress()
によって返されるポインタ Marshal.GetDelegateForFunctionPointer()
を使用してデリゲートにキャストできます .
次のコード サンプルは、myDLL.dll
を使用してこれを示しています。 前の例から:
class Program
{
// import necessary API as shown in other examples
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadLibrary(string lib);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void FreeLibrary(IntPtr module);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr module, string proc);
// declare a delegate with the required signature
private delegate int AddDelegate(int a, int b);
private static void Main()
{
// load the dll
IntPtr module = LoadLibrary("myDLL.dll");
if (module == IntPtr.Zero) // error handling
{
Console.WriteLine($"Could not load library: {Marshal.GetLastWin32Error()}");
return;
}
// get a "pointer" to the method
IntPtr method = GetProcAddress(module, "add");
if (method == IntPtr.Zero) // error handling
{
Console.WriteLine($"Could not load method: {Marshal.GetLastWin32Error()}");
FreeLibrary(module); // unload library
return;
}
// convert "pointer" to delegate
AddDelegate add = (AddDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(AddDelegate));
// use function
int result = add(750, 300);
// unload library
FreeLibrary(module);
}
}
# Win32 エラーの処理
相互運用メソッドを使用する場合、GetLastError を使用できます API 呼び出しに関する追加情報を取得するための API。
DllImport 属性 SetLastError 属性
SetLastError=true
呼び出し先が SetLastError (Win32 API 関数) を呼び出すことを示します。
SetLastError=false
呼び出し先がしないことを示します SetLastError (Win32 API 関数) を呼び出すため、エラー情報は取得されません。
例:
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);
存在しないミューテックスを開こうとすると、GetLastError は ERROR_FILE_NOT_FOUND を返します。 .
var lastErrorCode = Marshal.GetLastWin32Error();
if (lastErrorCode == (uint)ERROR_FILE_NOT_FOUND)
{
//Deal with error
}
システム エラー コードは次の場所にあります:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
GetLastError API
ネイティブの GetLastError があります 同様に使用できる API :
[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
- マネージ コードから Win32 API を呼び出すときは、常に Marshal.GetLastWin32Error を使用する必要があります。 .
理由は次のとおりです:
エラーを設定する (SetLastError を呼び出す) Win32 呼び出しの間に、CLR は SetLastError を呼び出すことができる他の Win32 呼び出しを呼び出すことができます。 同様に、この動作はエラー値を上書きする可能性があります。このシナリオで、GetLastError を呼び出すと、 無効なエラーを取得できます。
SetLastError =true の設定 、他の Win32 呼び出しを実行する前に、CLR がエラー コードを取得することを確認します。
# Marshal で構造を読む
Marshal クラスには PtrToStructure という名前の関数が含まれています 、この関数は、アンマネージ ポインターによって構造体を読み取る機能を提供します。
PtrToStructure function には多くのオーバーロードがありますが、それらはすべて同じ意図を持っています。
ジェネリック PtrToStructure :
public static T PtrToStructure<T>(IntPtr ptr);
T - 構造タイプ。
ptr - 管理されていないメモリ ブロックへのポインタ。
例:
NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);
- ネイティブ構造の読み取り中に管理対象オブジェクトを扱う場合は、オブジェクトを固定することを忘れないでください 😃
T Read<T>(byte[] buffer)
{
T result = default(T);
var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
result = Marshal.PtrToStructure<T>(gch.AddrOfPinnedObject());
}
finally
{
gch.Free();
}
return result;
}
# com のクラスを公開する簡単なコード
using System;
using System.Runtime.InteropServices;
namespace ComLibrary
{
[ComVisible(true)]
public interface IMainType
{
int GetInt();
void StartTime();
int StopTime();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class MainType : IMainType
{
private Stopwatch stopWatch;
public int GetInt()
{
return 0;
}
public void StartTime()
{
stopWatch= new Stopwatch();
stopWatch.Start();
}
public int StopTime()
{
return (int)stopWatch.ElapsedMilliseconds;
}
}
}
# ピン留めされたオブジェクト
GC (ガベージ コレクター) は、ゴミを掃除する責任があります。
GC 中 ガベージをクリーンアップし、ヒープの断片化を引き起こす未使用のオブジェクトをマネージ ヒープから削除します。 GC の場合 削除が完了すると、ヒープ上のオブジェクトの移動を伴うヒープ圧縮 (最適化) が実行されます。
GC 以来 管理対象オブジェクトの参照/ポインタをネイティブ コードに渡す場合、GC は決定論的ではありません いつでも開始できます。Inerop 呼び出しの直後に発生した場合、オブジェクト (ネイティブに渡された参照) がマネージド ヒープ上で移動される可能性が非常に高くなります。その結果、マネージド サイドで無効な参照が取得されます。 .
このシナリオでは、固定する必要があります ネイティブ コードに渡す前のオブジェクト。
ピン留めされたオブジェクト
ピン留めされたオブジェクトは、GC によって移動が許可されていないオブジェクトです。
Gc 固定ハンドル
Gc.Alloc を使用してピン オブジェクトを作成できます メソッド
GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned);
- 固定された GCHandle の取得 特定のオブジェクトを GC で移動できないオブジェクトとしてマークする 、ハンドルを解放するまで
例:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void EnterCriticalSection(IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void LeaveCriticalSection(IntPtr ptr);
public void EnterCriticalSection(CRITICAL_SECTION section)
{
try
{
GCHandle handle = GCHandle.Alloc(section, GCHandleType.Pinned);
EnterCriticalSection(handle.AddrOfPinnedObject());
//Do Some Critical Work
LeaveCriticalSection(handle.AddrOfPinnedObject());
}
finaly
{
handle.Free()
}
}
注意事項
- (特に大きなもの) オブジェクトをピン留めするときは、ピン留めされた GcHandle を解放してみてください ヒープの最適化を中断するため、できるだけ速く。
- GcHandle を解放するのを忘れた場合 何もしません。安全なコード セクション (finaly など) で行う
# コメント
C# を使用した Win32 API の操作
Windows は、Win32 API の形式で多くの機能を公開しています。これらの API を使用すると、Windows で直接操作を実行でき、アプリケーションのパフォーマンスが向上します。ソース ここをクリック
Windows は幅広い API を公開しています。さまざまな API に関する情報を取得するには、pinvoke などのサイトをチェックしてください。