[Programming IL]Delegates, 代理

概念

From ECMA:

Delegates are the object-oriented equivalent of function pointers. Unlike function pointers, delegates are object-oriented, type-safe, and secure.

代理是面向对象中函数指针的替代。不像函数指针一样,代理面向对象,类型安全,运行安全。

While, for the most part, delegates appear to be simply another kind of user-defined class, they are tightly controlled. The implementations of the methods are provided by the VES, not user code. The only additional members that can be defined on delegate types are static or instance methods.

除了实例构造函数和Invoke方法之外,代理还可以有两个方法: BeginInvoke, EndInvoke. 他们用于异步调用。同时,大多数时候,代理就像另一种用户定义的类,他们紧紧的控制,方法的实现由VES(Virtual Execution Engine)提供, 而不是用户代码。能定义在代理类型上的只有静态或者实例方法。

从实例开始

我们定义一个简单的Delegate, 然后反编译下 IL

using System;

using System.Collections.Generic;

 

public delegate void MyDelegate(string str);

public class MyClass

{    

    public static void Main()

    {

        MyDelegate del = new MyDelegate(Console.WriteLine);

        del("Hello, I am using delegate to print message");

    

        Console.Read();

    }

}

IL代码

我们摘取其中重要的部分,其他的部分参考[Programming IL]创建一个类,How To Declare A Class

声明一个代理实例

public delegate void MyDelegate(string str);

.class public auto ansi sealed MyDelegate

extends [mscorlib]System.MulticastDelegate

{

.method public hidebysig specialname rtspecialname

instance void .ctor(object 'object',

native int 'method') runtime managed

{

} // end of method MyDelegate::.ctor

 

.method public hidebysig newslot virtual

instance void Invoke(string str) runtime managed

{

} // end of method MyDelegate::Invoke

 

.method public hidebysig newslot virtual

instance class [mscorlib]System.IAsyncResult

BeginInvoke(string str,

class [mscorlib]System.AsyncCallback callback,

object 'object') runtime managed

{

} // end of method MyDelegate::BeginInvoke

 

.method public hidebysig newslot virtual

instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed

{

} // end of method MyDelegate::EndInvoke

 

} // end of class MyDelegate

不用我多说了,声明一个代理就是创建一个类

MyDelegate del = new MyDelegate(Console.WriteLine);

del("Hello, I am using delegate to print message");

IL_0001: ldnull

IL_0002: ldftn void [mscorlib]System.Console::WriteLine(string)

IL_0008: newobj instance void MyDelegate::.ctor(object,

native int)

IL_000d: stloc.0

IL_000e: ldloc.0

IL_000f: ldstr "Hello, I am using delegate to print message"

IL_0014: callvirt instance void MyDelegate::Invoke(string)

也许,有几个指令需要说明一下

Ldftn

得到方法指针. Ldftn指令包含了CIL中如何确定方法的元数据标示,还有得到方法地址的指令.

Ldnull

加载一个空指针

Newobj

创建一个新对象

Stloc

从栈里弹出值到到变量中

Ldloc

从变量中存储数据到栈中

IL已经很明显了,调用对象就是调用实例化构造函数而后调用方法

再来个复杂点的, 例子代码在这里

BeginInvoke

IL_002a: callvirt instance class [mscorlib]System.IAsyncResult AsyncPatterns.AsyncTargetCaller::BeginInvoke(int32, class [mscorlib]System.AsyncCallback, object)

EndInvoke

IL_0050: callvirt instance int64 AsyncPatterns.AsyncTargetCaller::EndInvoke(class [mscorlib]System.IAsyncResult)

WaitHandle

while (!result.IsCompleted)

{

result.AsyncWaitHandle.WaitOne(1000);

Console.WriteLine("Waiting for you");

}

IL_001b: stloc.1

IL_001c: br.s IL_003c

IL_001e: nop

IL_001f: ldloc.1

IL_0020: callvirt instance class [mscorlib]System.Threading.WaitHandle [mscorlib]System.IAsyncResult::get_AsyncWaitHandle()

IL_0025: ldc.i4 0x3e8

IL_002a: callvirt instance bool [mscorlib]System.Threading.WaitHandle::WaitOne(int32)

IL_002f: pop

IL_0030: ldstr "Waiting for you"

IL_0035: call void [mscorlib]System.Console::WriteLine(string)

IL_003a: nop

IL_003b: nop

IL_003c: ldloc.1

IL_003d: callvirt instance bool [mscorlib]System.IAsyncResult::get_IsCompleted()

IL_0042: ldc.i4.0

IL_0043: ceq

IL_0045: stloc.2

IL_0046: ldloc.2

IL_0047: brtrue.s IL_001e

到这里,IL层次的东西就比较清晰了,然而,笔者还是想再深入一些,打开SSCLI/BCL/System/Delegate.cs

Delegate In SSCLI 2

构造函数

代码

// This constructor is called from the class generated by the

// compiler generated code

protected Delegate(Object target,String method) {

if (target == null) throw new ArgumentNullException("target");  

if (method == null) throw new ArgumentNullException("method");  

// This API existed in v1/v1.1 and only expected to create closed

// instance delegates. Constrain the call to BindToMethodName to

// such and don't allow relaxed signature matching (which could make

// the choice of target method ambiguous) for backwards

// compatibility. The name matching was case sensitive and we

// preserve that as well.

if (!BindToMethodName(target, Type.GetTypeHandle(target), method, DelegateBindingFlags.InstanceMethodOnly | DelegateBindingFlags.ClosedDelegateOnly))

throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTargMeth"));

}

 

// This constructor is called from a class to generate a

// delegate based upon a static method name and the Type object

// for the class defining the method.

protected unsafe Delegate(Type target,String method)

{

if (target == null) throw new ArgumentNullException("target");  

if (!(target is RuntimeType)) throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeType"), "target");  

if (target.IsGenericType && target.ContainsGenericParameters) throw new ArgumentException(Environment.GetResourceString("Arg_UnboundGenParam"), "target");  

if (method == null) throw new ArgumentNullException("method");  

// This API existed in v1/v1.1 and only expected to create open

// static delegates. Constrain the call to BindToMethodName to such

// and don't allow relaxed signature matching (which could make the

// choice of target method ambiguous) for backwards compatibility.

// The name matching was case insensitive (no idea why this is

// different from the constructor above) and we preserve that as

// well.

BindToMethodName(null, target.TypeHandle, method,

DelegateBindingFlags.StaticMethodOnly | DelegateBindingFlags.OpenDelegateOnly | DelegateBindingFlags.CaselessMatching);

}

可以看到,他们都是要调用BindToMethodName, 我们再看看对应的代码

[MethodImplAttribute(MethodImplOptions.InternalCall)]

private extern bool BindToMethodName(Object target, RuntimeTypeHandle methodType, String method, DelegateBindingFlags flags);

MethodImplAttribute:             Specifies the details of how a method is implemented. 确定一个方法如何执行的细节

MethodImplOptions.InternalCall:    Specifies an internal call. An internal call is a call to a method that is implemented within the common language runtime itself. 指定一个内部调用,这个内部调用是调用一个由通用语言运行时本身的实现

那既然如此,就是应该在SSCLI文件夹下面的实现了, 找到ComDelegate.cpp这个类

FCIMPL5(FC_BOOL_RET, COMDelegate::BindToMethodName,
                        Object *refThisUNSAFE,
                        Object *targetUNSAFE,
                        void *methodTH,
                        StringObject* methodNameUNSAFE,
                        int flags)
{
    CONTRACTL
    {
        THROWS;
        DISABLED(GC_TRIGGERS);
        MODE_COOPERATIVE;
        SO_TOLERANT;
    }
    CONTRACTL_END;

    MethodDesc *pMatchingMethod = NULL;
    struct _gc
    {
        DELEGATEREF refThis;
        OBJECTREF target;
        STRINGREF methodName;
    } gc;

    gc.refThis    = (DELEGATEREF) ObjectToOBJECTREF(refThisUNSAFE);
    gc.target     = (OBJECTREF) targetUNSAFE;
    gc.methodName = (STRINGREF) methodNameUNSAFE;

    HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);

    // Caching of MethodDescs (impl and decl) for MethodTable slots provided significant
    // performance gain in some reflection emit scenarios.
    MethodTable::AllowMethodDataCaching();

    TypeHandle methodType = TypeHandle::FromPtr(methodTH);

    TypeHandle targetType((gc.target != NULL) ? gc.target->GetTrueMethodTable() : NULL);
    // get the invoke of the delegate
    TypeHandle delegateType = gc.refThis->GetTypeHandle();
    MethodDesc* pInvokeMeth = COMDelegate::FindDelegateInvokeMethod(delegateType.GetMethodTable());
    _ASSERTE(pInvokeMeth);

    //
    // now loop through the methods looking for a match
    //

    // get the name in UTF8 format
    SString wszName(SString::Literal, gc.methodName->GetBuffer());
    StackScratchBuffer utf8Name;
    LPCUTF8 szNameStr = wszName.GetUTF8(utf8Name);

    // pick a proper compare function
    typedef int (__cdecl *UTF8StringCompareFuncPtr)(const char *, const char *);
    UTF8StringCompareFuncPtr StrCompFunc = (flags & DBF_CaselessMatching) ? stricmpUTF8 : strcmp;
    // search the type hierarchy
    MethodTable *pMTOrig = methodType.GetMethodTable()->GetCanonicalMethodTable();
    for (MethodTable *pMT = pMTOrig; pMT != NULL; pMT = pMT->GetParentMethodTable())
    {
        Module * curModule = NULL;
        MethodTable * curMethodMT = NULL;
        MethodTable::MethodIterator it(pMT);
        it.MoveToEnd();
        for (; it.IsValid() && (pMT == pMTOrig || !it.IsVirtual()); it.Prev())
        {
            MethodDesc *pCurMethod = it.GetDeclMethodDesc();
            // We can't match generic methods (since no instantiation information has been provided).
            if (pCurMethod->IsGenericMethodDefinition())
                continue;

            //  update the current module if needed, so we won't fetch it over and over again
            MethodTable * methodMT = pCurMethod->GetMethodDescChunk()->GetMethodTable();
            if ( curMethodMT != methodMT )
            {
                curMethodMT = methodMT;
                curModule = curMethodMT->GetModule();
            }

            // faster equivalent of pCurMethod->GetName((USHORT) it.GetSlotNumber())
            PREFIX_ASSUME(curModule != NULL);
            LPCUTF8 curMethodName = curModule->GetMDImport()->GetNameOfMethodDef( pCurMethod->GetMemberDef() );
            CONSISTENCY_CHECK( curMethodName == pCurMethod->GetName((USHORT) it.GetSlotNumber()) );
            if ((pCurMethod != NULL) && (StrCompFunc(szNameStr, curMethodName) == 0))
            {
                // found a matching string, get an associated if needed
                pCurMethod =
                    MethodDesc::FindOrCreateAssociatedMethodDesc(pCurMethod,
                                                                 methodType.GetMethodTable(),
                                                                 (!pCurMethod->IsStatic() && pCurMethod->GetMethodTable()->IsValueType() && !pCurMethod->IsUnboxingStub()),
                                                                 pCurMethod->GetNumGenericMethodArgs(),
                                                                 pCurMethod->GetMethodInstantiation(),
                                                                 false /* do not allow code with a shared-code calling convention to be returned */ );
                BOOL fIsOpenDelegate;
                if (COMDelegate::IsMethodDescCompatible((gc.target == NULL) ? TypeHandle() : gc.target->GetTrueTypeHandle(),
                                                        methodType,
                                                        pCurMethod,
                                                        gc.refThis->GetTypeHandle(),
                                                        pInvokeMeth,
                                                        flags,
                                                        &fIsOpenDelegate))
                {
                    // Initialize the delegate to point to the target method.
                    BindToMethod(&gc.refThis,
                                 &gc.target,
                                 pCurMethod,
                                 (MethodTable*)methodTH,
                                 fIsOpenDelegate,
                                 TRUE);

                    pMatchingMethod = pCurMethod;
                    goto done;
                }
            }
        }
    }
    done:
        ;
    HELPER_METHOD_FRAME_END();

    FC_RETURN_BOOL(pMatchingMethod != NULL);
}

可以看到,回去找元数据中的MethodTable,而后根据MethodName找到对应的方法,而后调用,也可以看到,还有 BindToMethod, BindToMethodInfo也是类似的行为。

在对于MethodTable则是属于Object中ObjHeader的部分, lbq在这里有文章解释

Shared Source CLI 2.0 Internals 里也有解释

讲到这里,有些问题还是不够清晰,到这里先暂时中止好了 :)

参考

Ecma-335

Shared Source CLI 2.0 Internals

SSCLI 2.0

posted on 2008-11-18 12:34  xwang  阅读(...)  评论(... 编辑 收藏

导航

统计