среда, 14 ноября 2012 г.

Компоненты Visual Studio. Создание собственного редактора свойства

При написании своих собственных компонентов для Visual Studio часто возникает необходимость создать специальный редактор для свойств, значениями которых являются например объекты, неизвестный системе. Сделать свой собственный редактор свойства довольно просто.
Любой редактор свойства является классом унаследованным от UITypeEditor. Настройка поведения своего редактора свойства осуществляется путем переопределения предоставляемых этим классом виртуальных методов. UITypeEditor содержит следующие методы, предназначенные для переопределения:
  • EditValue – этот метод вызывается при начале редактирования свойства, в нем должен вызываться редактор, в результате возвращает полученное от редактора значение;
  • GetEditStyle – должен возвращать значение enum'а. Допускаются следующие значения:
    • None – никакой кнопки выводиться не будет;
    • Modal – кнопка в виде многоточия (типа "Обзор"), указывающая на то что при нажатии на нее откроется диалог для редактирования значения свойства:
    • DropDown – кнопка в виде выпадающего списка, указывающая на то что при нажатии на нее появится небольшая формочка для редактирования:
  • GetPaintValueSupported – указывает, нужно ли будет рисовать маленький прямоугольник, перед значением свойства  в PropertyGird'е:
  • PaintValue – вызывается при необходимости перерисовки маленькой картинки перед значением свойства в PropertyGird'е.
Теперь подробнее о том как работает этот механизм. В самом простом случае нужно переопределить два метода:
1) GetEditStyle – для задания вида редактора. Например если нужно получить выпадающую форму для редактирования, нужно сделать так:
public override UITypeEditorEditStyle GetEditStyle(
    ITypeDescriptorContext context)
{
    return UITypeEditorEditStyle.DropDown;
}
2) EditValue – для открытия редактора. Метод имеет следующую сигнатуру:
public Object EditValue(
 ITypeDescriptorContext context,
 IServiceProvider provider,
 object value)
где
  • context – не знаю для чего используется:-);
  • provider – обеспечивает объект интерфейса IWindowsFormsEditorService (путем вызова метода GetService(typeof(IWindowsFormsEditorService))). Этот объект предоставляет методы для отображения (DropDownControl) и закрытия (CloseDropDown) всплывающей формы редактора и метод ShowDialog для отображения диалога редактора.
  • value – текущее значение свойства.
Возвращает новое значение свойства, после изменения его в редакторе. Таким образом, переопределение метода EditValue должно выглядеть примерно так:
public override object EditValue(ITypeDescriptorContext context,
    IServiceProvider provider, object value)
{
    IWindowsFormsEditorService edSvc = (IWindowsFormsEditorService)
             provider.GetService(typeof(IWindowsFormsEditorService));
    if (edSvc != null)
    {
        MyEditorControl control = new MyEditorControl();
        control.Value = (MyEditableType)value;
        edSvc.DropDownControl(control);
        value = control.Value;
    }
    return value;
}
В коде можно увидеть использование неизвестного класса MyEditorControl. Этот класс и есть форма нашего редактора. Он является наследником класса UserControl (но может быть унаследован и от другого контрола). Реализация класса MyEditorControl должна иметь примерно следующий вид:
class MyEditorControl : UserControl {
    public MyEditableType Value {
        get; set;
    }
    //
}
Так как MyEditorControl является обычным контролом, вы можете пользоваться всеми средствами предоставляемыми Windows.Forms: создавать на нем кнопки, выпадающие списки, переопределять виртуальные методы (OnPaint, OnMouseDown, ...) и др.
Но это еще не все. В приведенном выше коде в качестве типа редактируемого свойства используется класс (или структура) MyEditableType. Этот тип является пользовательским и компонентная модель ничего не знает о нем. Для того чтобы объяснить компонентной модели как обращаться с этим типом, для него необходимо задать атрибут TypeConverterAttribute. В качестве параметра этот атрибут принимает тип класса, унаследованного от TypeConverter.

Исходный код в котором вы сможете увидеть применение редакторов свойств на практике можно найти здесь.

Игра на Java под Swing

Сейчас занимаюсь разработкой игры на Java, ориентируясь на Android, но ядро платформонезависимо. Поэтому для отладки использую Desktop версию. В Desktop версии графику реализовал через Swing. Для простоты в качестве элементов управления использую стандартные компонеты Swing. Для отрисовки графики испольую компонет Canvas.

Компонент Canvas
Унаследован от класса Component, специально предназначен для отрисовки на нем графики. На самом деле ничего особенного он из себя не представляет (убедиться можно посмотрев его исходный код). Он скорее своим названием декларирует что на нем будут рисовать. 
Класс предоставляет для переопределения метод paint(Graphics g), который выполняется при необходимости перерисовки.
Класс также предоставляет методы для использования двойной буфферизации (ДБ):  createBufferStrategy(int numBuffers) и getBufferStrategy().
Метод createBufferStrategy создает numBuffers буфферов. После его вызова можно использовать getBufferStrategy для получения объекта класса BufferStrategy, который предоставляет доступ к ДБ. Он имеет следующие важные методы:
  • getDrawGraphics() -- возвращает объект класса Graphics, на котором нужно рисовать.
  • show() -- отображает нарисованную в буфере картинку на экран (на компонент).
Т. е. для того чтобы отрисовать что-либо с использованием ДБ, нужно сделать следующее:
Canvas canv = new Canvas();
canv.createBufferStrategy(2);
Graphics g = canv.getDrawGraphics();
g.setColor(Color.RED);
g.drawRect(10, 10, 100, 100);
...
Цветофильтр
В игре используется цветовой фильтр для изменения цвета объектов (спрайтов). AWT предоставляет несколько способов для фильтрации (вродебы), я же использую класс RGBImageFilter. Он абстрактный и имеет абстрактный метод filterRGB(int x, int y, int rgb), который и нужно реализовать для наложения фильтра. Он должен возвращать цвет (int) пиксела в позиции (x, y). Параметр rgb -- исходный цвет пиксела. Чтобы применить этот фильтр к нужной картинке (Image), нужно сделать так:

FilteredImageSource filteredSrc = new FilteredImageSource(img.getSource(), filter);
Image filteredImage =  Toolkit.getDefaultToolkit().createImage(filteredSrc);

где, filter - это созданный ранее объект класса RGBImageFilter.

Доступ к ресурсам
Java позволяет хранить файлы различных типов непосредсвенно в jar файле. Для доступа к ним существует метод getClass().getResourceAsStream(name), где name -- путь к файлу ресурса. Путь включает /имя_файла.расширение. Имена пакетов разделяются символом /.
Например если вы поместили ваш файл empty.png в пакет com.yourname.resources.images, то путь к нему будет /com/yourname/resources/images/empty.png.
Метод возвращает поток для чтения (InputStream), далее с помощью ImageIO.read(inputStream) можно считать файл картинки в память.

Настройки пользователя
Для того чтобы хранить настройки и сохранения игры в папке пользователя создается подпапка ".название_гры". Получить путь к папке пользователя можно так:

System.getProperty("user.home");


Хранение данных
Данные храню в XML-файлах. Например карты хранятся в ресурсах в подкаталоге worlds. Их содержимое загружается в патмять с помощью сериализации. Для этого используется библиотека simple-xml. Она позволяет сериализировать и десериализировать объекты в стиле .NET (см. пост Сериализация в C#).

Кое-что
На Java я раньше не писал и игры не писал тоже (кроме простейшей игрухи на XNA). Поэтому в ходе работы я узнавал и понимал кое-что новое:
  • Картинки нужно кешировать. В игре используется разворот картинок, наложение фильтров и масштабирование. Все эти операции нагружают процессор. И когда я запустил игру (без кеширования), то заметил насколько сильно. 
  • Ну очевидно что нужно использовать ДБ, без нее никак.
  • При проектировании нужно максимально отделять логику игры от её графики.
  • Отрисовка графики тоже имеет логику) и нужно максимально отделить платформозависимую её часть от общей. Например класс Color является частью библиотеки AWT, и его не слудует использоват в ядре (движке) игры. Потом все равно придется переделывать чтобы заработало под Android.
Пожалуйста, оставляйте комментарии)

пятница, 14 сентября 2012 г.

mingw32-make.exe: Interrupt/Exception caught (code = 0xc00000fd, addr = 0x421963)

При сборке проекта в Qt Creater'е выскакивала вот эта ошибка:

mingw32-make.exe: Interrupt/Exception caught (code = 0xc00000fd, addr = 0x421963)

Долго искал в интернете решения проблемы. В 90% ответах ответов говорилось что это проблема связана с багом, в результате которого возникали проблемы, если в переменной окружения PATH содержались пути содержащие скобочки, и нужно перенести путь Qt'а перед всеми остальными путями и всё станет хорошо. Я так и сделал, но хорошо не стало.
Дальше я натолкнулся вот на этот пост http://antyadev.blogspot.com/2012/04/qt-creator-interruptexception-caught.html. Он и помог решить проблему из сабжа. Оказалось связана она с тем что у меня 64-ох разрядная Windows 7.

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

Не работает Sudo: <USER> is not in the sudoers file. This incident will be reported.

Чтобы исправть эту проблему нужно добавить пользователя в файл /etc/sudoers следующим образом:
echo '<USER> ALL=(ALL) ALL' >> /etc/sudoers

Видео в CentOS

Таже через некоторое время я заметил что видео в CentOS не воспроизводится. Выход нашел здесь http://chast.in/install-vlc-on-centos-5.html.

Chrome не устанавливается CentOS

Скачал rpm-пакет Google Chrome, а он при установки сообщает что версия lbs должна быть >= 4.0 и прекращает установку.
Погуглил. Оказалось что lbs не установлен: 

bash: lbs_release: command not found
 
Устанавливается lbs так:

yum install redhat-lsb

После этого Хром нормально установился.

CentOS не знает NTFS

У меня после установки CentOS 6.3 не позволял зайти на NTFS разделы. Погуглив нашел следующее решение:
  1. Скачать и установить: http://packages.sw.be/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.i686.rpm.
  2. Выполнить команду yum install fuse fuse-ntfs-3g.
После этого у меня стали доступны NTFS разделы.
Ссылки:

понедельник, 28 мая 2012 г.

PuTTY и кириллица

Для того чтобы в PuTTY правильно отображались кириллические символы нужно:
  • включить PuTTY;
  • зайти на вкладку Window->Translations;
  • вручную прописать кодировку CP866 в поле Remote character set;
  • сохранить настройки по необходимости и подключиться.
Работает в Windows XP.

И еще, де факто SSH сервером под Windows является OpenSSH.

воскресенье, 20 мая 2012 г.

Словарь программистских терминов

Ханитоп -- намеренно уязвимая машина, используемая для детектирования новых видов малвари. (Хакер, 160 выпуск)


LFI (Local File Include) -- уязвимость, позволяющая удаленному клиенту прочитать локальный файл на сервере. Если брать PHP, то уязвимость проявляется когда скрипт выполняет include файла, имя которого получает через GET или POST запрос. В этом случае, указав в запросе путь к нужному файлу (например ../../../../../etc/passwd) можно получит его содержимое в браузере.

RFI (Remote File Include) -- уязвимость, которая позволяет удаленному клиенту запустить удаленный скрипт на сервера.

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

Cygwin — UNIX-подобная среда и интерфейс командной строки для Microsoft Windows. (Википедия)

Что такое XXE-уязвимость

XXE (External Entity Expansion) -уязвимость -- это уязвленность сайта или программы к XML-инъекциям.

Благодаря возможностям предоставляемым DTD, существует возможность подгрузки внешнего XML-файлов при парсинге XML-данных.

С помощью этой уязвимости можно осуществлять DoS-атаки, если заставить парсер грузить огромный файл (например файл-подкачки C:\pagefile.sys). Также, если повезет, можно сливать файлы с сервера.

Описание уязвимости можно найти в 160-ом выпуске журнала ХАКЕР.

пятница, 18 мая 2012 г.

Двойная буферизация на C#. Дубль 2

О том, что такое двойная буферизация много написано здесь и здесь. Здесь можно почитать, как реализуется ДБ на Java. Я расскажу, как реализуется двойная буферизация на C#. Многое из того, что я тут написал можно прочитать в MSDN, но без деталей реализации, всё нужно будет принимать на веру.

Ручное управление двойной буферизацией (далее ДБ)

Для ручного управления двойной буферизацией, Framework .NET предоставляет следующие 3 класса:
  • BufferedGraphicsManager – предоставляет дефолтный BufferedGraphicsContext;
  • BufferedGraphicsContext - обеспечивает создание нового объекта BufferedGraphics;
  • BufferedGraphics – обеспечивает буфер для временного хранения графики и средства вывода её на полотно контрола.
Класс BufferedGraphicsManager служит для получения доступа (через статическое свойство Current) к объекту класса BufferedGraphicsContext ассоциированного с текущим доменом приложения (AppDomain). Так пишут в документации. По факту же Current возвращает созданный в статическом конструкторе класса BufferedGraphicsManager, объект класса BufferedGraphicsContext. Вот исходный код класса BufferedGraphicsManager:
public sealed class BufferedGraphicsManager
{
  private static BufferedGraphicsContext bufferedGraphicsContext;
  public static BufferedGraphicsContext Current
  {
    get
    {
      return BufferedGraphicsManager.bufferedGraphicsContext;
    }
  }
 
  static BufferedGraphicsManager()
  {
    AppDomain.CurrentDomain.ProcessExit += 
        new EventHandler(BufferedGraphicsManager.OnShutdown);
 
    AppDomain.CurrentDomain.DomainUnload +=
        new EventHandler(BufferedGraphicsManager.OnShutdown);
 
    BufferedGraphicsManager.bufferedGraphicsContext = 
        new BufferedGraphicsContext();
  }
 
  private static void OnShutdown(object sender, EventArgs e)
  {
    BufferedGraphicsManager.Current.Invalidate();
  }
}
Из этого кода видно, что хранящийся внутри BufferedGraphicsManager объект класса BufferedGraphicsContext уничтожается при выгрузке текущего AppDomain. BufferedGraphicsContext обеспечивает создание (а также уничтожение) нового экземпляра BufferedGraphics на основе объекта Graphics, предоставляя для этого единственный метод Allocate:
public BufferedGraphics Allocate(Graphics targetGraphics, Rectangle targetRectangle)
{
  if (this.ShouldUseTempManager(targetRectangle))
    return this.AllocBufferInTempManager(targetGraphics, IntPtr.Zero, targetRectangle);
  else
    return this.AllocBuffer(targetGraphics, IntPtr.Zero, targetRectangle);
}
Метод принимает в качестве параметра объект Graphics и область на нем, для которой нужно создать буфер. Если площадь этой области не превышает площадь MaximumBuffer (проверяется внутри метода ShouldUseTempManager*), вызывается метод AllocBuffer и возвращается полученный от него объект BufferedGraphics. Метод AllocBuffer создает внутри себя (с помощью метода CreateBuffer, описанного ниже), новый внеэкранный Graphics, оборачивает его в BufferedGraphics, сохраняет в переменную объекта buffer и возвращает её. Эта переменная используется для того, чтобы в дальнейшем при уничтожении экземпляра BufferedGraphicsContext (методом Dispose), уничтожить связанный с ним экземпляр BufferedGraphics. За создание внеэкранного (т.е. только в памяти) экземпляра Graphics отвечает метод CreateBuffer. Он с помощью нативной функции CreateDIBSection создает "аппаратно-независимый битмап" (DIB), на основе которого создает новый объект Graphics, и возвращает его в качестве результата. Если площадь переданной области превышает площадь MaximumBuffer, то вызывается метод AllocBufferInTempManager, исходный код которого приведен ниже:
private BufferedGraphics AllocBufferInTempManager(Graphics targetGraphics, IntPtr targetDC, Rectangle targetRectangle)
{
  BufferedGraphicsContext bufferedGraphicsContext = (BufferedGraphicsContext) null;
  BufferedGraphics bufferedGraphics = (BufferedGraphics) null;
  try
  {
    // Создаем новый "временный" контент
    bufferedGraphicsContext = new BufferedGraphicsContext();
    if (bufferedGraphicsContext != null) //  Для чего эта проверка? всегда ДА
    {
      // Создаем в нем буферизированное полотно (graphics), которое внутри себя (переменная context) хранит 
      // ссылку на него же
      bufferedGraphics = bufferedGraphicsContext.AllocBuffer(targetGraphics, targetDC, targetRectangle);
 
      // ВОТ, этот флаг указывает, что объект bufferedGraphics 
      // при своем уничтожении должен уничтожить породившего его
      // bufferedGraphicsContext:
      bufferedGraphics.DisposeContext = true; 
    }
  }
  finally
  {
    // В нормальной ситуации это условие не выполняется
    if (bufferedGraphicsContext != null
        && (bufferedGraphics == null
            || bufferedGraphics != null && !bufferedGraphics.DisposeContext))
      bufferedGraphicsContext.Dispose();
  }
  return bufferedGraphics;
}
Из этого кода видно, что внутри метода AllocBufferInTempManager создается новый экземпляр BufferedGraphicsContext, у которого вызывается метод AllocBuffer, а полученный от её BufferedGraphics возвращается в качестве результата. Причем, созданный временный объект BufferedGraphicsContext не уничтожается сразу же, а только при уничтожении созданного им BufferedGraphics. Для этого BufferedGraphics хранит обратную ссылку на своего создателя, а при уничтожении, если свойство DisposeContext равно true, забирает его с собой. Класс BufferedGraphics очень маленький. Его исходный код занимает чуть больше 100 строк. Он является простой оберткой над объектом Graphics, и предоставляет метод Render для копирования его на другой Graphics:
public void Render(Graphics target)
Копирование осуществляется нативной функцией BitBlt. Так как же использовать ДБ в ручном режиме? Об этом очень хорошо написано здесь. * исходный код метода ShouldUseTempManager:
private bool ShouldUseTempManager(Rectangle targetBounds)
{
  return targetBounds.Width * targetBounds.Height > this.MaximumBuffer.Width * this.MaximumBuffer.Height;
}

Автоматическая ДБ

Первый и простейший способ использовать ДБ для отрисовки контролаsource - это включить автоматическую ДБ для нужного контрола:
control.DoubleBuffered = true;
или
control.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
Рассмотрим что происходит с контролом, когда мы включаем для него автоматическую ДБ. Свойство DoubleBuffered, также как метод SetStyle, располагаются в классе System.Windows.Forms.Control. Заглянем в исходный этого класса. Код свойства DoubleBuffered выглядит так:
    protected virtual bool DoubleBuffered
    {
      get
      {
        return this.GetStyle(ControlStyles.OptimizedDoubleBuffer);
      }
      set
      {
        if (value != this.DoubleBuffered)
        {
          if (value)
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, value);
          else
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, value);
        }
      }
    }
Как видно из этого фрагмента кода, приведенные выше 2 способа включения ДБ ничем друг от друга не отличаются, за исключением того, что в сеттере DoubleBuffered устанавливается еще флаг ControlStyles.AllPaintingInWmPaint. Но так как этот флаг устанавливается и в конструкторе контрола*, то если вы его не сбрасывали вручную, оба этих способа имеют одинаковый эффект. Из исходного кода класса Control, так же можно увидеть что флаг ControlStyles.AllPaintingInWmPaint проверяется только внутри закрытого метода WmEraseBkgnd (а устанавливается только в конструкторе и сеттере свойства DoubleBuffered), вот его реализация:
    private void WmEraseBkgnd(ref Message m)
    {
      if (this.GetStyle(ControlStyles.UserPaint))
      {
        if (!this.GetStyle(ControlStyles.AllPaintingInWmPaint))
        {
          IntPtr wparam = m.WParam;
          if (wparam == IntPtr.Zero)
          {
            m.Result = (IntPtr) 0;
            return;
          }
          else
          {
            NativeMethods.RECT rect = new NativeMethods.RECT();
            UnsafeNativeMethods.GetClientRect(new HandleRef((object) this, this.Handle), out rect);
            using (PaintEventArgs e = new PaintEventArgs(wparam, Rectangle.FromLTRB(rect.left, rect.top, rect.right, rect.bottom)))
              this.PaintWithErrorHandling(e, (short) 1);
          }
        }
        m.Result = (IntPtr) 1;
      }
      else
        this.DefWndProc(ref m);
    }
Отсюда видно, что если флаг AllPaintingInWmPaint НЕ установлен, то при получении окном сообщения WM_ERASEBKGND**, происходит вызов метода PaintWithErrorHandling, с параметром layer равным 1, который в свою очередь вызывает перерисовку фона контрола, если не установлен флаг ControlStyles.Opaque***. Так же стоит рассмотреть флаг ControlStyles.UserPaint. Этот флаг указывает на то, что содержимое контрола будет отрисовываться средствами Framework.NET, а не средствами системы. Например, если вы зададите фоновую картинку для вашей формы, и сбросите флаг UserPaint, то картинка не будет отрисовываться. Вернемся теперь к ДБ. Основные действия по ДБ разворачиваются внутри метода WmPaint. Этот метод отвечает за обработку сообщения WM_PAINT, которое прилнетает когда какой-либо участок контрола нуждается в перерисовке. Метод WmPaint является закрытым и вызывается только из метода WndProc при условии, что установлен флаг ControlStyles.UserPaint:
protected virtual void WndProc(ref Message m)
{
  switch (m.Msg)
  {
    ...
    case WM_PAINT:
      if (this.GetStyle(ControlStyles.UserPaint))
      {
        this.WmPaint(ref m);
        break;
      }
      else
      {
        this.DefWndProc(ref m);
        break;
      }
    ...
  }
}
private void WmPaint(ref Message m)
{
  if (this.DoubleBuffered || this.GetStyle(ControlStyles.AllPaintingInWmPaint) && this.DoubleBufferingEnabled)
  {
    IntPtr num; // нативный хендл Graphics
    Rectangle rectangle; // перерисовываемый участок полотна

    // Инициализация num и rectangle...

    if (rectangle.Width > 0)
    {
      if (rectangle.Height > 0)
      {
        Rectangle clientRectangle = this.ClientRectangle;
        using (BufferedGraphics bufferedGraphics = BufferedGraphicsManager.Current.Allocate(num, clientRectangle))
        {
          Graphics graphics = bufferedGraphics.Graphics;
          graphics.SetClip(rectangle);
          System.Drawing.Drawing2D.GraphicsState gstate = graphics.Save();
          using (PaintEventArgs e = new PaintEventArgs(graphics, rectangle))
          {
            this.PaintWithErrorHandling(e, (short) 1, false);
            graphics.Restore(gstate);
            this.PaintWithErrorHandling(e, (short) 2, false);
            bufferedGraphics.Render();
          }
        }
      }
    }
    ...
  }
  else {
 // отрисовка без двойной буферизации...
  }
}
Как видно из приведенного выше фрагмента кода, для того чтобы графика отрисовывалась с ДБ нужно чтобы DoubleBuffered был равен true, или чтобы свойство DoubleBufferingEnabled****. Далее, всё вполне понятно из исходного кода. Как и ожидалось никаких чудес нету. Автоматическая ДБ происходит теми же средствами что и ручная.
private void PaintWithErrorHandling(PaintEventArgs e, short layer)
{
    ...
    switch (layer)
    {
      case (short) 1:
        if (!this.GetStyle(ControlStyles.Opaque))
        {
          this.OnPaintBackground(e);
          break;
        }
        else
          break;
      case (short) 2:
        this.OnPaint(e);
        break;
    }
    ...
}
* Фрагмент исходного кода конструктора класса Control в котором устанавливаются флаги ControlStyles:
    internal Control(bool autoInstallSyncContext)
    {
      ...
      this.SetStyle(ControlStyles.UserPaint | ControlStyles.StandardClick | ControlStyles.Selectable | ControlStyles.StandardDoubleClick | ControlStyles.AllPaintingInWmPaint | ControlStyles.UseTextForAccessibility, true);
      ...
    }
** Сообщение WM_ERASEBKGND приходит при изменении размера контрола. Фрагмент исходного кода, в котором обрабатывается сообщение WM_ERASEBKGND:
    protected virtual void WndProc(ref Message m)
    {
      switch (m.Msg)
      {
        ...
        case WM_ERASEBKGND:
          this.WmEraseBkgnd(ref m);
          break;
        ...
      }
    }
*** ControlStyles.Opaque – указывает .NET фреймворку что фон контрола не нужно отрисовывать. **** Закрытое свойство DoubleBufferingEnabled, имеет следующую реализацию:
    bool DoubleBufferingEnabled
    {
      private get
      {
        return this.GetStyle(ControlStyles.UserPaint | ControlStyles.DoubleBuffer);
      }
    }
Смысл этого свойства не очень понятен, так как метод WmPaint вызывается только при условии что установлен флаг ControlStyles.UserPaint, Поэтому DoubleBufferingEnabled здесь всегда будет true. А так как оно закрытое и нигде кроме WmPaint не проверяется, то для чего оно нужно?

четверг, 17 мая 2012 г.

Двойная буферизация в .NET

Существует два способа двойной буферизации для отрисовки WinForms контролов: автоматический и ручной.

Для того чтобы включить автоматическую двойную буферизацию, нужно всего лишь для контрола установить свойство DoubleBuffered = true или вызвать SetStyle(ControlStyles.OptimizedDoubleBuffer, true).

Для ручного же управления двойной буферизацией Framework .NET включает 3 класса:
BufferedGraphicsManager содержит одно статическое свойство Current, которое возвращает объект BufferedGraphicsContext для текущего домена приложения.* Для приложений активно использующих анимацию может быть эффективнее создать новый экземпляр BufferedGraphicsContext вместо того чтобы использовать BufferedGraphicsManager.Current. В этом случае по завершению работы с объектом, его необходимо уничтожить явным образом:
BufferedGraphicsContext myContext = new BufferedGraphicsContext();
// использование
myContext.Dispose();
или
using(BufferedGraphicsContext myContext = 
    new BufferedGraphicsContext()) {
// использование
}
BufferedGraphicsContext имеет один метод Allocate и одно свойство MaximumBuffer
Allocate(Graphics targetGraphics, Rectangle targetRectangle) -- создает новый BufferedGraphics на основе переданного через параметр targetGraphics размером targetRectangle.

Как работает метод Allocate

Исходный код метода:
    public BufferedGraphics Allocate(Graphics targetGraphics, Rectangle targetRectangle)
    {
      if (this.ShouldUseTempManager(targetRectangle))
        return this.AllocBufferInTempManager(targetGraphics, IntPtr.Zero, targetRectangle);
      else
        return this.AllocBuffer(targetGraphics, IntPtr.Zero, targetRectangle);
    }
Внутри метода происходит проверка MaximumBuffer и targetRectangle (методом ShouldUseTempManager**). Если требуемый размер полотна превышает заданный полем MaximumBuffer , то вызывается метод AllocBufferInTempManager. Этот метод внутри себя создает новый экземпляр класса BufferedGraphicsContext, вызывает у него метод AllocBuffer и возвращает полученный от него объект класса BufferedGraphics.***
Если же targetRectangle не превышает MaximumBuffer, то тогда временный объект не создаётся, а метод AllocBuffer вызывается непосредственно у текущего объекта (т.е. у самого себя).
Метод AllocBuffer внутри себя вызывает метод CreateBuffer, для создания нового экземпляра Graphics, оборачивает его в BufferedGraphics и сохраняет в переменную объекта buffer. Эта переменная используется для того, чтобы в дальнейшем при уничтожении экземпляра BufferedGraphicsContext (методом Dispose), уничтожить связанный с ним экземпляр BufferedGraphics.

Стоит заметить что объект BufferedGraphics также хранит в себе ссылку на создавший его BufferedGraphicsContext в закрытой переменной.

---------------------------------------------------------------


*  Код класса BufferedGraphicsManager выглядит следующим образом:
  public sealed class BufferedGraphicsManager
  {
    private static BufferedGraphicsContext bufferedGraphicsContext;
    public static BufferedGraphicsContext Current
    {
      get
      {
        return BufferedGraphicsManager.bufferedGraphicsContext;
      }
    }

    static BufferedGraphicsManager()
    {
      AppDomain.CurrentDomain.ProcessExit += 
          new EventHandler(BufferedGraphicsManager.OnShutdown);

      AppDomain.CurrentDomain.DomainUnload +=
          new EventHandler(BufferedGraphicsManager.OnShutdown);
 
      // В СТАТИЧЕСКОМ КОНСТРУКТОРЕ ПРОСТО СОЗДАЕТСЯ НОВЫЙ
      // НОВЫЙ ОБЪЕКТ BufferedGraphicsContext
      BufferedGraphicsManager.bufferedGraphicsContext = 
          new BufferedGraphicsContext();
    }

    private BufferedGraphicsManager()
    {
    }

    [PrePrepareMethod]
    private static void OnShutdown(object sender, EventArgs e)
    {
      BufferedGraphicsManager.Current.Invalidate();
    }
  }

** Исходный код метода ShouldUseTempManager():
    private bool ShouldUseTempManager(Rectangle targetBounds)
    {
      return targetBounds.Width * targetBounds.Height > this.MaximumBuffer.Width * this.MaximumBuffer.Height;
    }
*** При этом созданный внутри метода AllocBufferInTempManager экземпляр BufferedGraphicsContext не уничтожается, ответственность за его уничтожение ложится на пользователя получившего объект BufferedGraphics, который хранит обратную ссылку, при уничтожении которого и уничтожается породиший его объект BufferedGraphicsContext. Как это работает? Исходный код метода AllocBufferInTempManager:
    private BufferedGraphics AllocBufferInTempManager(Graphics targetGraphics, IntPtr targetDC, Rectangle targetRectangle)
    {
      BufferedGraphicsContext bufferedGraphicsContext = (BufferedGraphicsContext) null;
      BufferedGraphics bufferedGraphics = (BufferedGraphics) null;
      try
      {
        // Создаем новый "временный" контент
        bufferedGraphicsContext = new BufferedGraphicsContext();
        if (bufferedGraphicsContext != null) //  Всегда ДА
        {
          // Создаем в нем буферизированное полотно, которое внутри себя (переменная context) хранит 
          // ссылку на него же
          bufferedGraphics = bufferedGraphicsContext.AllocBuffer(targetGraphics, targetDC, targetRectangle);

          // ВОТ, этот флаг указывает, что объект bufferedGraphics 
          // при своем уничтожении должен уничтожить породившего его
          // bufferedGraphicsContext:
          bufferedGraphics.DisposeContext = true; 
        }
      }
      finally
      {
        // В нормальной ситуации это условие не выполняется
        if (bufferedGraphicsContext != null 
            && (bufferedGraphics == null 
                || bufferedGraphics != null && !bufferedGraphics.DisposeContext))
          bufferedGraphicsContext.Dispose();
      }
      return bufferedGraphics;
    }

вторник, 15 мая 2012 г.

Выход из .NET программы

В WinForms приложениях для завершения работы программы существует метод Application.Exit(). Для того чтобы немедленно завершить работу консольного приложения, нужно выполнить следующую команду:
Environment.Exit(0);
Метод Environment.Exit(int exitCode) является аналого сишной функции exit(int code).

воскресенье, 6 мая 2012 г.

Ввод строки с консоли в Java

Запрос ввода строки с консоли в Java выполняет следующий фрагмент кода:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String str = in.readLine();
В C# ввод строки с консоли реализуется так:
Console.ReadLine();

Запрос .NET программой прав администратора при запуске

Для того чтобы при запуске программы на C# выводился запрос на получение прав администратора нужно:
Добавить в проект Visual Studio файл монифеста Project->New Item->Application Manifest File
В добавившемся файле заменить строку:
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
на следующую:
<requestedExecutionLevel level="requireAdministrator" uiaccess="false" />
После этого рядом с иконкой программы появится картинка щита, указывающая что запуск программы требует прав администратора.

пятница, 4 мая 2012 г.

Apache + PHP + PostgreSQL

В Windows может быть недостаточно подключить к PHP расширение php_pgsql.dll для того чтобы коннект к базе работал. В случае если при попытке подключиться в PostgreSQL, PHP выдает одну из следующих ошибок:
  • Call to undefined function pg_connect();
  • PDOException: could not find driver,
убедитесь что правильно настроили расширение php_pgsql:
  • добавили или раскоменторовали строки extension=php_pgsql.dll и extension=php_dbo_pgsql.dll в файле php.ini;
  • перезагрузке веб-сервер.
Проверьте выводит ли информацию о расширении phpinfo(), если нет - расширение не загрузилось.
В этом случае в конфигурационный файл httpd.conf нужно добавить строку:
LoadFile "C:\Program Files\PostgreSQL\9.1\bin\libpq.dll"
и перезагрузить веб-сервер.

вторник, 10 апреля 2012 г.

C#: Чтение файла без блокировки

Для того чтобы прочиталь файл без блокировки его на запись и удаление другими процессами, нужно открыть его следующим образом:
FileStream stream = new FileStream(pathName, FileMode.Open, 
FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);

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

Массивы PHP и SplFixedArray

Оказывается, под любой элемент массива в PHP выделяется 144 байта. Почему, написано в этой статье http://habrahabr.ru/post/141093/. Для того чтобы сэкономить оперативную память при работе с большими массивами, можно использовать класс SplFixedArray. При использовании этого класса, можно экономить 88 байт на элемент (под каждый элемент выделяется всего 56 байт:).