Столкнулся с особенностью такой фичи с .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'у. Сам буду дальше изучать. Если интересно, выложу, что накопаю.