Есть программа, которая использует набор библиотек, написанных на C#.
Очень хочется внести изменения в эти библиотеки, так, чтобы некоторые классы и их методы вели себя иначе.
Библиотеки не обфусцированы, однако скомпилированы под .NET2.0 и содержат много созданного компилятором кода. Их пересоздание — весьма нетривиальный процесс из-за ограничений студии, ругающейся на левые имена и недостижимые метки.
Используя .NET Reflector и плагин Reflexil, я воткнул код, динамически загружающий мою библиотеку.
Вопрос — как теперь внести изменения в типы и методы оригинала?
Если бы методы класса были по-умолчанию виртуальными, я бы просто отнаследовался от интересующего меня класса и переопределил его, после чего заменил бы значение в нужных полях с помощью отражения. К сожалению, это не так.
Была идея вынести все публичные элементы за интерфейс, но я не знаю как в run-time изменить уже загруженный тип, навесить на него интерфейс, и изменить типы существующих полей на этот интерфейс.
Опять же, непонятно — как изменить статичные классы и их методы.
Подскажите, как такое можно провернуть.
Если ничего не получится, придётся всё-таки расковыривать библиотеки. На этот слуйчай, может, кто-нибудь подскажет что-нибудь кроме .NET Reflector и dotPeek для декомпиляции? Или надстройки к ним, позволяющие избавиться от $, >, < в именах?..
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Albeoris, Вы писали:
A>Здравствуйте, ldarcy, Вы писали:
L>>http://msdn.microsoft.com/en-us/library/hh549176.aspx A>Выглядит шикарно. Вот только, данное решение создаёт обёртки над оригинальными классами и...
в примере The Y2K bug нет никаких оберток. Код вызывает Datetime.Now, как и раньше.
Re: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Albeoris, Вы писали:
A>Если ничего не получится, придётся всё-таки расковыривать библиотеки. На этот слуйчай, может, кто-нибудь подскажет что-нибудь кроме .NET Reflector и dotPeek для декомпиляции? Или надстройки к ним, позволяющие избавиться от $, >, < в именах?..
Вы, похоже, выбрали не ту версию языка в настройках рефлектора. Он прекрасно восстанавливает "созданный компилятором код". Поставьте наиболее последнюю версию C#.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Albeoris, Вы писали:
A>Если бы методы класса были по-умолчанию виртуальными, я бы просто отнаследовался от интересующего меня класса и переопределил его, после чего заменил бы значение в нужных полях с помощью отражения. К сожалению, это не так.
Тогда можно использовать шаблон Декоратор + при необходимости создать прокси-класс, который будет исходного типа (чтобы новый класс можно было использовать вместо исходного там где есть контроль типа).
Re[2]: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Doc, Вы писали: Doc>Тогда можно использовать шаблон Декоратор + при необходимости создать прокси-класс, который будет исходного типа (чтобы новый класс можно было использовать вместо исходного там где есть контроль типа).
Этож не плюсы, где можно что угодно приводить к чему угодно. Это безопасный C#, в котором я могу отнаследоваться от какого-нибудь типа и даже подсунуть свой экземпляр вместо оригинального, но все не виртуальные методы будут вызываться у базового класса, а не моя имплементация.
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[2]: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Sinclair, Вы писали: S>Вы, похоже, выбрали не ту версию языка в настройках рефлектора. Он прекрасно восстанавливает "созданный компилятором код". Поставьте наиболее последнюю версию C#.
Версия верная — .NET 2.0, в котором, используя MonoDevelop, используется System.Core 3.5. Ошибок при декомпиляции тьма, но их можно исправить.
Этими сборками оперирует Unity3D, и даже после исправлениях всеш ошибок, приложение падает при попытке сериализовать данные редактора и инжектировать их в новую либу. =\
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[2]: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, hardcase, Вы писали: H>Стандартным ildasm декомпилировать в IL, засандалить необходимый код по всем необходимым местам и собрать сборку обратно с помощью ilasm.
На данный момент, это единственный из гарантированно рабочих вариантов. Но писать код на IL едва ли кто-нибудь будет. Но ты навёл меня на интересную мысль — ведь ничто не мешает мне декомпилировать всю сборку в IL, после чего убрать все аттрибуты sealed у классов и добавить virtual всем методам. Верно? Это можно сделать как-нибудь автоматически?
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[3]: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Albeoris, Вы писали:
A>Этож не плюсы, где можно что угодно приводить к чему угодно. Это безопасный C#, в котором я могу отнаследоваться от какого-нибудь типа и даже подсунуть свой экземпляр вместо оригинального, но все не виртуальные методы будут вызываться у базового класса, а не моя имплементация.
Окей, чтобы было лучше понятно, объясню на пальцах. Есть некоторые метод, который принимает на вход DateTime. У него он дергет свойство Year. Моя задача — сделать так, чтобы либо метод принимал на вход DateTimeEx, либо я мог передать ему DateTimeEx несмотря на то, что он хочет DateTime. Как я понимаю, ни то ни другое невозможно без изменения IL-кода. Моя задача — изменить его и реализовать задуманное. DateTime не реализует метод IDateTime. Он не позволяет отнаследоваться от него и переопределить свойство Year. И dynamic ему тоже не скормишь.
Поэтому ни фейковые сборки, ни декораторы мне не подходят. Что очень печалит. Я бы очень хотел верить в магию...
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[5]: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Albeoris, Вы писали:
A>Окей, чтобы было лучше понятно, объясню на пальцах. Есть некоторые метод, который принимает на вход DateTime. У него он дергет свойство Year. Моя задача — сделать так, чтобы либо метод принимал на вход DateTimeEx, либо я мог передать ему DateTimeEx несмотря на то, что он хочет DateTime.
В общем случае (если требуется решение для продакшна, с лицензионной чистотой и беспроблемным обновлением библиотек) задача не решается.
Частные решения:
1. Покупаем исходники, правим.
2. Пишем патчер, который будет править чужой il-код, см в сторону Mono.Cecil и обёрток вокруг него. Если сборка подписана строгим именем, придётся править все зависящие от неё сборки.
3. Перестаём самому стругать себе грабли, отходим на шаг назад и смотрим, можно ли решить исходную проблему без правки чужих библиотек.
Re[6]: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Sinix, Вы писали:
S>Частные решения: S>1. Покупаем исходники, правим.
Денег не хватит. Переговоры ведутся, но вероятность благоприятного исхода крайне мала.
S>2. Пишем патчер, который будет править чужой il-код, см в сторону Mono.Cecil и обёрток вокруг него. Если сборка подписана строгим именем, придётся править все зависящие от неё сборки.
В своё время пытался освоить Mono.Cecil, но всё упрёлось в то, что абсолютно верный (на мой взгляд) припер отказывался работать и не вносил никаких изменений, осталяя библиотеку неизменной. Если не сложно — можно пример по превращению не виртуальных методов в виртуальные (на примере любой либы, любого типа, любого метода — полноценную реализацию я сделаю сам)?
S>3. Перестаём самому стругать себе грабли, отходим на шаг назад и смотрим, можно ли решить исходную проблему без правки чужих библиотек.
Нельзя, так как это попытка прикрутить систему плагинов к приложению на них не расчитанному.
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[7]: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Albeoris, Вы писали:
A>В своё время пытался освоить Mono.Cecil, но всё упрёлось в то, что абсолютно верный (на мой взгляд) припер отказывался работать и не вносил никаких изменений, осталяя библиотеку неизменной. Если не сложно — можно пример по превращению не виртуальных методов в виртуальные (на примере любой либы, любого типа, любого метода — полноценную реализацию я сделаю сам)?
Что-то такое делал, но очень давно. Вечером попробую, по результатам отпишусь.
S>>3. Перестаём самому стругать себе грабли, отходим на шаг назад и смотрим, можно ли решить исходную проблему без правки чужих библиотек. A>Нельзя, так как это попытка прикрутить систему плагинов к приложению на них не расчитанному.
О как. Удачи, что ещё сказать
Re[8]: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Sinix, Вы писали:
A>>В своё время пытался освоить Mono.Cecil, но всё упрёлось в то, что абсолютно верный (на мой взгляд) припер отказывался работать и не вносил никаких изменений, осталяя библиотеку неизменной. Если не сложно — можно пример по превращению не виртуальных методов в виртуальные (на примере любой либы, любого типа, любого метода — полноценную реализацию я сделаю сам)? S>Что-то такое делал, но очень давно. Вечером попробую, по результатам отпишусь.
Хм. Так, с этим отбой. Всё оказалось очень просто. Не знаю — почему раньше не получалось:
private static void Main(string[] args)
{
if (args.Length == 0)
return;
string assemblyPath = args[0];
AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
ChangeAssembly(assembly);
assembly.Write(assemblyPath);
}
private static void ChangeAssembly(AssemblyDefinition assembly)
{
foreach (TypeDefinition type in assembly.MainModule.Types)
ChangeType(type);
}
private static void ChangeType(TypeDefinition type)
{
if (type.IsSealed)
type.IsSealed = false;
if (!type.IsPublic)
type.IsPublic = true;
foreach (FieldDefinition field in type.Fields)
ChangeField(field);
foreach (MethodDefinition method in type.Methods)
ChangeMethod(method);
foreach (TypeDefinition child in type.NestedTypes)
ChangeType(child);
}
private static void ChangeField(FieldDefinition field)
{
if (field.IsInitOnly)
field.IsInitOnly = false;
if (!field.IsPublic)
field.IsPublic = true;
}
private static void ChangeMethod(MethodDefinition method)
{
if (!method.IsPublic)
method.IsPublic = true;
if (!method.IsStatic && !method.IsVirtual && !method.IsAbstract)
{
method.IsVirtual = true;
method.IsNewSlot = true;
}
}
Но тут возникла другая проблема — прога благополучно падает с матами о том, что десериализуемый тип не имеет конструктора по-умолчанию. Смотрю рефлектором — действительно не имеет. Причём и до моих изменений. То ли там местный AOP развлекается, то ли конструкторы по умолчанию погибли смертью храбрых, то ли ошибки где-то ещё и ему не очень нравятся мои структуры с виртуальными методами. :D
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[9]: Run-time injection - как изменить поведение чужого класса?
Здравствуйте, Albeoris, Вы писали:
A>Но тут возникла другая проблема — прога благополучно падает с матами о том, что десериализуемый тип не имеет конструктора по-умолчанию.
Куча причин может быть, навскидку не угадаешь.
A>и ему не очень нравятся мои структуры с виртуальными методами. :D
???
Структуры не поддерживают объявление виртуальных методов напрямую, только реализацию методов из объявленных интерфейсов и переопределение методов object.
Да и смысл? Один фиг наследника от структуры не создашь.
В общем, чтобы не было дальнейших сюрпризов, очень советую прогнать PEVerify на полученном бинарнике.
Re[9]: Run-time injection - как изменить поведение чужого класса?
A>Но тут возникла другая проблема — прога благополучно падает с матами о том, что десериализуемый тип не имеет конструктора по-умолчанию. Смотрю рефлектором — действительно не имеет. Причём и до моих изменений. То ли там местный AOP развлекается, то ли конструкторы по умолчанию погибли смертью храбрых, то ли ошибки где-то ещё и ему не очень нравятся мои структуры с виртуальными методами. :D
Забавно. Методы замечательно паблишатся и виртуалятся. Проблемы только с полями. Потому что как только я делаю приватные поля класса публичными, он тут же требует конструктор без параметров. Мистика какая-то... Ну да ладно, сейчас каким-нибудь кастылём подопру...
Видимо, в этом направлении и буду копать. Теперь, имея под рукой уже изрядно исковерканную библиотеку, где всё, что угодно можно переопределять, наследовать и задавать, останутся всякие мелочи.
Наверное, было бы весьма забавно воткнуть туда Dependency Injection или выделить из всех типов интерфейсы и реализовать их динамическую загрузку из библиотек-плагинов. ^-^
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[9]: Run-time injection - как изменить поведение чужого класса?
P.S. По колу:
1. type.IsSealed = false; не надо выполнять для структур, делегатов и static-типов. type.IsPublic = true — пропускать для сгенеренных типов (замыкания, .Module, энумераторы и авайтеры).
2. field.IsPublic = true; — нужно автосгенеренные поля (для свойств/событий/лямбд/dynamic etc)
3. if (!method.IsStatic .. ) — + проверку на структуры и автосгенеренные типы.