Интересности      Книги      Утилиты    

28 декабря 2009 г.

Условная компиляция

Один мой друг как-то захотел сделать демо написанного собственным потом и кровью SDK на .NET. Естественно нужно было урезать часть очень важного функционала. Сделать это нужно было так как всем известно как в .NET происходит защита исходного кода. Плохо если не сказать другим словом – обфускация.

Обфускация по сути переименование и переименование это касается только приватной или внутренней реализации программы. Но SDK в силу того что нужно выставлять часть функционала наружу поддается защите обфускатором заметно хуже.

Поэтому необходимо было сделать демо. Можно было конечно покромсать код вручную – но это не выход. Во-первых, потому что это не разовая работа, а такое проделывать необходимо столько раз сколько билдов отдаются на растерзание публики и, во-вторых, это прямой путь к мешанине, неразберихе, путанице( так как какой-то код добавляется, какой-то удаляется) и как следствие к багам. Тем более, что на такую пустую работу при наличие большого количества “непубличного” функционала будет уходить достаточно много ценного времени.

Как же в такой ситуации быть? Есть необходимость собирать билд с включением или выключением определенного функционала условно.

В это случае поможет условная компиляция. Такую возможность C# как язык унаследовал от С/С++ (всем достаточно известно использование #define, #ifdef и т.п. в С/С++ ). Их хотя в шарпе уже не напишешь тех изощренных макросов как в си, но все же аналогичные директивы в C# имеют свое применение.

Пользоваться этим достаточно просто. Необходимо используя директиву #define объявить константу условной компиляции.

#define TEST

Также константу условной компиляции можно объявить используя опцию компилятора /define. А так как это можно сделать в опциях, тогда то же самое можно сделать и в свойствах проекта используя Visual Studio (вкладка Builde > Conditional compilation symbols):


image


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


При создании нового проекта для него создаются две конфигурации – Debug и Release, для первой объявлено по умолчанию две константы DEBUG и TRACE, для релизной конфигурации – только TRACE.


После объявления такую константу можно использовать либо с #if, #endif, #else директивами, либо в связке с атрибутом ConditionalAttribute. Например, код можно обернуть директивами:


#if (DEBUG)
            Debug.Listeners.Remove("Default");
            Debug.Listeners.Add(new SuperAssertTraceListener());
#endif

или с #else:


#if (DEBUG)
            stream = new StreamWriter("c:\\temp\\WorkTimeTracer.txt");
#else
            stream = null;
#endif



На директивы никаких ограничений в отличии атрибута – нет, если что-то не так код не скомпилируется. Но с атрибутом код более читабелен. На метод помеченные ConditionalAttribute накладываются следующие ограничения:



  1. Такой метод не должен возвращать значений
  2. Не должен быть объявлен в интерфейсе
  3. Не должен быть методом с идентификатором override.

Хотя ConditionalAttribute имеет один недостаток по сравнению с использованием директив. При компиляции в Release варианте, метод помеченный таким атрибутом сохраняется в MSIL коде.


Суть этих ограничений вполне логична. Ведь в конфигурациях отличных от указанной в атрибуте, тело метода будет восприниматься как комментарий. Если посмотреть на класс System.Diagnostics.Debug к примеру то там многие методы помечены этим атрибутом.



        [Conditional("DEBUG")]
        public static void Trace(string message)
        {
            Console.WriteLine(message);
        }


На самом деле применять условную компиляцию можно не только для того чтобы исключать функционал нужный для дебага, или для того чтобы сделать демку. Условную компиляцию стоит применять в “кроссплатформенной” разработке, например когда пишется клиентская часть на Silverlight и WPF, или для обычного и .NET Compact Framework


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


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


Progg it

6 комментариев:

  1. У ConditionalAttribute есть еще одна интересная особенность - если пометить им другой атрибут, то он как и все места его использования попадут в сборку только при наличии соответствующего дэфайна. Примером является атрибут SuppressMessage, он компилируется в сборку только при наличии дэфайна CODE_ANALYSIS.

    ОтветитьУдалить
  2. Поделитесь шрифтовой темой?

    ОтветитьУдалить
  3. 2Logonoff
    "Поделитесь шрифтовой темой?"
    Поделюсь:) Только скажите что вы имели в виду? Тему для студии?

    ОтветитьУдалить
  4. Да, тему для студии, больно она хороша))

    ОтветитьУдалить
  5. 2Logonoff
    Я выложил настройки студии, вот ссылка:
    http://depositfiles.com/files/loawvg5bw

    Если там чего то не бует хватать(шрифта например) дайте мне знать, но используется шрифт Consolas, и он по умолчанию есть в 7-ке и висте. Но если что дайте мне знать.

    ОтветитьУдалить
  6. Спасибо! Все установилось.

    ОтветитьУдалить