воскресенье, 24 марта 2013 г.

Move of multiple items

В этой статье я расскажу о том как реализовать перемещение нескольких элементов контейнера за раз. Такая необходимость, например, возникает, когда пользователь выделяет несколько, в общем случае несмежных, элементов в QTableView и перетаскивает их в пределах таблицы. С единичным выделением, все довольно просто: удалил элемент в старой позиции и вставил в новую; кто-то позаботится о случае с перемещением от меньших индексов к большим, когда после удаления элемента в исходной позиции, нужно будет скорректировать индекс назначения, а кто-то сочтет, что сойдет и так.

Например eсть коллекция: 0 1 2 3 4 5
Нужно переместить элемент из позиции 1 в позицию 3 (индексы считаем с нуля)
Удаляем элемент из позиции 1: 0 2 3 4 5
Вставляем его в позицию 3: 0 2 3 1 4 5
Я бы все-таки ожидал, что элемент из позиции 1 будет вставлен перед элементом в изначальной позиции 3. В таком случае результат выглядел бы как: 0 2 1 3 4 5

Возможно это выглядит всего лишь как вопрос личных предпочтений (я как раз недавно спорил с одним человеком на эту тему), но на самом деле это не так.
Например на первом скриншоте cxerimolio будет вставлено перед ribo и это правильно В то время как на втором скриншоте, к удивлению пользователя cxerizo будет вставлено не перед piro, как того следовало ожидать судя по обозначеной позиции для вставки, а после него. Поэтому в случае forward move нужно корректировать индекс назначения.

Прежде чем продолжить оговорюсь, что я не буду фокусироваться на реализации Qt модели - той, что наследуется от QAbstractItemModel. Меня интересует только как правильно перемещать элементы в контейнере на уровне бизнес логики, в то время как QAbstractItemModel это всего лишь навсего адаптер между model и view/delegate. Тем не менее бизнес модель должна как-то уведомлять представление о том, что в ней что-то изменилось, поэтому снабдим ее соответствующим сигналом для случая move. Назовем нашу business model "SomeContainer" - это обьект, содержащий в себе коллекцию неких обьектов (в данном примере используются int'ы) и предоставляющий простейший интерфейс для манипуляций над ними.
Вот так будет выглядеть SomeContainer в нашем простейшем случае:

class SomeContainer : public QObject
{
Q_OBJECT

public: //types

typedef std::vector< int > ValueList; //collection of values to be moved
typedef size_t IndexType; //type of indices in ValueList
typedef std::vector< IndexType > IndexList; //collection of indices in ValueList

public: //methods

explicit SomeContainer( QObject *parent = 0);

//! move an item at srcIndex to the position before the item at destIndex
void moveItem( IndexType srcIndex, IndexType destIndex );
//! move multiple items to the position before the item at destIndex
void moveItems( const IndexList& srcIndices, IndexType destIndex );

//auxiliary methods needed for unit testing purpose only
void push_back( int value ) { m_items.push_back( value ); }
int at( IndexType index ) const { return m_items.at( index ); }

signals:

//!notifies that element at index oldIndex has been moved to the position defined by newIndex
void itemMoved( IndexType oldIndex, IndexType newIndex );

private: //fields

ValueList m_items;

};

Теперь можно перейти к написанию функции move для случая с перемещением одного элемента. Как я уже говорил все довольно-таки просто - удаляем элемент, корректируем индекс назначения, вставляем елемент. Что мне не нравится в этом, так это то, что после удаления элемента все элементы массива следующие за удаленным элементом будут перемещаться влево, а перед его вставкой они снова будут перемещаться, но теперь уже вправо. На самом деле достаточно будет только перемещать элементы между исходным индексом и индексом назначения, причем делать это можно единожды. Нужно только правильно выбрать направление сдвига в зависимости от направления перемещения интересующего нас элемента. Это несколько улучшит производительность, поэтому мы сразу же реализуем данную оптимизацию.
Вот так будет выглядеть наша функция moveItem

void SomeContainer::moveItem( IndexType srcIndex, IndexType destIndex )
{
Q_ASSERT( srcIndex > 0 && srcIndex < m_items.size() ); //validity check for the source index
Q_ASSERT( destIndex > 0 && destIndex <= m_items.size() ); //validity check for the destination index
//NOTE: m_item.size() is a valid position for the destination index - the item will be moved to the tail

if ( destIndex > srcIndex ) //in case if destIndex < srcIndex --destIndex is not needed as all the items will be shifted to the right
--destIndex; // ... and the item from srcIndex will automatically be inserted before the item at destIndex

ValueList::iterator srcIt = m_items.begin();
std::advance( srcIt, srcIndex );

ValueList::iterator destIt = m_items.begin();
std::advance( destIt, destIndex );

if ( srcIndex > destIndex )
backwardMove( srcIt, destIt );
else
forwardMove( srcIt, destIt );

emit itemMoved( srcIndex, destIndex );
}

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

//! moves forward the item pointed by srcIt to the position pointed by destIt
void forwardMove( ValueList::iterator srcIt, ValueList::iterator destIt )
{
ValueList::value_type movedItem = *srcIt;

//shifting all the items in the range [ sourcIndex + 1, destinationIndex ] to the left
ValueList::iterator afterSrcIt = srcIt;
++afterSrcIt;

ValueList::iterator afterDestIt = destIt;
++afterDestIt;

std::copy( afterSrcIt, afterDestIt, srcIt );

//write the moved item into the destination position
*destIt = movedItem;
}

//! moves backward the item pointed by srcIt to the position pointed by destIt
void backwardMove( ValueList::iterator srcIt, ValueList::iterator destIt )
{
ValueList::value_type movedItem = *srcIt;

//shifting all the items in the range [ destinationIndex, sourcIndex - 1 ] to the right
ValueList::iterator afterSrcIt = srcIt;
++afterSrcIt;

//dest < src
std::copy_backward( destIt, srcIt, afterSrcIt );

//write the moved item into the destination position
*destIt = movedItem;
}

Теперь перейдем к перемещению множества элементов за один раз. Просто вызывать moveItem в цикле нельзя. Во-первых, нужно корректировать позицию вставки после перемещения некоторых элементов. Во-вторых нужно также корректировать исходные индексы.
Например, имеется коллекция: 0 1 2 3 4 5 6 7
Нужно переставить элементы из позиций 0 и 1 в позицию перед элементом 6.
Передвигаем 0 в позицию перед 6, получаем: 1 2 3 4 5 0 6 7
Теперь под индексом 1 находится не 1, а 2 и следовательно нам нужно скорректировать исходный индекс 1 соответствующим образом, чтобы следующая перестановка произошла правильно.

Аналогичные сложности возникают и при перемещении назад, не говоря уже о таких случаях как, например перемещение элементов по индексами 0, 5, 3 с сохранением порядка в позицию перед 4. Ситуация еще больше осложняется тем, что нужно еще корректно уведомлять подписчиков на сигнал itemMoved. В случае неправильных уведомлений программа может даже завершиться аварийно из-за того что PersistentModelIndex'ы в QModelView оказались в неправильном состоянии.

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

//We are using templates only for convenience in case if values type of SomeContainer::ValueList is changed in future

template< class T >
struct DecIfInRange
{
DecIfInRange( T minValue, T maxValue )
: m_minValue( minValue ), m_maxValue( maxValue )
{
}

T operator() ( const T& value ) const
{
return value > m_minValue && value <= m_maxValue ? value - 1 : value;
}

T m_minValue;
T m_maxValue;
};

template< class T >
struct IncIfInRange
{
IncIfInRange( T minValue, T maxValue )
: m_minValue( minValue ), m_maxValue( maxValue )
{
}

T operator() ( const T& value ) const
{
return value >= m_minValue && value < m_maxValue ? value + 1 : value;
}

T m_minValue;
T m_maxValue;
};

...
void SomeContainer::moveItems( const IndexList& srcIndices, IndexType destIndex )
{
IndexList src( srcIndices.begin(), srcIndices.end() );

for ( IndexList::iterator it=src.begin(); it != src.end(); ++it )
{
moveItem( *it, destIndex );

IndexList::iterator it2 = it;
++it2;

if ( *it < destIndex )
{
std::transform( it2, src.end(), it2, DecIfInRange< IndexType >( *it, destIndex ) );
}
else if ( *it > destIndex ) //if we move from the right of the destination point the next element should be moved on one index to the right.
{
std::transform( it2, src.end(), it2, IncIfInRange< IndexType >( destIndex, *it ) );
++destIndex;
}
}
}

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

суббота, 9 февраля 2013 г.

Git, извлекаем данные из удаленного репозитория в непустой каталог

Мы работаем над довольно большим програмным продуктом весь исходный код которого хранится под SVN. Полная сборка всего приложения с использование IncrediBuild занимает 2 часа. Часто после очередного апдейта, если кто-то изменил что-то в одной из базовых библиотек приходится тратить слишком много времени на то, что все заново собрать. Поскольку наша команда работает только над небольшой частью продукта, мы решили использовать Git для того, что обмениваться кодом между собой без необходимости подтягивать изменения от других команд каждый раз - все что нужно, это время от времени синхронизироваться с SVN. Мы установили свой Git сервер и внесли в репозиторий только нужные нам проекты, отправив все остальные в игнор. Однако столкнулись со следующим затруднением. Когда кто-нибудь из наших разработчиков пытается извлесь файлы из Git в папку где лежат исходники извлеченные из SVN, используюя git init & git remote add + git pull, Git ругается на то, что такие файлы уже существуют в целевом каталоге и отказывается извлечь их.

error: Untracked working tree file 'file_name' would be overwritten by merge.

Проблема решается вызовом git reset --hard FETCH_HEAD. Команда означает, что нужно извлечь все файлы из ревизии FETCH_HEAD в локальную копию со сбросом индекса (staging area) и всех локальных изменений, при этом файлы стоящие в игноре остаются незатронутыми. Таким образом, чтобы извлечь файлы из Git в папку где уже существуют такие файлы, нужно сделать следующие шаги

git init
git remote add <remote_repo>
git fetch <remote_repo> <branch>
git reset --hard FETCH_HEAD

среда, 9 января 2013 г.

Подсветка синтаксиса в less

Есть команда выводящая результаты с подсветкой / раскраской - скажем это yum search, но выхлоп очень большой и нужно использовать пейджер less. Однако текст, прошедший через less становится одноцветным. Как сделать так, чтобы он оставался цветным. Вот ответ:
yum --color=always search your_text | less -R
По умолчанию когда обнаруживается pipe цвета автоматически отключаются. Флаг --color=always говорит, что отключать их не нужно даже в пайпе. Флаг -R сообщает less, что нужно отображать входные цветовые escape последовательности, т.е. подсвечивать текст.

вторник, 25 декабря 2012 г.

Удаление указателей из контейнера и их уничтожение

Предположим что ваш последовательный контейнер хранит указатели и вам нужно удалить и уничтожить обьекты удовлетворяющие некоторому критерию. Если указатели не являются умными, то идиому remove + erase использовать не получится поскольку обьекты будут только удалены, но не будут уничтожены. Удаление поштучно является неплохой идеей для std::list, но это будет накладно для std::vector и std::deque, поскольку контейнеру придется многократно перемещать оставшиеся элементы так, чтоб на месте удаленный элементов не оставалось дыр, т.е. в совокупности понадобится O( N2 ) перемещений.
Ниже описано решение проблемы с использованием std::stable_partition в паре с erase. Идея в том, чтобы сначала переместить всех кандидатов на удаление в конец коллекции (оптимизация под вектор), а затем вызвать erase для всех их.
Алгоритмическая сложность: N сравнений + N перестановок в случае, если std::stable_partition сможет использовать внутренний буффер или N log N перестановок в противном случае.
Сначала обьявим функтор для уничтожения элементов в контейнере
template< class TPointer >
struct DeletePointer
{
void operator () ( TPointer item ) { delete item; }
};
Затем переходим к определению самой функции, удаляющей и уничтожающей элементы контейнера
template< class Collection, class Predicate >
void delete_erase_if( Collection& c, Predicate pred ) {
typename Collection::iterator removeBegin = std::stable_partition( c.begin(), c.end(), std::not1 ( pred ) );
std::for_each( removeBegin, c.end(), DeletePointer< typename Collection::value_type > () );
c.erase( removeBegin, c.end() );
}
Поскольку stable_partition переставлят все элементы, удовлетворяющие предекату, в начало, а нам нужно переставлять их в конец, то нам приходится как-то инвертировать предикат. Для этого в теле функции используется std::not1 из модуля <functional>.
Если ваш модуль подключает библиотеку QtCore то вы можете избавиться от функтора DeletePointer заменив вторую строчку в теле delete_erase_if на вызов qDeleteAll.
К минусам приведенной выше реализации относится то, что функция не принимает указатели на функции в качестве предиката. Для того, чтобы обойти данное ограничение, нужно просто обернуть указатель на функцию-предикат в std::ptr_fun.

понедельник, 24 декабря 2012 г.

Указатель на функцию как параметр шаблона

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

Для удобства обьявляем тип указателя на функцию:
typedef QDateTime ( QDateTime::*DateTimeIncrement ) ( int ) const;
Обьявляем шаблон (функцию или класс), принимающий указатель на функцию как параметр
template <DateTimeIncrement Increment>
QDateTime add( QDateTime dateTime, int delta )
{
return ( dateTime.*Increment )( delta );
}
А потом можно заюзать шаблон...
inline QDateTime addDays( QDateTime dateTime, int delta ) { return add< &QDateTime::addDays >( dateTime, delta ); }
...
QDateTime tomorrow = addDays( QDateTime::currentDateTime(), 1 );

воскресенье, 25 ноября 2012 г.

Linux, как указать программе путь к .so библиотеке

Пока я разрабатывал одну библиотеку, мне нужно было как-то тестировать ее без копирования в соответствующие системные каталоги.
Идеальным выглядело решение, когда исполнительный файл с тестами и тестируемая библиотека лежат в одном каталоге, как в Windows. Проблема была в том, что в Linux это не работало - экзешник не мог найти библиотеку.

Решается это просто. экзешник нужно запускать следующей командой (при условии, что текущим каталогом является тот, где лежит экзешник с библиотекой/библиотеками):

$ LD_LIBRARY_PATH=`pwd` ./executable_name

Если и это не помогает, то попробуйте посмотреть какую именно библиотеку пытается найти ваш экзешник, вызвав команду

$ ldd executable_name

или
$ readelf -d executable_name

Скорее всего просто напросто требуется библиотека с четко прописанной версией в ее названии. Например: libMyLib.so.1 вместо libMyLib.so.1.0.0
Лечится это символическими ссылками:

$ ln -s libMyLib.so.1.0.0 libMyLib.so.1

суббота, 31 марта 2012 г.

Hello RPM

Here I'm giving a brief guidance how to create a simplest RPM file for a QT4 based application. If you want this post to be translated to English just contact me and I will do it upon first request with giving you all possible assistance in urgent case. In return I will expect your help with checking it on grammatical mistakes.

В интернете можно найти много литературы по RPM, о формате .spec файла и о том как пользоваться rpmbuild.
В этом посте я приведу краткое руководство о том как создать простейший RPM пакет не углубляясь в детали. Пост будет особенно интересен тем, кому нужно создать RPM для проекта написанного на Qt.
Итак, у меня есть приложение HelloWorld, написанное на QT 4.8 и доступное для загрузки отсюда. Минимальные требования, для сборки проекта - наличие Qt4.8 и qmake (qt и qt-devel пакеты для Fedora Linux соответственно). 
Комментарии к исходникам:
  1. Когда распакуете архив с исходниками обратите внимание что корневая папка называется "helloworld-1.0". Это первый важный момент. "helloworld" - это название приложения и название пакета, который мы сейчас будем создавать. Нужно чтобы название приложения совпадало с названием пакета. "1.0" - это версия нашего продукта. "-" между названием и версией соответствует соглашению о построении полных названий пакетов. Очень важно чтобы корневая папка именовалась как [название приложения/пакета]-[версия приложения]. Без этого вы получите ошибку на стадии %prep при сборке. От пакетов требуется, чтобы все символы названия были в нижнем регистре поэтому позаботьтесь, чтобы исполнителный файл тоже соответствовал этому требованию.
  2. В файле HelloWorld.pro обратите внимание на строчки:  
    unix {
        target.path = /$(DESTDIR)
        INSTALLS += target
    }
    На основании этой строки qmake сгенерируют инструкцию в Makefile для команды "make install". Назначение макроса DESTDIR я поясню позже. Сейчас же обратите внимание, что он начинается с символа "/". Он необходим, инача qmake допишет в Makefile еще и путь к файлу проекта, что приведет к проблемам на фазе %install.
  3. В корневом каталоге лежит скрипт configure который просто вызывает qmake. Этот скрипт будет автоматически вызываться на стадии %prep и его назначение в том, чтобы подготовить Makefile. Именно поэтому я поместил в него вызов qmake. Проследите, чтобы этот скрипт был помечен как исполняемый файл (chmod +x configure)
Для создания и проверки правильности rpm пакетов вам понадобятся rpmdevtools и rpmlint. Устанавливаем их командой
sudo yum install rpmdevtools rpmlint
Теперь создаем дерево сборки командой
rpmdev-setuptree
В результате у вас появится каталог ~/rpmbuild с дочерними каталогами SPECS, SOURCES, RPMS, SRPMS, BUILD и BUILDROOT. Информацию о назначении каталогов вы найдете на rpm.org.  Полученное дерево каталогов вы можете использовать для построения rpm пакетов для произвольных версий любых программних продуктов.

Скопируйте архив с исходниками в ~/rpmbuild/SOURCES.


Теперь переходим к самому главному. Скачайте spec файл перейдя по следующей ссылке (скачать) и положите его в папку ~/rpmbuild/SPECS.
Перейдите в этот каталог (cd ~/rpmbuild/SPECS) и запустите на исполнение команду
rpmbuild -ba helloworld.spec
В результате в папке RPMS (со смещением на текущую архитектуру) будет лежать ваш RPM с бинарниками, а в папке SRPMS - с исходниками и spec файлом. Если вам нужен только RPM с бинарниками замените -ba  на -bb.

Несколько комментариев к spec файлу:
  1. Поле Name содержит имя rpm файла, который будет сгенерирован. Это имя должно совпадать с названием приложения.
  2. Поле Source0 содержит название нашего архива с исходниками
  3. Обратите внимание на строку 
    make install DESTDIR=$RPM_BUILD_ROOT%{_bindir}
    в разделе %install. Помните, что в HelloWorld.pro мы устанавливали target.path в /$(DESTDIR)? Теперь же мы инициализируем эту переменную путем взятым из контекста сборки и передаем ее команде make install, которая в свою очередь создаст такой каталог (он будет находиться по адресу ~/rpmbuild/BUILDROOT/[полное имя пакета]/usr/bin) и скопирует туда собранный исполняемый файл. 
  4. Строка  %{_bindir}/%{name} в разделе %files инструктирует сборщик, что в результирующий RPM необходимо включить файл с именем helloworld (именно в это значение развернется макрос %name) лежащий со смещением usr/bin (значение макроса %_bindir) относительно $RPM_BUILD_ROOT (т.е. ~/rpmbuild/BUILDROOT/[полное имя пакета]). Без этой строки ваш RPM файл останется пустым.
После запуска команды rpm -i helloworld-1.0-1.fc16.x86_64.rpm программа helloworld будет установлена в папку /usr/bin (значение макроса _bindir)

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

Кроме того у описаной процедуры есть недостаток. Если нужно установить несколько файлов из одного пакета в разные места назначения, передача usr/bin через DESTDIR не очень подходит. Возможно лучше было бы захардкодить /usr/bin в  HelloWorld.pro. Другое решение расширить набор входных параметров. В общем тут тоже есь над чем подумать.

Если этот пост был полезен вам, чиркните пару слов. Также буду признателен тем кто будет давать дополнительную полезную информацию на данную тему.

четверг, 2 июня 2011 г.

Qyoto Signals Emission

Классы Qyoto, являются обертками над соответствующими классами Qt. У некоторых Qt-шных классов имеются сигналы которые нужно как-то эмитить из managed кода.
Например, если вы реализуете класс, наследующий QAbstractItemModel, вам наверняка понадобится эмитить сигнал dataChanged(...).
Делается это просто. Любой класс, наследующий managed QObject имеет унаследованное свойство Emit типа IObjectSignals. Для QAbstractItemModel сущестует интерфейс IQAbstractItemModelSignals, содержащий все необходимые сигналы класса QAbstractItemModel.
Все, что вам нужно сделать в вашем классе, наследующем QAbstractItemModel это, в месте где необходимо послать сигнал, выполнить следующий код:
IQAbstractItemModelSignals emt = (IQAbstractItemModelSignals)Emit;
emt.DataChanged(...);
Для остальных Qyoto классов подход будет аналогичным.
Пример того, как нужно создавать обработчики сигналов и как подписываться на сигналы вы можете найти по указанной ссылке: http://misc-sonofagun.blogspot.com/2010/12/qyoto-and-gc-issues.html

среда, 1 июня 2011 г.

QVariant( String )

В текущей версии Qyoto (ver. 4.5.0.0, 2011-06-01) конструктор класса QVariant(String) имеет баг из-за которого QVariant не поддерживает юникодные строки. Связано это с тем, что внутри managed QVariant вызывается не тот unmanaged конструктор:
QVariant(const char*)
вместо
QVariant(const QString&)
Лечится это довольно просто: создается managed класс, назовем его QVariantQString, наследующий QVariant с единственным конструктором принимающим System.String и вызывающим внутри нужный unmanaged конструктор. Далее везде, где нужно создать QVariant( String ), нужно создавать QVariantQString( String ).
Ниже приведен полный работающий код этого класса:


using System;

namespace Qyoto
{
  [SmokeClass("QVariant")]
  internal class QVariantQString : QVariant
  {
    protected QVariantQString(Type dummy) : base(dummy) {}
    
    protected new void CreateProxy()
    {
      interceptor = new SmokeInvocation(typeof(QVariantQString), this);
    }
    
    public QVariantQString(string str) : this((Type) null)
    {
      CreateProxy();
      interceptor.Invoke("QVariant$", "QVariant(const QString&)", typeof(void), typeof(string), str);
    }
  } //class
} //namespace




* This source code was highlighted with Source Code Highlighter.

воскресенье, 6 марта 2011 г.

MonoDevelop + NUnit (Fedora 14)

Согласно информации с официального сайта MonoDevelop (http://monodevelop.com/Download/What%27s_new_in_MonoDevelop_2.4), NUnit интегрирован в IDE.
Вот как это должно выглядеть:





Однако, когда мне понадобилось написать юнит-тесты для своего проекта, никакой интеграции я не обнаружил. Ни в списке "Add-in Manager'а" ни, даже, в "Edit References" диалоге нет и упоминания NUnit.
На моей Fedora 14 установлены:
  • mono-core v.2.6.7-3.fc14 + auto-detected dependencies (incl. mono-nunit, mono-nunit-devel)
  • monodevelop v.2.4-1.fc14 + auto-detected dependencies
Разбираясь с проблемой я обнаружил, что при загрузке
"/usr/lib/monodevelop/AddIns/NUnit/MonoDevelop.NUnit.dll"
MonoDevelop не может найти следующие referenced-библиотеки:
  • nunit.core.dll
  • nunit.core.interfaces
  • nunit.framework.dll
  • nunit.util.dll
несмотря на то, что они зарегистрированы в GAC (/usr/lib/mono/gac/).
Решать проблему использованием мягких или жестких ссылок не хотелось.
Поначалу, я думал, что сборки были как-то криво зарегистированы в GAC и пытался найти решение работая в этом направлении, но никакие попытки перерегистрировать эти библиотеки через gacutil успеха не принесли.

В итоге я сдался и все-таки создал мягкие ссылки рядом с MonoDevelop.NUnit.dll на библиотеки, которые MonoDevelop не мог найти.
ln -s [path to assembly in gac]/nunit.core.dll /usr/lib/monodevelop/AddIns/NUnit/nunit.core.dll

ln -s [path to assembly in gac]/nunit.core.interfaces.dll /usr/lib/monodevelop/AddIns/NUnit/nunit.core.interfaces.dll

ln -s [path to assembly in gac]/nunit.framework.dll /usr/lib/monodevelop/AddIns/NUnit/nunit.framework.dll

ln -s [path to assembly in gac]/nunit.util.dll /usr/lib/monodevelop/AddIns/NUnit/nunit.util.dll
Как я уже упомянул выше, путь к GAC: /usr/lib/mono/gac/
однако ваши пути к GAC и MonoDevelop могут отличаться, например: /usr/local/lib/mono/gac и т.п., в зависимости от того, что и куда вы поставили.

Это полностью решило проблему: появились соответствующие элементы UI и даже nunit сборки в "Edit References" диалоге:




Стоит правда упомянуть, что MonoDevelop все еще использует nunit-2.4.8 в то время как уже выпущена 2.5.9 и по-видимому переход на более новый nunit состоится нескоро и зависит от активности сообщества.

воскресенье, 9 января 2011 г.

Finalizer, Как работает GC?

В .Net GC реализует алгоритм "Пометить, Освободить, Упаковать" описанный например здесь. Изначально GC рассматривает все объекты как недостижимые. В процессе прохода по графу он помечает все достижимые объекты флажком, а все оставшиеся, соотвтетственно, подлежат сборке. Такой алгоритм осуществляется в один проход и позволяет легко определять и уничтожать "острова изоляции", а также не подвержен проблеме зацикленных ссылок.
Значительный интерес представляет то, как GC поступает с объектами, имеющими файналайзеры. Можно ли из файналайзера обращаться к другим reference type объектам? Могут ли эти reference type объекты оказаться уже уничтоженными и что тогда произойдет?
Давайте определим термины:
завершенными мы будем называть объекты для которых были вызваны их файналайзеры.
уничтоженными или собранными мы будем называть те объекты, память занимаемая которыми была освобождена и возвращена куче.
Сразу же скажу, что, в отличии от C++, обратиться к уничтоженным объектам в .Net н е в о з м о ж н о. Зато обращение к завершенным объектам вполне допустимо, более того, они даже могут быть "воскрешены".

На нижеприведенной диаграмме приблизительно описан жизненный цикл reference type объектов:



Из диаграммы сразу видно, что объекты имеющие файналайзеры живут на один цикл дольше обычных и, по всей видимости, не могут быть собраны с поколением 0 (хотя это зависит от деталей реализации GC).

Условные обозначения на диаграмме:
Finalization Queue - очередь финализации (терминология из MSDN)
Freachable Queue - согласно терминологии MSDN, это список объектов готовых к завершению (финализации). Объекты попавшие в этот список, а так же все объекты на которые они ссылаются и так далее, не подлежат уничтожению и переживают сборку мусора. Поэтому из файналайзера можно смело обращаться к любым объектам, все они живы, другое дело что они могут оказаться уже завершенными.
Рассмотрим следующую ситуацию:

class ClassA
{
  bool _disposed;

  public void DoSomething()
  {
    if (_disposed)
    throw new ObjectDisposedException("ClassA");
  }

  ~ClassA()
  {
    _disposed = true;
  }
} //class


class ClassB
{
  ClassA _classA;

  public ClassB()
  {
    _classA = new ClassA();
  }

  ~ClassB()
  {
    _classA.DoSomething();
  }
} //class

* This source code was highlighted with Source Code Highlighter.

К моменту когда ClassB в файналайзере обращается к ClassA.DoSomething экземпляр ClassA может оказаться уже завершенным и в этом случае метод DoSomething кинет ObjectDisposedException. Если исключение корректно обработается в вызывающем коде, то никаких проблем нет. Если же оно "пролетит мимо" то это может привести к аварийному завершению всего приложения согласно тексту из MSDN. Вот почему в файналазерах необходимо осторожно работать с обращениями к другим reference type объектам.

Теперь об оживлении.
В файналайзере объект имеет право передать другому, в том числе и живому объекту ссылку на самого себя. В этом случае он и все объекты на которые он ссылается вновь окажутся доступными для кода программы и как бы воскреснут. Правда восрешенный объект не будет больше состоять в Finalization Queue и когда он следующий раз окажется недоступным, его файналайзер больше не будет вызван и объект окажется просто уничтоженным. Для того, чтоб вновь поставить объект в очередь финализации нужно воспользоваться вызовом
GC.ReRegisterForFinalize
Хотя лично мне подобный трюк не нравится потому, что он, например, может привести к появлению бессмертных Дунканов Маклаудов, утечкам памяти и излижним нагрузкам на GC.

Ниже приведен пример кода в котором объект восстанавливается в файналайзере:

class RecoverableObject
{
  int _id;
  static int Counter;
  bool _canDie;

  public static RecoverableObject Root;

  static RecoverableObject()
  {
    Root = new RecoverableObject();
    Root.CanDie = true;
  }

  public RecoverableObject()
  {
    _id = ++Counter;
  }

  ~RecoverableObject()
  {
    if (CanDie)
    {
      Console.WriteLine("good bye Id={0}", _id);
      return;
    }

    Console.WriteLine("obj.Id={0} is going to be recovered", _id);
    Root.Other = this;
    GC.ReRegisterForFinalize(this);
  }

  public int Id
  {
    get
    {
      return _id;
    }
  }

  public bool CanDie
  {
    get
    {
      return _canDie || this == Root;
    }
    set
    {
      _canDie = value;
    }
  }

  public RecoverableObject Other { get; set; }



  static void RecoverableObjectTest()
  {
    RecoverableObject obj;

    obj = new RecoverableObject();
    Console.WriteLine("first collection...");
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Console.WriteLine("after the first collection");
    Console.WriteLine();

    obj = RecoverableObject.Root.Other;
    RecoverableObject.Root.Other = null;
    Console.WriteLine("obj.Id={0} is available again", obj.Id);
    Console.WriteLine("second collection...");
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Console.WriteLine("after the second collection");
    Console.WriteLine();

    obj = RecoverableObject.Root.Other;
    RecoverableObject.Root.Other = null;
    Console.WriteLine("obj.Id={0} is available again", obj.Id);
    obj.CanDie = true;
    Console.WriteLine("third collection, obj.Id={0} must die!", obj.Id);
    GC.Collect();
    GC.WaitForPendingFinalizers();
    Console.WriteLine("after the third collection");
    Console.WriteLine();

    Console.WriteLine("RecoverableObjectTest exiting...");
  }

} //class



* This source code was highlighted with Source Code Highlighter.

Вкратце, вот что нужно знать о финализации:
  1. Все объекты, имеющие метод Finalize помещаются в очередь финализации при их создании. Уничтожение объектов помещенных в очередь финализации производится как минимум в два этапа (может быть и больше, если их оживлять)
  2. Метод Finalize не должен выдавать исключения, поскольку они не могут обрабатываться приложением и могут привести к завершению работы приложения.
  3. Реализация методов Finalize (деструкторов) может отрицательно воздействовать на производительность, и злоупотреблять ими не рекомендуется.


Данный пост отвечает на вопрос, поставленный в посте "Qyoto, Issues for investigation" о том правомерно ли обращаться из файналайзера к reference type объектам. Отвечает утвердительно: "Да, правомерно"

В ближайших статьях я постараюсь дать ответы на оставшиеся вопросы.

четверг, 6 января 2011 г.

Qyoto, issues for investigation

Я выкачал исходники Qyoto и нашел в них несколько моментов, требующих более глубокого изученения:
  1. P/Invoke. Все managed обертки над Qt-шными классами содержат в себе член SmokeInvokation, через который осуществляются вызовы unmanaged кода и вызовы managed callback функций. Внутри этого класса все вызовы осуществляются через P/Invoke. Насколько это снизит производительность приложения? Предварительные тесты (на .Net Framework 4.0 показали), что вызов unmanaged функции без параметров через P/Invoke при отстутствии нагрузки на GC осуществляются в 50-70 раз медленнее, чем вызов аналогичной managed функции. Если в среднем managed код исполняется в 2-3 раза медленнее чем unmanaged и при этом он будет еще и осуществлять многократные вызовы через P/Invoke, то такой код будет работать в (2..3)*(50..70) = (100..200) раз медленнее чем unmanaged. Для нехитрого GUI со стандартными контролами это все равно кажется некритичным, но если вы, например, собираетесь отображать геоинформационные данные и ваши требования к производительности GUI высоки, то такое замедление видится неприемлемым. Более того, оно будет даже более существенным в случае маршаллинга данных и высоких нагрузок на GC. Тем не менее нужно изучить каковы накладные расходы на P/Invoke в Mono, может статься, что они не так уж велики.
  2. Следующий момент, это вызов деструкторов Qt-шных объектах в файналайзерах их managed оберток. Во-первых файналайзеры создают дополнительную нагрузку на GC и снижают производительность. Во-вторых в классах, поддерживающих интерфейс IDisposable, в методах Dispose() тоже вызываются деструкторы unmanaged объектов, но сами обертки при этом не помечаются как не нуждающиеся в финализации вызовом GC.SuppressFinalize(). В третьих, обращение к reference type объектам (SmokeInvokation) из файналайзеров кажется мне непрвильным, потому, что порядок уничножения reference type объектов недетерменирован и, таким образом, SmokeInvokation объект к моменту обращения уже может оказаться собранным GC. Это тоже нужно проверить.
  3. И наконец, если ref type объект был создан в некоторой функции и начиная с некоторого момента в создавшей его функции больше не будет обращений к нему, то GC разрешено собрать его, не дожидаясь выхода потока управления из данной, создавшей объект функции (я добавлю здесь пояснения). Таким образом может получится, что файналайзер объекта будет вызван из потока GC в то время пока unmanaged код все еще будет работать с его unmanged ресурасами. Чтоб предотвратить такую ситуацию нужно использовать HandleRef или GCHandle совместно с GC.KeepAlive. Нужно также проверить, как в Qyoto разруливается описанная ситуация и предусмотренна ли она вообще.

воскресенье, 26 декабря 2010 г.

Qyoto and GC issues

Сегодня пол дня слушал песню Amy Winehouse "You Know I'm No Good" и долбался с одной проблемой в Qyoto (а их по-видимому там не мало). До причины я все-таки докопался, но хотел описать все завтра, так как сейчас уже довольно поздно, но неожиданно для себя в одном из источников по Qyoto, с которым я работал, в одном из примеров обнаружил метку, лежащую на форме с цитатой из этой песни. Это судьба, это словно зуб Гоя (Серьезный Человек), подумал я и решел все-таки написать пост сегодня.
А проблема следующая: У меня есть два идентичных куска кода на C# (Qyoto) и на C++ (Qt 4.7), создающие QMainWindow с QTreeView, лежащем на нем. Этому TreeView скармливается QStandardItemModel содержащая в себе некоторые QStandardItem'ы. В случае Qt все работает хорошо, а вот в Qyoto item'ы на мгновение появляются, а потом почему-то исчезают.
В результате выяснилось, что item'ы на которые нет ссылок из managed кода просто собираются GC, не смотря на то, что они были добавлены в модель и по идее ссылки на них должны храниться в этой самой QStandardItemModel!!! Такова уж специфика работы Smoke, будем иметь это ввиду.
Выяснить это удалось при помощи слабых ссылок. Ниже приведен код на примере которого можно убедиться в верности предположения:

using System;
using Qyoto;
using System.Collections.Generic;
namespace QyotoDrv
{
  public class MainWindow : QMainWindow
  {
    Ui.MainWindow _ui;
    
    QStandardItem it1; //menu item referred from MainWindow

    WeakReference _wr1; //weak reference to it1
    WeakReference _wr2; //weak reference to it2
    
    const string SlotShowWR = "slot_ShowWR()";
    
    public MainWindow ()
    {
      _ui = new Ui.MainWindow(); 
      _ui.SetupUi(this);  
      
      SetModel();
    }
    
    void SetModel()
    {
      var model = new QStandardItemModel(_ui.treeView);
      
      it1 = new QStandardItem("Hello, World!");
      model.AppendRow(it1);

      //the following menu item is not referred from MainWindow and will be collected by GC,
      //in spite of being added to the model!!!
      QStandardItem it2 = new QStandardItem("Good bye, World!");
      model.AppendRow(it2);
      
      _ui.treeView.SetModel(model);

      //set weak references pointing to the corresponding menu items
      _wr1 = new WeakReference(it1);
      _wr2 = new WeakReference(it2);
      
            
      QMenu file = MenuBar().AddMenu("Click me!");
      
      QAction showWR = new QAction("Show Weak References", this);
      file.AddAction(showWR);
      Connect(showWR, SIGNAL("triggered()"), this, SLOT(SlotShowWR));
    }
    
    [Q_SLOT(SlotShowWR)]
    void ShowWeakReference()
    {
      //after the main window will be shown wait a fiew seconds until the second item has disappeared
      Console.WriteLine("wr1.IsAlive={0}", _wr1.IsAlive); //is alive
      Console.WriteLine("wr2.IsAlive={0}", _wr2.IsAlive); //is not alive!
    }
  } 
}


* This source code was highlighted with Source Code Highlighter.


Запустите приложение. После появления главного окна дождитесь пока второй item не исчезнет, после чего щелкните на пункт меню "Click me! > Show Weak References" и посмотрите на консоль.
Комменты на английском сделаны для возможных англоязычных посетителей, чтоб хоть чем-то помочь им, слишком уж мало материала в сети на тему Qyoto.

понедельник, 20 декабря 2010 г.

UI-калка

Для удобства можно включать .ui фалы в C#-проект настроив pre-build step так, чтобы они автоматически ui-кались, т.е. утилита uics генерировала из них .cs файлы (см. предыдущий пост). Тогда вы сможете редактировать их в QtDesigner просто кликнув по ним в Solution Explorer не выходя из MonoDevelop.IDE автоматически проставит для них свойства "Build Action=Nothing" и "Copy to output directory=Do not copy" (на всякий случай проконтролируйте это), а значит они не будут встраиваться в результирующую сборку в качестве "embedded" ресурсов и их существование в проекте - вопрос исключительно удобства.
Для того что-бы .ui файлы автоматически ui-кались при компиляции (точнее перед ее началом) создадим скрипт uicAll.sh и положим его в папку проекта. Содержимое скрипта следующее:

for uifile in `find $1 -name "*.ui"`
do
    csfile="${uifile%.*}.Designer.cs"
    if [ ! -f $csfile ] || [ `stat -c %Y $uifile` -gt `stat -c %Y $csfile` ]; then
        echo "uicking... $uifile"
        uics "$uifile" > "$csfile"
    fi
done


После чего добавим в свойствах проекта новый pre-build step как на скриншоте ниже:

пятница, 17 декабря 2010 г.

Qyoto

Не то, что бы я любитель экзотики, но так получилось, что у меня дома теперь Fedora 14 и MonoDevelop. Хочется писать кросс-платформенные приложения с удобным и красивым GUI, но к сожалению WPF на mono не портирован и видимо портирован никогда не будет. Windows Forms выступать альтернативой WPF просто не может, а GTK# мне не интересен, возможно потому, что напоминает мне Windows Forms.
К счастью я несколько знаком с Qt и знаю насколько мощным является этот Framework и какие красивые и дружественные пользователю GUI он позволяет создавать. Кроме того, оказалось что для Qt существует binding под mono, называемый Qyoto, а возможность работать с QT напрямую из .Net кажется весьма привлекательной. Недостатком Qyoto является малое количество документации к нему.

Для начала работы вам понадобится установить следующие пакеты (для Fedora KPackageKit)
* qyoto - сами библиотеки, основная из них (qt-dotnet.dll)
* qyoto-devel - утилиты для разработки, рекомендую установить, если вы собираетесь работать с ui файлами
* qt-devel - если вы хотите работать с QtDesigner (это нативный Qt-шный дезайнер)

В сети достаточно материалов о том как создавать простые приложения и как связывать сигналы и слоты.
Ниже приведено несколько полезных ссылок:
http://zetcode.com/tutorials/qyotosharptutorial/
http://ibboard.co.uk/Programming/using-qyoto.html
http://www.kdedevelopers.org/node/2090

Во всех примерах GUI создается прямиком из кода, мне не удалось найти ниодного описания того, как подключать .ui файлы созданные в QtDesigner (MonoDevelop, не имеет дизайнера для qyoto).
И все же такая возможность существует! В пакет qyoto-devel входит утилита uics предназначенная именно для этого.
Предположим, что у вас имеется файл MainWindow.cs с классом MainWindow : QMainWindow, который вы хотите инициализировать из файла MainWindow.ui. Для этого, при помощи утилиты uics вам сначала придется сгенерировать .cs файл, некий аналог .Designer.cs файла в Windows Forms:

$ uics MainWindow.ui > MainWindow.Designer.cs

В выходном файле будет два класса: Ui_MainWindow и Ui.MainWindow.
Вам нужно будет в конструкторе MainWindow создать экземпляр Ui.MainWindow класса, и при помощи метода SetupUi этого обьекта инициализировать MainWindow.
Все элементы GUI являются public членами класса Ui.MainWindow, поэтому для того, чтоб всегда иметь возможность доступа к ним, экземпляр класса Ui.MainWindow сохраняется в соответствующем поле класса MainWindow, как показано в приведенном ниже примере:

class MainWindow : QMainWindow
{
  Ui.MainWindow _ui;

  public MainWindow ()
  {
    _ui = new Ui.MainWindow();
    _ui.SetupUi(this);
    
    //инициализация прочих членов
    
    Show();
    
  }

  /* Члены класса */
}


* This source code was highlighted with Source Code Highlighter.


Qt-шные классы (контролы и прочее) описаны в библиотеке qt-dotnet.dll. Эта библиотека должна быть подключена к вашему проекту.
В общем Qyoto кажется вполне пригодным к применению. Был правда случай когда регистр первой буквы свойства сгенеренного uics отличался от регистра первой буквы этого же свойства в qt-dotnet.dll, в результате чего сгенерированный код оказался некомпилируемым. Я столкнулся с такой ситуацией пока только для одного свойства отдельно взятого класса. В моем случае можно было легко отказаться от использования этого класса, что я и сделал, но думаю, что проблему можно было бы решить и иначе: например поправив исходники uics. Пока, что это не кажется проблемой. Посмотрим, что будет дальше!

VS Debugger

По работе мне часто приходится отлаживать mixed код в Visual Studio 2008. Порой студию клинит и отладчик перестает входить в некоторые функции и останавливаться на установленных в них бряках. Поскольку случается это не очень часто, то способы лечения этой проблемы быстро забываются и каждый следующий раз оказывается как первый. Чтоб облегчить себе жизнь в будущем и, возможно, немного помочь другим, я опишу в этом посте все необходимые шаги, пока информация еще свежа после последней терапии. Ниже использованы скриншоты из VS 2010, но для VS 2008 действия абсолютно аналогичны.

1. Если вы используете mixed код, то в свойствах всех проектов вам придется установить тип отладчика "mixed".

В C++ проектах:



В C# проектах:



Далее в свойствах компилятора указываем формат отладочной информации. Нам нужно, чтоб она хранилась в pdb (Program DataBase) файле, поэтому выбираем опцию "Program Database /Zi" или следующую за ней (честно говоря второе я никогда не пробовал), как на следующем скриншоте.



Теперь осталось только указать компановщику, чтоб он генерил .pdb базу:



Все остальные проблемы решаются подчисткой ncb, suo, obj и т.п. файлов и полной перекомпиляцией, но это уже по мере необходимости.

Во время отладки вы можете проверить загружена ли отладочная информация для нужных вам модулей следующим образом: При запущенном отладчике в студии нужно войти в пункт меню Debug > Windows > Modules.



В открывшемся списке вы сможете найти инересующий вас модуль.Если в колонке "Symbol File" вы увидите имя вашего pdb, а в колонке "Symbol Status" рядом с ним - статус "Symbols loaded", то все в порядке, в противном случае вы можете попытаться загрузить его вручную из соответствующего пункта контекстного меню как это показано на скриншоте ниже:



Это, пожалуй все.
Вот ссылка на пост, где я нашел часть приведенной выше информации: http://geekswithblogs.net/dbutscher/archive/2007/06/26/113472.aspx

четверг, 16 декабря 2010 г.

yyyy.MM.dd HH.mm.ss -> time_t

Поскольку time_t это время (UTC) в секундах, прошедшее с 1 января 1970 года, 00:00:00, то переход от System.DateTime к time_t легко осуществить следующим образом:

DateTime time = DateTime.UtcNow; //предположим что это вермя которое мы хотим сконвертить к time_t
DateTime unixEpochMidnight = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
long time_t = (long)(time - unixEpochMidnight).TotalSeconds;


* This source code was highlighted with Source Code Highlighter.


Однако недавно передо мной стала задача в том, чтобы разобрать строчку с простым представлением даты и времени, что-то типа "2010-12-15 14:30:00", сконвертировать ее в time_t, но сделать все это нужно было в unmanaged коде на C++. В том чтобы распарсить строку нет ничего сложного. Проблема только в том, чтобы из года, месяца, дня, часа, минут, секунд перейти к time_t. Сложность встает благодаря наличию високосных годов, однако она оказалась довольно просто решаемой.
Ниже приведен C# код программы осуществляющей преобразование к time_t. Почему не C++? Потому, что мне было удобней сначало решить задачку на языке который я лучше знаю, а потом портировать решение на С++

using System;

namespace TimeCs
{

  class Program
  {

    const long SecPerDay = 3600 * 24;
    const long A1 = 365 * SecPerDay; //=365 * 86400
    const long A2 = SecPerDay / 4; //=21600
    const long A3 = -SecPerDay / 100; //=864 Обратите внимание на занк "-"
    const long A4 = SecPerDay / 400; //=216
    const long SecPerYear = A1 + A2 + A3 + A4;


    static long SubYearsToSec(int y1, int y2)
    {
      long dif1 = SecPerYear * y1 / SecPerDay;
      long dif2 = SecPerYear * y2 / SecPerDay;
      return (dif1 - dif2) * SecPerDay;
    }
    

    static bool IsLeapYear(int year)
    {
      if (year % 400 == 0)
        return true;

      if (year % 100 == 0)
        return false;

      return year % 4 == 0;       
    }
    

    //month 1..12
    //day 1..31
    static int ToDayInYear(int year, int month, int day)
    { 
      int daysInFeb = IsLeapYear(year) ? 29 : 28;
      int[] daysInMonths = new int[] { 31, daysInFeb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
      int stopIdx = month - 1; //1..12 -> 0..11
      int ax = 0;
      
      for (int i=0; i<stopIdx; ++i)
      {
        ax += daysInMonths[i];
      }      
      ax += day;

      return ax;
    }


    static long SubMonthsDaysToSec(int y1, int m1, int d1, int y2, int m2, int d2)
    {
      long dd1 = ToDayInYear(y1, m1, d1);
      long dd2 = ToDayInYear(y2, m2, d2);
      
      return (dd1 - dd2) * SecPerDay;
    }


    static long Sub(DateTime dt1, DateTime dt2)
    {
      long dYearsSec = SubYearsToSec(dt1.Year, dt2.Year);
      long dDaysSec = SubMonthsDaysToSec(dt1.Year, dt1.Month, dt1.Day, dt2.Year, dt2.Month, dt2.Day);
      long dTime = ((long)(dt1.Hour - dt2.Hour) * 60L + (long)(dt1.Minute - dt2.Minute)) * 60L + (long)(dt1.Second - dt2.Second);
      
      return dYearsSec + dDaysSec + dTime;
    }
    
    
    static long ToTime_T(DateTime dateTime)
    {
      DateTime unixEpochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
      
      return Sub(dateTime, unixEpochTime);
    }


    static void Main(string[] args)
    {
      DateTime now = new DateTime(2010, 12, 15, 14, 48, 30, DateTimeKind.Utc);
      long time_t = ToTime_T(now);
      Console.WriteLine("time_t = {0}", time_t);
      
      TimeSpan span = now - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
      long referenceTime = (long)span.TotalSeconds;
      Console.WriteLine("reference time_t = {0}", referenceTime);
    }
    
  } //class
} //namespace


* This source code was highlighted with Source Code Highlighter.


Замечу, что в программе есть места, которые можно несколько улучшить, в частности функцию ToDayInYear можно было бы написать и без использования цикла, но предпочту предоставить это читателю.

Перенаправление бинарного вывода процесса

В .Net для запуска одного процесса из под другого используется класс System.Diagnostics.Process.
Ниже приведен пример того, каким образом нужно перенаправлять БИНАРНЫЙ! вывод дочернего процесса.
В своем примере я запускаю утилиту "icat" из пакета "The Sleuth Kit", которая должна быть запущена от имени суперпользователя.

      ProcessStartInfo si = new ProcessStartInfo("icat", "<аргументы командной строки для утилиты icat>");
      si.UseShellExecute = false; //необходимо, чтобы получить возможность перенаправлять потоки
      si.RedirectStandardOutput = true; //сообщаем о том, что мы будем перенаправлять именно поток вывода
      si.UserName = "root"; //нужно, если собираетесь запускать вызываемую программу от имени другого пользователя
      si.Password = new System.Security.SecureString(); //ваш пароль, то же что и для UserName
      Process proc = new Process();
      proc.StartInfo = si;
      proc.Start();
      
      //proc.WaitForExit(); а вот этого делать нельзя!
      //WaitForExit можно вызывать только после полного считывания выходного потока
      
      using (var r = new BinaryReader(proc.StandardOutput.BaseStream))
      using (var w = new BinaryWriter(File.Create("<путь к фалу, куда направить output>")))
      {
        //копируем содержимое
      }

* This source code was highlighted with Source Code Highlighter.

пятница, 13 марта 2009 г.

Release/Debug References

В Visual Studio, если один проект, назовем его SomeExe, в солюшне ссылается на другой, назовем его SomeDll, то в режиме Release SomeExe будет использовать релизную версию dll-ки SomeDll, а в дебажной - дебажную. Но что если мы не хотим иметь Reference на проект, а хотим/можем ссылаться на dll? Как тогда сделать так, чтобы в релизе наш проект ссылался на релизную сборку, а в дебаге - на дебажную?
Могу предложить следующий, довольно удобный вариант:
Пусть у нас имеется некторый путь к папке. Создадим в ней две подпапки "Release" и "Debug" и положим в них релизную и дебажную версию dll-ки, как, собственно, и поступает студия по умолчанию. Далее добавляем в проект (SomeExe) Reference на одну из этих версий, предположим на дебажную. Сохраняемся.
Открываем файл проекта в текстовом редакторе и находим следующий тег:
<ItemGroup>
  <Reference Include="SomeDll, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
   <SpecificVersion>False</SpecificVersion>
   <HintPath>[некоторый путь]\Debug\SomeDll.dll</HintPath>
  </Reference>
</ItemGroup>

* This source code was highlighted with Source Code Highlighter.


Исправляем в пути слово Debug на студийный макрос $(ConfigurationName) и получаем нечто следующее:
<ItemGroup>
  <Reference Include="SomeDll, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
   <SpecificVersion>False</SpecificVersion>
   <HintPath>[некоторый путь]\$(ConfigurationName)\SomeDll.dll</HintPath>
  </Reference>
</ItemGroup>

* This source code was highlighted with Source Code Highlighter.

и это будет работать

Hello, world!

В этом блоге я буду писать на любые темы, относящиеся к программированию