Embed Interop Types vs. o__SiteContainer0 vs. ConfuserEx
От: Fortnum  
Дата: 25.09.14 17:29
Оценка: 120 (4)
Столкнулся с особенностью такой фичи с .Net 4 как Embed Interop Types. Столкнулся на примере со стандартной для фреймворка Interop-сборки Accessibility.dll, которая служит для работы с подсистемой Microsoft Active Accessibility, и там дано определение интерфейсу IAccessible.

Конкретного вопроса нет, но хотел бы получить больше информации по данной особенности, т.к. до того как сам лично с ней столкнулся, даже не подозревал о ней.

Итак, по порядку. Началось с того, что я обратил внимание, что обфускатор ConfuserEx отказывается обфусцировать (переименовывать) мой статический класс, в котором объявлен следующий метод:

  Скрытый текст
public static IAccessible GetRootParent(this IAccessible child)
{
    var parent = child;

    while (parent.accParent != null)
    {
        parent = (IAccessible)parent.accParent;
    }

    return parent;
}


Заглянув при помощи ILSpy в необфусцированную сборку был сильно удивлен, увидев следующее:

  Скрытый текст
namespace Accessibility
{
    [CompilerGenerated, Guid("618736E0-3C3D-11CF-810C-00AA00389B71"), TypeIdentifier]
    [ComImport]
    public interface IAccessible
    {
        object accParent
        {
            [DispId(-5000)]
            [return: MarshalAs(UnmanagedType.IDispatch)]
            get;
        }
    }
}

namespace MyNamespace
{

...

public static class MyClass
{
    [CompilerGenerated]
    private static class <GetRootParent>o__SiteContainer0
    {
        public static CallSite<Func<CallSite, object, IAccessible>> <>p__Site1;
        public static CallSite<Func<CallSite, object, bool>> <>p__Site2;
        public static CallSite<Func<CallSite, object, object, object>> <>p__Site3;
    }

    public static IAccessible GetRootParent(this IAccessible child)
    {
        IAccessible parent = child;
        while (true)
        {
            if (ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site2 == null)
            {
                ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site2 = CallSite<Func<CallSite, object, bool>>.Create(
                    Binder.UnaryOperation(CSharpBinderFlags.None, ExpressionType.IsTrue, typeof(ActiveAccessibilityHelper), new CSharpArgumentInfo[]
                    {
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                    }));
            }

            Func<CallSite, object, bool> arg_F2_0 = ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site2.Target;
            
            CallSite arg_F2_1 = ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site2;
                
            if (ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site3 == null)
            {
                ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site3 = CallSite<Func<CallSite, object, object, object>>.Create(
                    Binder.BinaryOperation(CSharpBinderFlags.None, ExpressionType.NotEqual, typeof(ActiveAccessibilityHelper), new CSharpArgumentInfo[]
                    {
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.Constant, null)
                    }));
            }                

            if (!arg_F2_0(arg_F2_1, ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site3.Target(
                    ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site3, parent.accParent,
                    null)))
            {
                break;
            }
                
            if (ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site1 == null)
            {
                ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site1 =
                    CallSite<Func<CallSite, object, IAccessible>>.Create(Binder.Convert(CSharpBinderFlags.ConvertExplicit,
                        typeof(IAccessible), typeof(ActiveAccessibilityHelper)));
            }

            parent = ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site1.Target(ActiveAccessibilityHelper.<GetRootParent>o__SiteContainer0.<>p__Site1, parent.accParent);
        }
        
        return parent;
    }

    ...


Конечно, такое, наверное, можно не обфусцировать, подумал я. Но тем не менее решил разобраться, что это такое.

Сразу отмечу, что в Accessible.dll интерфейс IAccessible определен следующим образом:

  Скрытый текст
[Guid("618736E0-3C3D-11CF-810C-00AA00389B71"), TypeLibType(TypeLibTypeFlags.FHidden | TypeLibTypeFlags.FDual | TypeLibTypeFlags.FDispatchable)]
[ComImport]
public interface IAccessible
{
    ...

    [DispId(-5000)]
    object accParent
    {
        [DispId(-5000), TypeLibFunc(TypeLibFuncFlags.FHidden)]
        [MethodImpl(MethodImplOptions.InternalCall)]
        [return: MarshalAs(UnmanagedType.IDispatch)]
        get;
    }

    ...
}


, т.е. IAccessible объявлен как [InterfaceType(ComInterfaceType.InterfaceIsDual)], чему соответствующие объекты, судя по описанию, и должны соответствовать.

Несколько поэкспериментировав, я пришел к выводу, что вышеприведенный "бешенный" код есть результат выставления свойства Embed Interop Types ссылки на Accessibility.dll в referenc'ах проекта в true. Если Embed Interop Types поставить в false, то код генерится совершенно нормальный. При этом пропадает помеченный атрибутом CompilerGenerated вложенный в определение типа MyClass приватный класс o__SiteContainer0, пропадает пространство имен Accessibility, а, главное, обфускация моего статического класса ConfuserEx'ом так же начинает работать.

Ближе к делу. Я написал небольшую тестовую программку следующего содержания:

  Скрытый текст
namespace MyNamespace
{
    public static class MyClass
    {
        static void Main()
        {
            var calcProcess = Process.GetProcessesByName("calc")[0];

            var hWnd = calcProcess.MainWindowHandle;

            var stopWatch = new Stopwatch();

            stopWatch.Start();

            for (int i = 0; i < 10000; i++)
            {
                IAccessible accessible;

                AccessibleObjectFromWindow(hWnd, OBJID_WINDOW, IID_IAccessible, out accessible);

                GetRootParent(accessible);
            }

            stopWatch.Stop();

            Console.WriteLine(stopWatch.ElapsedMilliseconds);

            Console.ReadKey(true);
        }

        static readonly Guid IID_IAccessible = new Guid(0x618736e0, 0x3c3d, 0x11cf, 0x81, 0x0c, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);

        const int OBJID_WINDOW = 0;

        [DllImport("oleacc.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern int AccessibleObjectFromWindow(IntPtr hWnd, int objId, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.Interface)] out IAccessible obj);

        static IAccessible GetRootParent(this IAccessible child)
        {
            var parent = child;

            while (parent.accParent != null)
            {
                parent = (IAccessible)parent.accParent;
            }

            return parent;
        }
    }
}


И теперь думаю, а почему, собственно, при Embed Interop Types = false у ссылки на Accessibility.dll ее выполнение занимает на моей машине 1 секунду, а при Embed Interop Types = true ее выполнение занимает 10 секунд?

То есть вопрос, собственно, в чем... этот "бешенный" код очень походит на код, генерируемый при использовании ключевого слова dynamic, который вроде как дает замедление порядка 100 раз. То есть Embed Interop Types = true так же дает задержку? Или может быть это как-то связано с тем, что интерфейс дуальный, и во втором случае вызов идет через IDispatch... тогда непонятно, почему ILSpy в обоих случаях показывает содержание Main метода одинаковым, без всяких этих CallSite'ов... хотел привести его код в ILSpy, но он 100% такой же как в исходниках (ildasm'ом не смотрел).

Упростил GetRootParent до следующего содержания:
  Скрытый текст
static IAccessible GetRootParent(this IAccessible child)
{
    var parent = child.accParent;

    return parent;
}


Получил в ILSpy следующее:

  Скрытый текст
public static class MyClass
{
    [CompilerGenerated]
    private static class <GetRootParent>o__SiteContainer0
    {
        public static CallSite<Func<CallSite, object, IAccessible>> <>p__Site1;
    }

    private static IAccessible GetRootParent(this IAccessible child)
    {
        object parent = child.accParent;
    
        if (MyClass.<GetRootParent>o__SiteContainer0.<>p__Site1 == null)
        {
            MyClass.<GetRootParent>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, IAccessible>>.Create(
                Binder.Convert(CSharpBinderFlags.None, typeof(IAccessible), typeof(MyClass)));
        }

        return MyClass.<GetRootParent>o__SiteContainer0.<>p__Site1.Target(MyClass.<GetRootParent>o__SiteContainer0.<>p__Site1, parent);
    }

    ...


По ConfuserEx'у еще. Нашел я то место, где он помечает, что MyClass нельзя обфусцировать, это в файле LdtokenEnumAnalyzer.cs происходит. Задание типу MyClass не переименовываться происходит при обработке метода MyClass.GetRootParent на инструкции вида:

IL_0010:  ldtoken    Accessibility.IAccessible.


Что это такое, и почему при наличии такой инструкции в одном из методов класса он помечает весь класс к запрету на переименование, в эту логику я пока не врубился. Вот, ниже в коде я пометил то место, где он решает пометить MyClass к запрету на переименование из-за инструкции ldtoken в методе GetRootParent:

  Скрытый текст
namespace Confuser.Renamer.Analyzers {
    internal class LdtokenEnumAnalyzer : IRenamer {

        public void Analyze(ConfuserContext context, INameService service, IDnlibDef def) {
            var method = def as MethodDef;
            if (method == null || !method.HasBody)
                return;

            // When a ldtoken instruction reference a definition,
            // most likely it would be used in reflection and thus probably should not be renamed.
            // Also, when ToString is invoked on enum,
            // the enum should not be renamed.
            for (int i = 0; i < method.Body.Instructions.Count; i++) {
                Instruction instr = method.Body.Instructions[i];
                if (instr.OpCode.Code == Code.Ldtoken) {
                    if (instr.Operand is MemberRef) {
                        IMemberForwarded member = ((MemberRef)instr.Operand).ResolveThrow();
                        if (context.Modules.Contains((ModuleDefMD)member.Module))
                            service.SetCanRename(member, false);
                    }
                    else if (instr.Operand is IField) {
                        FieldDef field = ((IField)instr.Operand).ResolveThrow();
                        if (context.Modules.Contains((ModuleDefMD)field.Module))
                            service.SetCanRename(field, false);
                    }
                    else if (instr.Operand is IMethod) {
                        MethodDef m = ((IMethod)instr.Operand).ResolveThrow();
                        if (context.Modules.Contains((ModuleDefMD)m.Module))
                            service.SetCanRename(method, false);
                    }
                    else if (instr.Operand is ITypeDefOrRef) {  // Вот здесь обнаруживает этот ldtoken
                        if (!(instr.Operand is TypeSpec)) {
                            TypeDef type = ((ITypeDefOrRef)instr.Operand).ResolveTypeDefThrow();
                            if (context.Modules.Contains((ModuleDefMD)type.Module) &&
                                HandleTypeOf(context, service, method, i)) 
                                DisableRename(service, type, false); // А здесь помечает весь MyClass к запрету на переименование
                        }
                    }
                    else
                        throw new UnreachableException();
                }
                else if ((instr.OpCode.Code == Code.Call || instr.OpCode.Code == Code.Callvirt) &&
                         ((IMethod)instr.Operand).Name == "ToString") {
                    HandleEnum(context, service, method, i);
                }
                else if (instr.OpCode.Code == Code.Ldstr) {
                    TypeDef typeDef = method.Module.FindReflection((string)instr.Operand);
                    if (typeDef != null)
                        service.AddReference(typeDef, new StringTypeReference(instr, typeDef));
                }
            }
        }


Так что если есть у кого какие мысли по этому поводу, буду весьма благодарен! И по Embed Interop Types true/false (скорости выполнения, InterfaceIsDual, и "бешеному" коду), и по ConfuserEx'у. Сам буду дальше изучать. Если интересно, выложу, что накопаю.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.