サーチ…


備考

C#を使用したWin32 APIの操作

Windowsは、Win32 APIの形式で多くの機能を公開しています。これらのAPIを使用すると、ウィンドウで直接操作でき、アプリケーションのパフォーマンスが向上します。

Windowsは幅広いAPIを公開しています。さまざまなAPIに関する情報を得るには、 ピンボケのようなサイトをチェックできます。

アンマネージ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"__stdcallが必要な理由については、 extern "C" 慣習C ++ネームマングリングの 呼び出し 」を参照してください。

動的ライブラリの検索

externメソッドが最初に呼び出されると、C#プログラムは適切なDLLを検索してロードします。 DLLの場所を検索する方法と、検索場所にどのように影響するかについては、 このstackoverflowの質問を参照してください。

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

C ++ネームマングリング

C ++コンパイラは、異なる引数を持つオーバーロードを可能にするために、引数型などのエクスポートされた関数の名前に追加情報をエンコードします。このプロセスをネームマングリングといいます。これは、の名前として、C#の(と一般的には他の言語との相互運用機能)の関数をインポートすると問題が発生するint add(int a, int b)機能は、もはやadd 、それはすることができ、 ?add@@YAHHH@Z_add@8コンパイラおよび呼び出し規約に応じて、 _add@8またはその他のものを使用します。

名前のマングリングの問題を解決する方法はいくつかあります。

  • extern "C"を使用して関数をエクスポートして、C名前のマングリングを使用するC外部リンケージに切り替えます。

    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になります。

  • 変更された名前をインポートする。変換された名前を表示するには、いくつかのDLLビューアが必要です。明示的に指定することができます:

    __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
    

呼び出し規約

コール元関数のいくつかの規則があります。呼び出し元(呼び出し元または呼び出し先)がスタックから引数を取り出す方法、引数を渡す方法、順序を指定する方法があります。 C ++ではデフォルトでCdecl呼び出し規約が使用されていますが、C#ではWindows APIで通常使用されるStdCallが必要です。あなたはどちらか一方を変更する必要があります:

  • C ++の呼び出し規約をStdCallに変更する:

    extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
    
    [DllImport("myDLL.dll")]
    
  • または、C#の呼び出し規約をCdeclに変更します。

    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")]
  • thiscall__ thiscall )は主にクラスのメンバーである関数で使用されます。

  • 関数がthiscall__ thiscall )を使用すると、クラスへのポインタが最初のパラメータとして渡されます。

アンマネージ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エラーの処理

interopメソッドを使用する場合は、 GetLastError APIを使用して、API呼び出しに関する追加情報を取得できます。

DllImport属性SetLastError属性

SetLastError = true

呼び出し先がSetLastError(Win32 API関数)を呼び出すことを示します。

SetLastError = false

呼び出し先 SetLastError(Win32 API関数)を呼び出さないことを示します。したがって、エラー情報は取得されません。

  • SetLastErrorが設定されていない場合、false(デフォルト値)に設定されます。

  • Marshal.GetLastWin32Errorメソッドを使用してエラーコードを取得できます。

例:

[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);

存在しないmutexをオープンしようとすると、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を使用する必要があります。

理由は次のとおりです。

エラーを設定し、あなたのWin32コール(SetLastErrorを呼び出し)の間に、CLRは、他のWin32が呼び出すことができSetLastErrorとしても、この動作があなたのエラー値を上書きすることができた呼び出しを呼び出すことができます。このシナリオでは、 GetLastErrorを呼び出すと無効なエラーが発生します。

SetLastError = trueを設定すると、他のWin32呼び出しを実行する前にCLRがエラーコードを取得することを確認します。

固定オブジェクト

GC (ガベージコレクター)はゴミを掃除する責任があります。

GCはゴミを掃除しながら、管理されていないオブジェクトを管理対象のヒープから削除し、ヒープの断片化を引き起こします。 GCは、削除を行ったときに、ヒープ上のオブジェクトの移動を伴うヒープ圧縮(defusionintation)を実行します。

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を解放するのを忘れるなら、何もしません。安全なコードセクションで実行します(最終的なものなど)

マーシャルで構造を読む

MarshalクラスにはPtrToStructureという名前の関数が含まれています。この関数は、アンマネージポインタで構造体を読み取ることができます。

PtrToStructure関数には多くのオーバーロードがありますが、それらはすべて同じ意図を持っています。

一般的な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;
    }


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow