четверг, 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 разруливается описанная ситуация и предусмотренна ли она вообще.