分析器API正在通過DllImportAttribute返回託管代碼中指定的元數據。在使用Marshal.GetDelegateForFunctionPointer方法的「動態拼貼」的情況下,模塊和函數名稱從未被指定爲元數據並且不可用。包含所需元數據的動態拼寫聲明的替代方法可能會避免此問題。嘗試使用System.Reflection.Emit API(如TypeBuilder.DefinePInvokeMethod)作爲一個解決方案。
這是一個使用System.Reflection.Emit的示例,它可以與分析器API一起工作。
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還會搜索user32.dll,以便爲我們的入口添加W.這意味着MessageBoxW實際上是我們調用的本機函數。但是,GetPinvokeMap返回'MessageBox'作爲函數名稱(代碼中的module_name變量)。
現在讓我們通過序號而不是名稱來調用函數。使用Windows SDK中的dumpbin程序:
dumpbin /exports C:\Windows\SysWOW64\user32.dll
...
2046 215 0006FD3F MessageBoxW
...
2046是MessageBoxW的序號。調整我們的DllImport聲明使用入口點領域,我們得到:
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "#2046")]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, int options);
這次GetPInvokeMap返回「#2046」。我們可以看到,剖析器對被調用的本地函數的「名稱」一無所知。
更進一步,被調用的本地代碼可能甚至沒有名稱。在以下示例中,運行時在可執行內存中創建了「添加」功能。沒有函數名稱或庫曾經與正在執行的本機代碼相關聯。
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);
}
}
}
很好的問題!!!你試過(當你得到「dynamic_pinvoke」)跳過GetPinvokeMap並切換到StackWalk64系列函數嗎? (http://msdn.microsoft.com/en-us/library/windows/desktop/ms680650(v=vs.85).aspx) –
在所有這些調用中記錄HRESULT返回值。 –
@HansPassant:所有調用都返回S_OK,但GetPinvokeMap以0x80131130結尾(CLDB_E_RECORD_NOTFOUND)。 – dud3