MSIL - как вызвать частный метод из MSIL?

Я пишу «фабрику слабых событий» - код, который преобразует любого делегата в нового делегата с идентичной подписью, но с реализацией WeakReference на цели. Я использую MSIL, чтобы избежать вызовов Delegate.CreateDelegate (производительность которых оказалась низкой).

Слабые ссылочные делегаты работают идеально до тех пор, пока базовый метод (Method исходного делегата) объявлен общедоступным. Как только будет использован частный или анонимный метод, MSIL закроет во время выполнения ошибку MethodAccessException.

Используя скомпилированные деревья выражений, я смог вызвать частные методы, поэтому должна быть возможность динамически генерировать MSIL, который вызывает частный метод. ... так что не так со следующим?

        // var target = this.Target
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Callvirt, targetPropGetter);         
        il.Emit(OpCodes.Stloc, ilTarget);            

        // if(target != null)
        // {
        il.Emit(OpCodes.Ldloc, ilTarget);
        il.Emit(OpCodes.Brfalse_S, ilIsNullLabel);

        //      Method( @target, parm1, parm2 ...);
        il.Emit(OpCodes.Ldloc, ilTarget);                  // this = Target
        short argIndex = 1;
        foreach (var parm in delgParams)                   // push all other args
            il.Emit(OpCodes.Ldarg, argIndex++);

        il.Emit(OpCodes.Callvirt, delegat.Method);   // <-- Bombs if method is private
        il.Emit(OpCodes.Ret);

        // }
        il.MarkLabel(ilIsNullLabel);

Так в чем же секрет вызова частного члена? Отражение может это сделать, деревья выражений могут это сделать ... почему приведенный выше код не работает?


РЕДАКТИРОВАТЬ: Большое спасибо всем, кто дал здесь ответы. Оказывается, единственным решением, которое последовательно работало в моем контексте, было использование универсальных делегатов (Action) ... поскольку Action исходит из mscorlib, JIT кажется вполне счастливым, позволяя ему вызывать частный метод . попробуйте использовать своего собственного делегата, и JIT рвет точно так же, как если бы вы посылали Call или Callvirt прямо к цели.

Любой, кто хочет увидеть рабочий код, может перейти к codeplex - ответы, приведенные здесь, помогли реализовать возможности WeakDelegate. .


person Mark    schedule 26.10.2010    source источник
comment
Вы уже знаете почему, это личное.   -  person Hans Passant    schedule 27.10.2010
comment
@Hans - Reflection может это делать, деревья выражений могут это делать ...   -  person Mark    schedule 27.10.2010
comment
Но IL не может этого сделать. Я не знаю о деревьях выражений, но Reflection требует повышенного доверия - если вы находитесь в среде с частичным доверием, Reflection также не может вызывать частные методы. Невозможно сказать, что эта инструкция IL требует повышенного доверия - IL - это IL, а IL не может вызывать частные методы для других классов.   -  person Joe White    schedule 29.10.2010
comment
да, мне интересно посмотреть рабочий код, но я не могу, потому что есть только двоичные файлы   -  person Andriy Tylychko    schedule 11.11.2013


Ответы (3)


Вы вставляете свой IL в DynamicMethod или в метод в динамической сборке? Насколько я понимаю, нет способа пропустить проверки видимости из динамической сборки, но вы можете пропустить их при использовании DynamicMethod (см. здесь).

person kvb    schedule 27.10.2010
comment
Да, IL создается типом, а не динамическим методом. - person Mark; 28.10.2010

Решение (моей конкретной проблемы) заключалось в использовании делегатов вместо прямых вызовов методов. Вы можете с комфортом создать открытый делегат и передать его коду IL, а затем, когда код IL вызывает метод Invoke делегата, JIT принимает шаблон как допустимый и разрешает вызов частных методов.

Как я уже сказал, это решение (которое, к счастью, позволяет коду, сгенерированному во время выполнения, вызывать частные методы), хотя оно все еще не объясняет, как такие технологии, как деревья выражений и отражение, могут вызывать частные методы.

person Mark    schedule 28.10.2010
comment
Вы пробовали использовать вместо этого функцию "Звонок"? Код, сгенерированный компилятором Visual Studio C #, использует Call как код операции для частных методов, так что это действительно прекрасный способ вызвать частный метод. И да, Callvirt также можно использовать для вызова невиртуальных методов, но в данном конкретном случае он может вас обрушить, потому что среда выполнения CLR не может определить тип экземпляра объекта. - person Richard Flamsholt; 28.10.2010
comment
Я считаю, что деревья выражений используют DynamicMethod под капотом, что позволяет им пропускать проверки видимости. Отражение встроено в среду выполнения и специально предназначено для динамического вызова частных методов, но этот механизм не предоставляется другому коду. Если вы отправляете код в динамическую сборку, невозможно избежать проверок видимости среды выполнения. - person kvb; 28.10.2010
comment
@ Ричард - да, я пробовал Call и Callvirt. Оказывается, все дело в безопасности. - person Mark; 28.10.2010

Используйте Call, а не Callvirt.

[Изменить: не в качестве общей рекомендации, а специально для решения этой проблемы]

Callvirt предназначен для вызова виртуальных методов, где адрес назначения также зависит от точного типа экземпляра. Это не работает, когда вы используете слабую ссылку.

С другой стороны, цель частного метода может быть определена во время компиляции. Поэтому целесообразно вызывать его с помощью Call.

person Richard Flamsholt    schedule 27.10.2010
comment
1. Callvirt можно использовать как с виртуальными, так и с не виртуальными методами экземпляра. 2. Код операции Call нельзя использовать с проверкой достоверности для вызова незавершенных виртуальных методов, за исключением очень ограниченного набора случаев (в основном, чтобы разрешить base вызовы из реализации метода подкласса). - person kvb; 27.10.2010
comment
1. Верно, но неактуально. Обратите внимание на слабую ссылку здесь: callvirt полагается на знание типа экземпляра. 2. Думаю, вы что-то путаете: дело не в виртуальных методах, а как раз наоборот. Частный метод по своей сути является не виртуальным методом, и код операции Call обычно излучается как код операции вызова для закрытых членов, по крайней мере, в C #. - person Richard Flamsholt; 28.10.2010
comment
при вызове метода экземпляра Callvirt можно всегда безопасно использовать вместо Call, но обратное неверно (потому что метод может быть виртуальным). Вы правы в том, что в этом конкретном случае проблема OP не связана с виртуальностью (потому что метод явно частный и, следовательно, не виртуальный), но замена Callvirt на Call не будет работать, что является что я пытался объяснить. - person kvb; 28.10.2010
comment
@kvb А, я, конечно, говорил об этом конкретном случае, не предполагая, что Колл может заменить Callvirt. Вероятно, мне следовало быть менее кратким в своем ответе, но я решил, что все это понятно, поскольку проблема OP заключалась только в работе с частными методами. Тогда исключение MethodAccessException могло быть вызвано какой-то (неизвестной нам) проблемой безопасности с поиском типа экземпляра, который требуется Callvirt, а какой Call нет. Отсюда и мое предложение попробовать Call. - person Richard Flamsholt; 29.10.2010