В данном цикле статей я расскажу про MSIL, кодогенерацию в .NET, изменение существующих сборок, Mono.Cecil, аспектно-ориентированное программирование и многое другое (если конечно вам это будет нужно:) ). Все это будет сопровождаться работающими примерами, так как я считаю, что код - это лучший способ донести идею. Это моя первая статья вообще, и на GotDotNet в частности, так что любые комментарии приветствуются. Цикл будет продолжаться, если это кого-либо заинтересует. Итак, начнем.
Многие .NET разработчики знают, что для доступа к объектам чужой сборки можно использовать Reflection. С помощью типов из System.Reflection мы можем получить доступ ко многим объектам .NET сборки, просмотреть их метаданные, и даже использовать те объекты, доступ к которым ограничен (например, private методы чужого класса). Но Reflection в .NET очень ограничен, и главная причина этому - данные, с котороми вы работаете через Reflection все еще считаются кодом. Таким образом, вы, к примеру, можете получить CodeAccessSecurity exception, если сборка, к которой вы пытаетесь применить Refelection, запрещает это. По этой же причине Reflection работает довольно медленно. Но наиболее важным для дальнейшего является то, что стандартный Reflection не позволяет изменять существующие сборки, только генерировать и сохранять новые. Качественно иной подход предлагает бесплатная библиотека Mono.Cecil, поставляющаяся вместе с исходным кодом. Главное отличие Mono.Cecil от Reflection в том, что она рассматривает сборку как набор инструкций MSIL (как поток байт) , а не как некий код. Таким образом, с помощью Mono.Cecil можно как угодно изменять MSIL код имеющейся сборки. Сразу рассмотрим на примере использование возможностей Mono.Cecil. Предположим, у нас есть консольное приложение без исходного кода, в котором есть тип Program. У нас нет доступа к исходному коду, но мы хотим, чтобы данное приложение при вызове каждого метода выводило,, на консоль некоторое сообщение. Для этого напишем собственное консольное приложение. В качестве аргумента при запуске будем передавать путь к целевому приложению: using Mono.Cecil;
using Mono.Cecil.Cil;class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
return;
string assemblyPath = args[0];
// считываем сборку в формате Mono.Cecil
var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
// получаем метод Console.WriteLine, используя стандартные методы Reflection
var writeLineMethod = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
// создаем ссылку на метод, полученный Reflection, для использования в Mono.Cecil
var writeLineRef = assembly.MainModule.Import(writeLineMethod);
foreach (var typeDef in assembly.MainModule.Types)
{
foreach (var method in typeDef.Methods)
{
// Для каждого метода в полученной сборке
// Загружаем на стек строку "Inject!"
method.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Ldstr, "Inject!"));
// Вызываем метод Console.WriteLine, параметры он берет со стека - в данном случае строку "Injected".
method.Body.Instructions.Insert(1, Instruction.Create(OpCodes.Call, writeLineRef));
} Read more: gotDotNet
Многие .NET разработчики знают, что для доступа к объектам чужой сборки можно использовать Reflection. С помощью типов из System.Reflection мы можем получить доступ ко многим объектам .NET сборки, просмотреть их метаданные, и даже использовать те объекты, доступ к которым ограничен (например, private методы чужого класса). Но Reflection в .NET очень ограничен, и главная причина этому - данные, с котороми вы работаете через Reflection все еще считаются кодом. Таким образом, вы, к примеру, можете получить CodeAccessSecurity exception, если сборка, к которой вы пытаетесь применить Refelection, запрещает это. По этой же причине Reflection работает довольно медленно. Но наиболее важным для дальнейшего является то, что стандартный Reflection не позволяет изменять существующие сборки, только генерировать и сохранять новые. Качественно иной подход предлагает бесплатная библиотека Mono.Cecil, поставляющаяся вместе с исходным кодом. Главное отличие Mono.Cecil от Reflection в том, что она рассматривает сборку как набор инструкций MSIL (как поток байт) , а не как некий код. Таким образом, с помощью Mono.Cecil можно как угодно изменять MSIL код имеющейся сборки. Сразу рассмотрим на примере использование возможностей Mono.Cecil. Предположим, у нас есть консольное приложение без исходного кода, в котором есть тип Program. У нас нет доступа к исходному коду, но мы хотим, чтобы данное приложение при вызове каждого метода выводило,, на консоль некоторое сообщение. Для этого напишем собственное консольное приложение. В качестве аргумента при запуске будем передавать путь к целевому приложению: using Mono.Cecil;
using Mono.Cecil.Cil;class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
return;
string assemblyPath = args[0];
// считываем сборку в формате Mono.Cecil
var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
// получаем метод Console.WriteLine, используя стандартные методы Reflection
var writeLineMethod = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
// создаем ссылку на метод, полученный Reflection, для использования в Mono.Cecil
var writeLineRef = assembly.MainModule.Import(writeLineMethod);
foreach (var typeDef in assembly.MainModule.Types)
{
foreach (var method in typeDef.Methods)
{
// Для каждого метода в полученной сборке
// Загружаем на стек строку "Inject!"
method.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Ldstr, "Inject!"));
// Вызываем метод Console.WriteLine, параметры он берет со стека - в данном случае строку "Injected".
method.Body.Instructions.Insert(1, Instruction.Create(OpCodes.Call, writeLineRef));
} Read more: gotDotNet
0 comments:
Post a Comment