プロファイラー API は、通常はDllImportAttributeを介して、マネージ コードで指定されたメタデータを返します。Marshal.GetDelegateForFunctionPointerメソッドを使用する「動的 pinvoke」の場合、モジュールと関数の名前がメタデータとして指定されておらず、利用できませんでした。必要なメタデータを含む動的 pinvoke 宣言への代替アプローチは、おそらくこの問題を回避します。TypeBuilder.DefinePInvokeMethodなどの System.Reflection.Emit APIを 1 つの解決策として使用してみてください。
以下は、プロファイラー API で機能する System.Reflection.Emit を使用した例です。
using System;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Reflection;
namespace DynamicCodeCSharp
{
class Program
{
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
private delegate int MessageBoxFunc(IntPtr hWnd, string text, string caption, int options);
static readonly Type[] MessageBoxArgTypes = new Type[] { typeof(IntPtr), typeof(string), typeof(string), typeof(int)};
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);
static MethodInfo BuildMessageBoxPInvoke(string module, string proc)
{
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(module), AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(module);
TypeBuilder typeBuilder = moduleBuilder.DefineType(proc);
typeBuilder.DefinePInvokeMethod(proc, module, proc,
MethodAttributes.Static | MethodAttributes.PinvokeImpl,
CallingConventions.Standard, typeof
(int), MessageBoxArgTypes,
CallingConvention.StdCall, CharSet.Auto);
Type type = typeBuilder.CreateType();
return type.GetMethod(proc, BindingFlags.Static | BindingFlags.NonPublic); ;
}
static MessageBoxFunc CreateFunc()
{
MethodInfo methodInfo = BuildMessageBoxPInvoke("user32.dll", "MessageBox");
return (MessageBoxFunc)Delegate.CreateDelegate(typeof(MessageBoxFunc), methodInfo);
}
static void Main(string[] args)
{
MessageBoxFunc func = CreateFunc();
func(IntPtr.Zero, "Hello World", "From C#", 0);
}
}
}
現在のアプローチの問題を示すいくつかの例。
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, int options);
static void Main(string[] args)
{
MessageBox(IntPtr.Zero, "Hello World", "From C#", 0);
}
user32.dll からエクスポートされた MessageBox 関数はありません。MessageBoxA と MessageBoxW のみが含まれます。DllImport 属性でExactSpelling=falseを指定せず、CharSet が Unicode であるため、.Net は、W が追加されたエントリ ポイントの user32.dll も検索します。これは、MessageBoxW が実際に呼び出しているネイティブ関数であることを意味します。ただし、GetPinvokeMap は関数名 (コード内の module_name 変数) として「MessageBox」を返します。
代わりに、名前ではなく序数を介して関数を呼び出すことができます。Windows SDK で dumpbin プログラムを使用する:
dumpbin /exports C:\Windows\SysWOW64\user32.dll
...
2046 215 0006FD3F MessageBoxW
...
2046 は MessageBoxW の序数です。取得した EntryPoint フィールドを使用するように DllImport 宣言を調整します。
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "#2046")]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, int options);
今度は GetPInvokeMap が「#2046」を返します。プロファイラーは、呼び出されるネイティブ関数の「名前」について何も知らないことがわかります。
さらに言えば、呼び出されるネイティブ コードには名前さえない場合があります。次の例では、'Add' 関数が実行時に実行可能メモリに作成されます。実行中のネイティブ コードに関数名またはライブラリが関連付けられたことはありません。
using System;
using System.Runtime.InteropServices;
namespace DynamicCodeCSharp
{
class Program
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int AddFunc(int a, int b);
[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr VirtualAlloc(IntPtr addr, IntPtr size, int allocType, int protectType);
const int MEM_COMMIT = 0x1000;
const int MEM_RESERVE = 0x2000;
const int PAGE_EXECUTE_READWRITE = 0x40;
static readonly byte[] buf =
{
// push ebp
0x55,
// mov ebp, esp
0x8b, 0xec,
// mov eax, [ebp + 8]
0x8b, 0x45, 0x08,
// add eax, [ebp + 8]
0x03, 0x45, 0x0c,
// pop ebp
0x5d,
// ret
0xc3
};
static AddFunc CreateFunc()
{
// allocate some executable memory
IntPtr code = VirtualAlloc(IntPtr.Zero, (IntPtr)buf.Length, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// copy our add function implementation into the memory
Marshal.Copy(buf, 0, code, buf.Length);
// create a delegate to this executable memory
return (AddFunc)Marshal.GetDelegateForFunctionPointer(code, typeof(AddFunc));
}
static void Main(string[] args)
{
AddFunc func = CreateFunc();
int value = func(10, 20);
Console.WriteLine(value);
}
}
}