はじめに
C#では以下の手順でアンマネージ関数のP/Invoke時にマーシャリングをカスタマイズできます。
ICustomMarshaler
インターフェイスを実装したクラスを作成する。- 適用するアンマネージ関数の引数または戻り値に属性
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(<カスタムマーシャラー>))]
を指定する。
以下ではWin32APIの関数内でメモリに割り当てられた文字列に対するカスタムマーシャラーを紹介します。
カスタムマーシャラー
CoTaskMem→Unicode文字列
Win32APIのCoTaskMemFree
関数あるいはC#のMarshal.FreeCoTaskMem
関数で解放するメモリ上の文字列のカスタムマーシャラーです。
internal sealed class CoTaskMemToStringUniMarshaler : ICustomMarshaler { private static CoTaskMemToStringUniMarshaler instance; internal static ICustomMarshaler GetInstance(string cookie) { return instance ??= new CoTaskMemToStringUniMarshaler(); } public void CleanUpManagedData(object ManagedObj) => throw new NotImplementedException(); public IntPtr MarshalManagedToNative(object ManagedObj) => throw new NotImplementedException(); public object MarshalNativeToManaged(IntPtr pNativeData) => Marshal.PtrToStringUni(pNativeData); public void CleanUpNativeData(IntPtr pNativeData) => Marshal.FreeCoTaskMem(pNativeData); public int GetNativeDataSize() => -1; // 値型ではない。 }
HLOCAL
→Unicode文字列
Win32APIのLocalFree
関数で解放するメモリ上の文字列のカスタムマーシャラーです。
internal sealed class LocalMemToStringUniMarshaler : ICustomMarshaler { private static LocalMemToStringUniMarshaler instance; internal static ICustomMarshaler GetInstance(string cookie) { return instance ??= new LocalMemToStringUniMarshaler(); } public void CleanUpManagedData(object ManagedObj) => throw new NotImplementedException(); public IntPtr MarshalManagedToNative(object ManagedObj) => throw new NotImplementedException(); public object MarshalNativeToManaged(IntPtr pNativeData) => Marshal.PtrToStringUni(pNativeData); public void CleanUpNativeData(IntPtr pNativeData) => NativeMethods.LocalFree(pNativeData); public int GetNativeDataSize() => -1; // 値型ではない。 private static class NativeMethods { [DllImport("kernel32.dll")] public static extern IntPtr LocalFree(IntPtr hMem); } }
FormatMessageW
の例
Win32APIのFormatMessageW
関数はFORMAT_MESSAGE_ALLOCATE_BUFFER
フラグを指定することでメモリ上に割り当てられた文字列を取得できます。この文字列はLocalFree
関数で解放するので、上記のカスタムマーシャラーが使用できます。
using System; using System.Runtime.InteropServices; class Program { static void Main() { const int ERROR_OUTOFMEMORY = 14; NativeMethods.FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, default, ERROR_OUTOFMEMORY, 0, out string message, 0, default); Console.WriteLine(message); // -> "メモリ リソースが不足しているため、この操作を完了できません。" } private const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100; private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; private static class NativeMethods { [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] public static extern uint FormatMessageW( uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(LocalMemToStringUniMarshaler))] out string lpBuffer, uint nSize, IntPtr Arguments); } }