.NET 2.0 中 GetDelegateForFunctionPointer 函数实现原理浅析 [草稿]
Posted on 2004-07-22 00:49 Flier Lu 阅读(3244) 评论(1) 编辑 收藏 举报 Junfeng Zhang在其 BLog 的文章,What is new in .Net framework 2.0 (7) --- Marshal.GetDelegateForFunctionPointer,中介绍了 .NET 2.0 中如何使用 Marshal.GetDelegateForFunctionPointer 方法,直接从导入的函数指针生成 Delegate。这一功能可以大大减少使用者的薄记工作,让 interop 的操作更加自然。其文中给出的示例代码如下:
using System.Runtime.InteropServices;
using System;
public class TestClass
{
public static void Main(String[] args)
{
IntPtr user32 = LoadLibrary("user32.dll");
IntPtr procaddr = GetProcAddress(user32, "MessageBoxW");
Console.In.ReadLine();
MyMessageBox mbx = (MyMessageBox)Marshal.GetDelegateForFunctionPointer(procaddr, typeof(MyMessageBox));
mbx(IntPtr.Zero, "Hello, World", "A Test Run", 0);
}
internal delegate int MyMessageBox(IntPtr hwnd, [MarshalAs(UnmanagedType.LPWStr)]String text, [MarshalAs(UnmanagedType.LPWStr)]String Caption, int type);
[DllImport("kernel32.dll")]
internal static extern IntPtr LoadLibrary(String dllname);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetProcAddress(IntPtr hModule, String procname);
}
这一功能虽然强大,但实现代码非常简洁,通过 ILDASM 我们可以看到如下实现:
.method public hidebysig static void Main(string[] args) cil managed
{
IL_0000: ldstr "user32.dll"
IL_0005: call native int TestClass::LoadLibrary(string)
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: ldstr "MessageBoxW"
IL_0011: call native int TestClass::GetProcAddress(native int,
string)
IL_0016: stloc.1
IL_0022: ldloc.1
IL_0023: ldtoken TestClass/MyMessageBox
IL_0028: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_002d: call class [mscorlib]System.Delegate [mscorlib]System.Runtime.InteropServices.Marshal::GetDelegateForFunctionPointer(native int,
class [mscorlib]System.Type)
IL_0032: castclass TestClass/MyMessageBox
IL_0037: stloc.2
IL_0038: ldloc.2
IL_0039: ldsfld native int [mscorlib]System.IntPtr::Zero
IL_003e: ldstr "Hello, World"
IL_0043: ldstr "A Test Run"
IL_0048: ldc.i4.0
IL_0049: callvirt instance int32 TestClass/MyMessageBox::Invoke(native int,
string,
string,
int32)
IL_004e: pop
IL_004f: ret
} // end of method TestClass::Main
而在 mscorlib.dll 中,这个 Marshal::GetDelegateForFunctionPointer 函数实际上是通过一个 native 函数具体完成的:
[SuppressUnmanagedCodeSecurity]
public abstract sealed class Marshal
{
[SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
public static Delegate GetDelegateForFunctionPointer(IntPtr ptr, Type t)
{
if (ptr == IntPtr.Zero) throw new ArgumentNullException("ptr");
if (t == null) throw new ArgumentNullException("t");
if (!(t is RuntimeType)) throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeType"), "t");
Type type1 = t.BaseType;
if ((type1 == null) || ((type1 != typeof(Delegate)) && (type1 != typeof(MulticastDelegate))))
throw new ArgumentException(Environment.GetResourceString("Arg_MustBeDelegate"), "t");
return Marshal.GetDelegateForFunctionPointerInternal(ptr, t);
}
[MethodImpl(MethodImplOptions.InternalCall)]
internal static Delegate GetDelegateForFunctionPointerInternal(IntPtr ptr, Type t);
}
而 Marshal.GetDelegateForFunctionPointer 函数的具体实现,是在 mscorwks.dll 中的MarshalNative::GetDelegateForFunctionPointerInternal 完成的,伪代码如下:
OBJECTREF MarshalNative::GetDelegateForFunctionPointerInternal(LPVOID pCallback, TypeHandle *type)
{
HELPER_METHOD_FRAME_BEGIN_2(type, NULL);
if(GetThread()->m_State & TS_AbortRequested == TS_AbortRequested)
Thread::HandleThreadAbort();
OBJECTREF obj = COMDelegate::ConvertToDelegate(pCallback, type->GetMethodTable());
if(GetThread()->m_State & TS_AbortInitiated == TS_AbortInitiated)
Thread::HandleThreadAbort();
return obj;
}
除了进行堆栈保护,以及在开始和结束时处理线程中断状态以外,MarshalNative::GetDelegateForFunctionPointerInternal 实际上把大部分工作转交给 COMDelegate::ConvertToDelegate 完成。.NET Framework 2.0 中的 COMDelegate::ConvertToDelegate 函数与 Rotor 中的实现相比起来要复杂许多。Rotor 中此函数只需要处理已经被 Thunk 处理过的回调指针,对非法指针直接抛出参数错误异常。
// 检查 ptr 指向函数入口是否是已经被 Thunk 处理过的
// 如果处理过,则返回被处理后的实际入口地址,否则返回 NULL
//
// vm\i386\cgenx86.cpp:4642
//
UMEntryThunk* UMEntryThunk::Decode(LPVOID pCallback)
{
if (*((BYTE*)pCallback) != 0xb8 ||
( ((size_t)pCallback) & 3) != 2) {
return NULL;
}
return *(UMEntryThunk**)( 1 + (BYTE*)pCallback );
}
// Marshals an unmanaged callback to Delegate
//
// vm\comdelegate.cpp:500
//
OBJECTREF COMDelegate::ConvertToDelegate(LPVOID pCallback)
{
THROWSCOMPLUSEXCEPTION();
if (!pCallback) {
return NULL;
}
UMEntryThunk *pUMEntryThunk = UMEntryThunk::Decode(pCallback);
if (!pUMEntryThunk) {
COMPlusThrow(kArgumentException, IDS_EE_NOTADELEGATE);
}
return ObjectFromHandle(pUMEntryThunk->GetObjectHandle());
}
.NET 2.0 中的 COMDelegate::ConvertToDelegate 函数则相对来说较为复杂,还需要处理未 Thunk 处理的 Unmanaged 函数指针的情况,大致流程的伪代码如下:
OBJECTREF COMDelegate::ConvertToDelegate(LPVOID pCallback, MethodTable *mtType)
{
if(ptr == NULL) return NULL;
if(g_pMdaStaticHeap.m_pMda == NULL)
{
// 进行 ManagedDebuggingAssistants 相关的初始化和处理工作
}
// 检查 ptr 指向函数入口是否是已经被 Thunk 处理过的
// 如果处理过,则返回被处理后的实际入口地址,否则返回 NULL
UMEntryThunk *thunk = UMEntryThunk::Decode(pCallback);
if(thunk)
{
// 在全局唯一的函数指针到 Delegate 缓存 HashMap 中查找是否已经处理过此指针
Delegate *d = s_pDelegateToFPtrHash.LookupValue(thunk, NULL);
if(d != -1) return d;
}
// 查找此 Delegate 是否已经由 JIT 生成方法体
MethodDesc *md = FindDelegateInvokeMethod(mtType->m_pEEClass);
if(md == NULL)
{
// 使用 StubLinkerCPU 类通过 ComputeDllImportStub 函数构造方法体
}
// 对于泛型类,则使用 CreateGenericDllImportStubForDelegate 函数进行处理
// 是否启用全局安全设置
if (Security::s_dwGlobalSettings & CORSETTING_SECURITY_OFF == 0)
{
Module *mod = mtType->getModule();
PEFile *file = mod->GetPEFile();
IMDInternalImport *imp = file->GetMDImport();
if(关心文件输入节的安全性)
{
DeclActionInfo *act = Security::DetectDeclActions(md, DECLSEC_UNMNGD_ACCESS_DEMAND);
if(act)
{
// 使用 StubLinkerCPU::EmitSecurityInterceptorStub 等方法添加安全检测代码
}
}
}
_UNCHECKED_OBJECTREF oref;
SetObjectReferenceUnchecked(&oref, mtType->Allocate());
return UNCHECKED_OBJECTREF_TO_OBJECTREF(oret);
}