вторник, апреля 13, 2010
вторник, декабря 29, 2009
google prettify
Например, C#:
class A
{
public abstract void Foo();
}
или Python:
def select_odd(list):
return [n for n in list if n % 2 == 0]
Сама либа здесь. Я
вторник, ноября 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 (если не считать багов =))
Проект живет здесь. Буду рад комментариям.