вторник, апреля 13, 2010

Эксперимент с комментами

Интереса ради подключил сюда Disqus. Посмотрим, с чем его едят.

вторник, декабря 29, 2009

google prettify

Оказывается, существует subj - библиотека для подсветки синтаксиса кода (используется, например, Google Code). Поддерживает кучу языков.

Например, C#:

class A
{
public abstract void Foo();
}



или Python:


def select_odd(list):
return [n for n in list if n % 2 == 0]


Сама либа здесь. Я бомжара без хостингачитер, поэтому прицепил экземпляры, выложенные на gstatic.com экземпляры. Буду надеяться, что их в ближайшее время не поудаляют (да и меня заодно с ним %))

вторник, ноября 10, 2009

Доступ к классам С++ из C#

Иногда есть желание использовать из C# код на плюсах (т.е. построенный на классах). Традиционно варианта три:

  • использовать С++/CLI, чтобы завернуть native классы в классы CLR.
  • Сделать (вручную или с помощью SWIG) обертку вокруг классов, и использовать P/Invoke для доступа к ним.
  • оформить классы в виде COM-объектов.

Первый вариант напряжен тем, что требует наличия компилятора Visual C++ (реализации для mono, например, нет). Кроме того, C++/CLI – язык специфичный, ему нужно учиться (я как-то огреб утечку памяти, не разобравшись в разнице между !Class() и ~Class()).

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

Недостаток третьего подхода в том, что COM, вообще говоря, штука тяжелая, со своими требованиями к оформлению кода, вдобавок, обычно требующая регистрации в реестре (хотя это можно обойти). Достоинство – прямое отображение COM-интерфейсов на интерфейсы .NET.

Вот тут я прочитал про интересный подход, “скрещивающий” второй и третий варианты взаимодействия. Принцип следующий - С++ объект допиливается так, чтобы реализовывать IUnknown (т.е. иметь в начале vtable методы QueryInterface, AddRef, Release), но создается посредством фабричной функции, экспортированной из DLL.

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

Также обратите внимание, что сигнатура метода ComputePi() имеет привычный вид, а не безобразие типа

HRESULT __stdcall ComputePi(float* pResult);

Клиентский код выглядит ещё проще. Обратите внимание, что для поддержки не-COM-овской сигнатуры на методе выставлен атрибут [PreserveSig]. Значение атрибута [Guid] должно совпадать с GUID-ом, обрабатываемым в QueryInterface.

При загрузке .NET создаст прокси, называемый RCW (runtime callable wrapper), вызовет QueryInterface для заданного GUID’а, после чего станет возможным работать RCW как с обычным объектом.

Достоинства:

  • минимум кода пишется с обоих сторон
  • никакого реестра, регистрации и пр.
  • кроссплафтоменно (работает и в MS.NET и в mono). Максимум – придется описать самому GUID для IUnknown.

Недостатки:

  • Обычный С++ класс (не содержащий в начале vtable методы IUnknown) так использовать нельзя – система тупо рассчитывает на то, что vtable имеет заявленную структуру. На крайний случай, вы можете делегировать вызовы своему объекту.
  • В стандартном COM-е коды HRESULT используются для сообщений об ошибках. При interop-е по ним строятся исключения .NET. Здесь такая схема не работает, нужно использовать свой механизм сигнализации об ошибках. Исключения С++, по крайней мере, в случае MinGW, приводят к завершению работы программы (думаю, в случае VC++ они могут сработать, но не тестил).
  • Сложное межобъектное взаимодействие (передачу объектов в виде параметров) делать таким образом можно задолбаться. Но это общее место любого interop-а.

четверг, августа 20, 2009

Про google Guice

Оказывается, guice умеет разрешать циклы в зависимостях.

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

Забавно, Castle.Windsor, например, этого не делает принципиально. А Ninject вообще зависает на циклических зависимостях.

четверг, августа 13, 2009

Реализация закладки для элемента управления PropertyGrid

Одной из важных фич Syringe является возможность перехватывать события, порождаемые тестируемым элементом управления.

Я решил реализовать настройку перехватчиков средствами того же PropertyGrid’а, что и используется для редактирования свойств. Тогда интерфейс оказывается максимально похож на VisualStudio и, следовательно, не вызывает удивления.

Логично было вынести список событий на отдельную закладку.

Я попробовал использовать стандартную закладку EventsTab, но не осилил. Для своей инициализации она требует слишком много зависимостей, запрашиваемых неявно через родительский контейнер. Даже если бы я уже приделал DI-контейнер (зря я это откладываю, ох, зря) и отследил все обращения, все равно я оказался бы перед необходимостью реализовывать целую кучу интерфейсов типа IDesignerHost, IEventsBindingService и пр., функциональность которых мне не очень-то нужна.

Поэтому я решил реализовать свою закладку с нуля. Ну, не совсем =) На самом деле, у родительского класса абстрактными являются только свойство TabName и метод GetProperties.

Я реализовал GetProperties так, чтобы он возвращал по PropertyDescriptor’у для каждого события в тестируемом объекте (а список событий получил через TypeDescriptor), причем для этих дескрипторов указал в качестве редактора использовать список доступных логгеров (я их реализовал два – пишущий в лог и показывающий MessageBox). Адаптацию логгеров к разным типам событий была сделана еще раньше.

Я добавил свою закладку в PropertyGrid, и, о чудо… она не появилась. Выяснилось (при просмотре в Reflector’е), что, кроме реализации абстрактных методов, есть еще одно неочевидное условие: закладка должна что-то возвращать по запросу свойства Bitmap, которое используется для выбора иконки закладки! OMFG! Перекрыв это свойство я таки достиг успеха.

Так что теперь Syringe может перехватывать события (правда, пока не умеет нормально форматировать их аргументы), а пользователь может удобно выбрать тип перехватчика. Это первая (и очень полезная, ИМХО) фича, которой принципиально нет в UserControlTestContainer.

Моя доволен =)

вторник, августа 11, 2009

Упрощение реализации INotifyPropertyChanged

Объект, корректно реализующий интерфейс INotifyPropertyChanged оказывается “наблюдаемым” (observable), т.е. изменения его состояния могут легко отслеживаться подписчиками и, например, отображаться на визуальных компонентах в актуальной форме.

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

Опять же, довольно много кода приходится писать по нескольку раз.

Общественность =) неоднократно предлагала различные решения этой проблемы – от построения прокси-INotifyPropertyChanged вокруг POCO-объекта и патча бинарников с помощью PostSharp-а до различных структур, упрощающих привязку.

Я, естественно, пошел своим путем, т.к. во всех вариантах меня что-то не устраивало =)

В итоге остановился на том, что создал интерфейс и пару реализаций (для отдельного свойства и для списка), которые можно увидеть здесь, здесь и здесь. Пример использования можно увидеть здесь. Как видно, boilerplate-а поубавилось.

Недостатки: текущим реализациям надо передать имя свойства и их события надо перенаправить классу-модели.

Первое можно забороть с помощью Reflection-а (смотреть, какое свойство вызвало метод Set()), второе – сделать класс ModelBase, а в нем метод, настраивающий подписку для всех свойств разом.

Но я пока не решил, надо ли мне это =)

Fluent data binding

В .NET есть возможность привязывать свойства графических элементов к свойствам некоторого объекта-модели.

В результате привязки изменения в одном месте отражаются в другом. Например, редактирование текста в TextBox-е может автоматически обновлять поле Text в модели. Или изменение в модели флага , привязанного к свойству Enabled может блокировать элемент управления.

Сампл можно посмотреть здесь (показана только т.н. “простая привязка”, “сложная” делается ), хотя, уверен, ни для кого ничего фундаментально нового в этом нет.

Чем это хорошо? Хорошо это тем, что существенно уменьшается объем кода, связанного с отображением данных.

Чем это плохо? Плохо это тем, что свойства как модели, так и представления кодируются строками. Со всеми вытекающими отсюда следствиями, от которых и велемудрый R# не всегда защитит. Пытается защищать от этого графический редактор в VS,  но, опять же, только частично (к тому же, я лично предпочитаю писать код, а не тыкать мышкой).

Работая над Syringe, я решил защититься от возможных проблем со строками и набросал небольшой набор классов и методов-расширений, обеспечивающих устойчивую к опечаткам и типобезопасную привязку.

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

Пример можно увидеть здесь. Как видно, код стал компактней, при его написании сложнее ошибиться.

Реализацию можно посмотреть в исходниках проекта на google.code. Там же можно посмотреть и пример привязки к спискам данных.

четверг, июля 30, 2009

Syringe UserControl testing container

Это такая утилита для тестирования и отладки элементов управления (UserControl’ов) WindowsForms (возможно, потом и WPF).

Похожа на поставляемый с VS UserControlTestContainer. То есть пользователь может выбрать тип контрола, контрол будет создан на форме и пользователь имеет возможность всячески глумиться над ним, в том числе напрямую редактировать его публичные свойства.

Основное отличие – возможность “подсовывать” контролу его зависимости с помощью механизма Dependency Injection (поэтому проект и называется “шприц” =)). Предусмотрено два способа:

- регистрация зависимости в контейнере для контролов, получающих их через стандартный механизм Component.GetService();

- инъекция зависимостей при конструировании контрола (в конструктор и в свойства).

В результате появится возможность тестировать гораздо более самодостаточные компоненты.

Текущая версия 0.1 (наваянная вчера часа за два =)) почти точно соответствует UserControlTestContainer (если не считать багов =))

Проект живет здесь. Буду рад комментариям.