четверг, 21 октября 2010 г.

Многопоточное программирование. 2. Алгоритм Петерсона... чуть более подробно. ч.2. Случай N процессов

В прошлый раз был рассмотрен алгоритм Петерсона для взаимного исключения двух процессов. На этот раз рассмотрим алгоритм Петерсона, обощенный на N параллельно выполняющихся процессов.
Код алгоритма:
#define FALSE  0
#define N     10

int turn[N];
int stage[N + 1];

void enterRegion(int process)
{ 
  for (int i = 1; i <= N - 1; i++) // перебор стадий
  {
    stage[process] = i;
    turn[i] = process;
    for (int j = 1; j <= N; j++) // "опрос" остальных процессов
    {
      if (j == process)
        continue;
      while (stage[j] >= i  && turn[i] == process);
    }
  }
}

void leaveRegion(int process)
{
  stage[process] = FALSE;
}
Для простоты и улучшения читабельности кода не будем рассматривать процесс с индексом 0, так же не будем рассматривать стадию с индексом 0, точнее стадия с индексом 0 будет своеобразным "стартом".
А теперь немного теории.
Алгоритм Петерсона для N процессов по сути тот же самый, что и для случая двух процессов, однако перед входом в критическую процесс должен преодолеть (n-1) стадий, т.н. "состязания". Количество стадий равно (n-1), потому что на каждой стадии будет оставаться хотя бы один процесс, а остальные будут переходить на следующую стадию, т.е. не более (n-j) процессов могут покинуть стадию под номером j. В конце концов у нас останется только два процесса, один из которых пройдет в критическую секцию. Два процесса будут как раз на (n-1)-й стадии. Преодоление каждой стадии напоминает версию для двух процессов, где один из процессов временно устраняется.
stage[process] - номер стадии, в которой находится процесс с идентификатором process
turn[i] = process - идентификатор процесса process, зашедшего последним в стадию под номером i

Рассмотрим поведение процессов на рисунке.

Пусть поочередно все 4 процесса попали на первую стадию. Пусть последним оказался процесс 1.
Происходит "опрос" остальных процессов на наличие их на той же самой стадии, где находится работающий процесс, либо на стадиях с бОльшим номером. Пусть вследствие прерываний до цикла, где происходит опрос, добрался процесс 2. В этом случае условие
while (stage[j] >= i  && turn[i] == process)
не выполнится.
Несмотря на то, что на данной стадии кроме процесса 2 имеются еще процессы, процесс 2 НЕ является последним перешедшим на данную стадию (мы условились, что последним будет процесс 1). Стало быть процесс 2 проходит на следующую стадию. И если так случится, что прерываний не будет, и его никто не "догонит", то процесс 2 благополучно войдет в критическую секцию, затем выйдет из нее, присвоит переменной interested[process] значение FALSE, что будет означать что данный процесс находится на стадии 0, т.е. на "старте", ну или на самой "низшей" стадии.
Если же переключению процессов случаются примерно через равные промежутки времени, то всегда для последнего процесса, зашедшего на данную стадию, условие
while (stage[j] >= i  && turn[i] == process)
не будет выполняться по той простой причине, что он последний: (turn[i] == process).
Таким образом на последующую стадию переходит на один процесс меньше, и так пока не останется двух процессов, после чего один из двух войдет в критическую секцию. Второй из двух войдет в критическую секцию после выхода первого, потому что (stage[j] >= i) будет равно FALSE. А если второго процесса кто либо "догонит", тем не менее (turn[i] == process) будет равно FALSE, и процесс все равно войдет в критическую секцию.

суббота, 2 октября 2010 г.

Многопоточное программирование. 1. Алгоритм Петерсона... чуть более подробно


Разберем сегодня алгоритм Петерсона. Посвящается всем начинающим, продолжающим,
прогулявшим пары по операционным системам и тем, до кого не дошло сразу. Алгоритм
Петерсона позволяет добиться взаимного исключения двух процессов. Рассмотрим 3 варианта поведения двух процессов при возникновении прерываний в трех различных точках. Также в каждом из рассмотренных случаев будем допускать прерывание при первом входе в критическую секциию одного из процессов
#define FALSE 0
#define TRUE 1
#define N 2 // количество процессов

int turn; // чья очередь
int interested[N];   // изначально значения равны 0

void enter_region(int process) // номер процесса - 0 или 1
{
  int other;
  other = 1 - process;    // точка 1
  interested[process] = TRUE;  // точка 2 (заинтересованный процесс)
  turn = process;         // точка 3
  while(turn == process && interested[other] == TRUE); // активное ожидание
}

void leave_region(int process) // номер процесса - 0 или 1
{
  interested[process] = 0; // признак выхода из критической секции
}
Итак, turn и interested[] являются глобальными переменными, прочие
переменные - локальные.
Случай 1: переключение процессов возникло в точке 1
Процесс 0 Процесс 1
other = 1
Прерывание процесса 0, переключение на процесс 1

other = 0, interested[1] = TRUE, turn = 1
Проверка условий turn == 1 (true) и interested[0] == TRUE (false)
Вход в критическую секцию
Прерывание процесса 1, переключение на процесс 0
interested[0] = TRUE, turn = 0
Проверка условий turn == 0 (true) и interested[1] == TRUE (true)
Вход в бесконечный цикл, переключение на процесс 1
Процесс 1 выходит из критической секции и устанавливает interested[1] = FALSE
Прерывание процесса 1, переключение на процесс 0
Проверка условий turn == 0 (true) и interested[1] == TRUE (false)
Вход в критическую секцию


Случай 2: переключение процессов возникло в точке 2
Процесс 0 Процесс 1
other = 1, interested[0] = TRUE
Прерывание процесса 0, переключение на процесс 1
other = 0, interested[1] = 1, turn = 1
Проверка условий turn == 1 (true) и interested[0] == TRUE (true)
Вход в бесконечный цикл, переключение на процесс 0
turn = 0
Проверка условий turn == 0 (true) и interested[1] == 1 (true)
Вход в бесконечный цикл, преключение на процесс 1

Проверка условий turn == 1 (false) и interested[0] == 1 (true)
Вход в критическую секцию
Прерывание процесса 1, переключение на процесс 0
Проверка условий turn == 0 (true) и interested[1] == 1 (true)
Вход в бесконечный цикл, преключение на процесс 1

Процесс 1 выходит из критической секции и устанавливает interested[1] = FALSE
Прерывание процесса 1, переключение на процесс 0
Проверка условий turn == 0 (true) и interested[1] == 1 (false)
Вход в критическую секцию

Случай 3: переключение процессов возникло в точке 3
Процесс 0 Процесс 1

other = 1, interested[0] = TRUE, turn = 0
Прерывание процесса 0, переключение на процесс
other = 0, interested[1] = TRUE, turn = 1
Проверка условий turn == 1 (true) и interested[0] == TRUE (true)
Вход в бесконечный цикл, преключение на процесс 0
Проверка условий turn == 0 (false) и interested[1] == TRUE (true)
Вход в критическую секцию
Прерывание процесса 0, переключение на процесс
Проверка условий turn == 1 (true) and interested[0] == TRUE (true)
Вход в бесконечный цикл, преключение на процесс 0
Процесс 0 выходит из критической секции и устанавливает interested[0] = FALSE
Прерывание процесса 0, переключение на процесс
Проверка условий turn == 1 (true) и interested[0] == TRUE (false)
Вход в критическую секцию


Продолжение следует...

среда, 29 сентября 2010 г.

Особенности выравнивания данных в структурах


Начинающим да и продолжающим программистам порой непонятно, почему в некоторых случаях размер структуры может быть больше, чем суммарный размер входящих в нее членов. Например:
struct Test
{
 char var1; // 1 байт
 short var2; // 2 байта
};
Как видно, суммарный размер структуры должен быть равен 3 байтам, на деле же оказывается вовсе не 3, а 4 байта.
Именно такой результат возвратит нам sizeof(Test). Все дело в выравнивание, которое осуществляет компилятор.
Т.е. данные помещаются по адресам, кратным размерам своих типов - 2-байтовые данные буду находятся по адресу, кратному 2, 4-байтные - по адресу, кратному 4 и т.д. (выравнивание по умолчанию)
Выравнивание нужно, потому что с выровненными данными процессор работает более эффективно. Если процессор обращается к невыровненным данным, то сначала он считывает одну часть данных, затем считывает другую часть, после этого компонует и получает исходные данные. Все это сокращает производительность.
Для выравнивания между данными помещаются специальные байты отступа (padding)

Рассмотрим примеры (ОС Windows).
struct Test
{
    unsigned char var1;
    unsigned char var2;
    unsigned char var3;
};
012345678
charcharchar...
В данном примере все просто. 3 однобайтовых данных выравниваются по однобайтовой границе.
В итоге суммарный размер равен 3 байта.

Другой пример:
struct Test
{
    unsigned char var1;
    unsigned short var2;
};
012345678
charPshort...
Здесь данные типа short будут выровнены по 2-байтовой границе, для этого будет использован отступ в 1 байт. Поменяем местами данные следующим образом:
struct Test
{
    unsigned short var1;
    unsigned char var2;
};
012345678
shortcharP...
В данном примере байт отступа будет использоваться для дополнения последнего члена структуры до размера, кратного максимальному размеру члена, т.е. до 2 байт. Таким образом, 1 байт на char + 1 байт на отступ = 2 байта (short)
Допустим у нас есть такая структура:
struct Test
{
    unsigned int var1;
    unsigned char var2;
};
012345678910
intcharPPP...
В данном случае в структуре присутствуют данные с максимальным размером 4 байта (int), следовательно последний член структуры необходимо дополнить до ближайшего кратного 4 числа, большего 1 (т.к. char), т.е. до 4 байтов. Для этого потребуется 3 байта для отступа.

Если имеется структура
struct Test
{
    unsigned int var1;
    unsigned short var2;
    unsigned char var3;
};

012345678910
int short char P ...
В данном случае член типа short не дополняется до размера int, потому что следом идет член типа char, который не требует выравнивания по 4-байтной границе. А после этого члена для выравнивания до 4 байт (число, кратное максимальному размеру типа в данной структуре (int) - 4 байта)

Аналогично рассмотрим структуру вида
struct Test
{
 double var1;
 unsigned int var2;
 unsigned short var3;
 unsigned char var4;
};
012345678910111213 14151617
double int short char P ...
Член типа double занимает как есть 8 байт, далее 4 байта на int, после которого байты отступа не требуются, т.к. граница выровнена уже по 4 байтам, а идущий следом член типа short требует всего лишь 2-байтового выравнивания. Затем один байт на char и байт отступа для выравнивания по числу, кратному размеру максимального типа (double), т.е. 16.

Представим такую ситуацию:
struct Test
{
    unsigned short var1;
    unsigned char var2;
    double var3;
};
012345678910 1112131415161718
short char PPPPP double ...
В данном примере данное типа char дополняется 1 байтом отступа до размера short, но далее идет член типа double, его рамер равен 8 байтами, поэтому он должен выравниваться по 8-байтовой границе. Следовательно отступ увеличивается еще на 4 недостающих байта.

ВНИМАНИЕ! Данные результаты получены при опции компилятора
Struct member alignment = Default;
Для значений /zp1, /zp2, zp[n] результаты будут отличны от полученных.

вторник, 10 августа 2010 г.

Array name != pointer to the first array element?

from the standard (4.2.1):
An lvalue or rvalue of type 'array of N T' or 'array of unknown bound of T' can be converted to an rvalue of type 'pointer to T'. The result is a pointer to the first element of the array.
I.e.:
An Array variable, defined as
T ar[N];  //array of N T
or
T ar[] = {val1, val2, val3}; //array of unknown bound of T
can be converted to an rvalue of type 'pointer to T'

Let's assume we have an array:
int ar[10];
if we want to pass it to a function
void Foo(int* ar);
we should write next:
Foo(ar); // ar automatically converted to a pointer to the 1st element
Or
int ar[10];
int* p = ar;
*p = 10; // equals ar[0] = 10;
p+=2;
*p = 20; // equals ar[2] = 20;
We can also write the next:
int (*p)[10];
Here p is a pointer to array of 10 int's (type is int (*)[10]). Exactly "pointer to array"!
In this case the expression
p = ar;
won't compile. In the declaration
int (*p)[10];
p is already a pointer(!),
thus if ar[10] is an array, and ar is a pointer, then if (*p)[10] is a pointer,
and p is a pointer to pointer
So we should write the next:
p = &ar;
Despite ar and &ar have the same meaning.
since p is associated with 10 int's array, then while incrementing
p will change to sizeof(int), rather than 10*sizeof(int).
int ar[3][4];
for(int i=0; i < 3; i++)
{
    for (int j=0; j < 4; j++)
    {
        ar[i][j]=i+j;
    }
} 
int (*p)[4];
p=ar;
++p; //advance to the next row
int a_2_1 = *(*(p+2)+1) // equals int a_2_1 = p[2][1]

четверг, 10 июня 2010 г.

Include guards is not all-healing

Once upon a time a got a linker error:
//файл testing.h
#ifndef TESTING_H
#define TESTING_H
class MyClass
{
private:
    int classData;
public:
    MyClass();
    ~MyClass();
};

namespace TestNS
{
    int namespaceData;
};
#endif
//файл testing.cpp
#include "Testing.h"

MyClass::MyClass()
{
 //........
}

MyClass::~MyClass()
{
 //........
};
//файл mainfile.cpp
#include "Testing.h"
int main()
{
    TestNS::namespaceData = 0;
    return 0;
}

(MSVC2008):
Error 1 error LNK2005: "int TestNS::namespaceData" (?namespaceData@TestNS@@3HA) already defined in Testing.obj mainfile.obj
Error 2 fatal error LNK1169: one or more multiply defined symbols found E:\test.exe
It seems strange at first glance since I use include guards, and all should be OK.
But they are intended to only avoid the problem of double inclusion.
The error occurs when the linker finds two matches that collide.

In our case there are two object files - mainfile.obj and testing.obj.
Each of them has TestNS::namespaceData. A solution is not to define the variable in the header file. It´s needed to be defined in the source file and only declared in the header fileby use extern directive

//file testing.h
namespace TestNS
{
    extern int namespaceData;
};
//file testing.cpp
namespace TestNS
{
 int namespaceData;
};
Now it works Another way to get rid of the error is to declare the variable as constant
//file testing.h
namespace TestNS
{
 const int namespaceData;
};

суббота, 29 мая 2010 г.

C++ и Static

Рассмотрим употребление ключевого static в языке С++. Думаю кому-то это сообщение будет полезно.
Рассмотрим следующие обасти видимости, имеющиеся в С++:
Local scope (область видимости - блок)
Function scope (область видимости - функция)
File scope (область видимости - файл)
Class scope (область видимости - класс/структура)

Начнем с самого простого.
Допустим в блоке находится локальная статическая переменная:
while(true)
{
    static int i=0;
    i++;
}
В этом случае переменная i будет создана лишь раз, и значение ее будет храниться втчение времени выполнения программы, несмотря на выход за пределы области видимости данного блока.

Аналогично дело обстоит с функциями:
void Foo()
{
    static int i=0;
    i++;
}
В данном случае значение переменной i будет сохраняться между вызовами Foo().
В обоих приведенных примерах статическая переменная видна только в пределах блока, в котором она определена.
Нам ничто не мешает создать глобальную статическую переменную, которая будет видна повсюду в файле и только в файле, в котором она определена, и никаким extern`ом к ней из другого файла не обратиться:
// File: example.cpp

// Global static local to this file:
   static int i;
Рассмотрим классы.
Классы, как известно, могут содрежать данные и методы, данные и методы могут быть статическими. Статические члены в большинстве случаев ведут себя так же как и нестатические. Они так же подчиняются модификаторам доступа (public, private, protected). Они содержатся только в области видимости данного конкретного класса. Доступ к данным осуществляется посредством операторов '.' или '->'. Но, в отличие от нестатических данных, к статическим можно обращаться с помощью оператора '::', используя имя класса. И для этого совсем необязательно, чтобы объект данного класса был создан.

Статические данные разделяются между всеми экземплярами класса. Они действуют подобно глобальным данным для класса.
Статические данные класса находят множество применений. Допустим, подсчет количества экземпляров данного класса:
class A
{
    static int i;
    public:
    A(){i++}
}
Паттерн одиночка(singleton) так же использует статическую переменную для того, чтобы гарантировать, что класс будет создан единожды при первом обращении.

Однажды созданная статическая переменная существуе на протяжении всего времени работы программы. Все статические переменные разрушаются (вызываются их деструкторы)при возврате из main() во время вызова atexit(). Деструкторы статической переменной вызываются только если переменная была создана и инициализирована. Это означает, что статический локальный объект в области видимости функции не нуждается в разрушении, если эта функция не была вызвана. В данном примере использование локальной статической переменной в синглтоне избавляет от проблемы утечки ресурсов, поскольку объект разрушается при выходе из программы.
class Singleton
{
    private:
    Singleton(){}
    public:
    static Singleton& Instance()
    {
        static Singleton inst;
        return inst;
    }
};
Данная разновидность синглтона называется синглтон Мейерса (подробнее Александреску)

Опишем класс со статической переменной-членом:
// File: example.h
   class A
   {
   public:
      A();
      static int i;
   };
Но объявление статической переменной внутри определения класса не является ее определением. Определение переменной должно присутствовать в области видимости того пространства имен, в котором заключено определение класса данной статической переменной. Поэтому код следует дополнить следующим образом:
class A
   {
   public:
      A();
      static int i;
   };
   int A::i = 99;
Если не инициализировтаь значение статической переменной, то по умолчанию будет присвоено значение 0.
Можно проводить инициализацию с помощью выражения (в данном случае, с помощью функции, возвращающей значение)
// File: example.cpp
   int A::i = GetValue();
Допускается инициализация внутри определения класса для переменных интегрального типа: int, char, long, bool (ISO Standard, 9.4.2), а также типов из пространства std, таких как std::float_round_style и т.д.
Статические переменные-члены не могут содержаться в локальных класса, а так же они не могут быть mutable.

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

Статические методы чаще всего используются для манипуляций со статическими данными класса:
// File: example.h
   class A
   {
   public:
      A();
      static int GetValue () { return val; }
   private:
      static int val;
   };
Статические методы также могут быть доступны посредством операторов '.' и '->' по имени объекта, либо по имени класса с помощью оператора '::'.
Статические методы не могут быть виртуальными. Не может присутствовать одновременно статический и нестатический методы с одинаковыми типами параметров. Так же статические методы не могут быть помечены модификаторами const, volatile и const volatile. Локальные классы не могут содержать статических членов

Всё...
Не совсем наверно всё. Остались моменты, которые я хотел бы обсудить с кем-либо, но ... не сейчас

воскресенье, 23 мая 2010 г.

Qt и условная сборка qmake

Попытаюсь сэкономить кому-нибудь время,силы и нервы.
В одном из Qt проектов (.lib) нужно было сделать так, чтобы при сборке в режиме debug сгенерированная dll помещалась в дерикторию debug, а при сборке в режиме release - в директорию release.
Решение состояло в том, чтобы прописать в pro-файле следующее:
CONFIG += designer plugin debug
TEMPLATE = lib
TARGET = $$qtLibraryTarget($$TARGET)
.....................

debug {
DLLDESTDIR += ../MyApp/debug
message(1)

}
release {
DLLDESTDIR += ../MyApp/release
message(2)
}
Но почему-то сгенерированная dll помещалась всегда в обе папке, независимо от режима сборки. И на консоль выдавались оба сообщения - message(1) и message(2)

Дело было в том, что переменная CONFIG уже содержала в себе значения и debug, и release.
Результат выполнения message($$CONFIG) такой:
Project MESSAGE: lex yacc warn_on debug uic resources rtti_off exceptions_off stl_off incremental_off thread_off windows release ReleaseBuild Release build_pass qt warn_on release incremental flat link_prl precompile_header autogen_precompile_source copy_dir_files debug_and_release debug_and_release_target embed_manifest_dll embed_manifest_exe debug shared stl exceptions rtti mmx 3dnow sse sse2 def_files release ReleaseBuild Release build_pass designer plugin debug
Решение состоит в использовании функции CONFIG и выглядит следующим образом:
CONFIG( debug, debug|release ) {
DLLDESTDIR += ../MyApp/debug
message(1)
} else {
DLLDESTDIR += ../MyApp/release
message(2)
}
Теперь будет выполняться только единственная ветвь в зависимости от режима сборки.

понедельник, 17 мая 2010 г.

C++ и указатели на...

С++ необъятен. На нем можно порой такие конструкции написать, что не сразу поймешь в чем тут дело. Хотелось затронуть тему указателей. Опытные программисты знают подобные конструкции, поэтому будет больше полезно для сведения (но не для использования) начинающим и продолжающим.

Начнем от простого к сложному.
char *ch; // указатель на символьный тип
int (*arrayPointer)[10];    // указатель на массив из 10-ти целых.
float *pointersArray[10];      // массив из 10-ти указателей на float.

int Foo();         // Функция, возвращающая целое.
double *Foo();  // Функция, возвращающая указатель на double .
int (*Foo())[10];  // Функция, возвращающая указатель на массив из 10-ти целых.

int (*ptrFoo)();      // Указатель на функцию, возвращающую целое.
float *(*ptrFoo)();   // Указатель  на функцию, возвращающую  указатель на float .
char (*ptrFoo[10])(); // Массив из 10-ти указателей на функции, возвращающие char.

double (*Foo())(); // Функция, возвращающая указатель на функцию, возвращающую double
Из приведенных прототипов можно понять правило формирования указателей на что-либо.

Cоздадим указатель на функцию, возвращающую указатель на массив из 10-ти указателей на функции, принимающие в качестве параметра указатель на float, и возвращающие указатель на double.

Сперва создадим указатель:
*Foo; // Или обозначим это буквой A
Сделаем его указателем на функцию:
(*Foo)(); // Или (A)(), данное выражение обозначим как B
Сделаем чтобы данная функция возвращала просто указатель, пока ни на что:
*(*Foo)(); // Или *B, данное выражение обозначим как С
Теперь уточним, что требуется указатель на массив из 10-ти элементов:
(*(*Foo)())[10]; // или (C)[10]
Уточним что элементы массива должны быть указателями:
*(*(*Foo)())[10]; // или *(C)[10], данное выражение обозначим как D
И не просто указателями, а указателями на функции:
(*(*(*Foo)())[10])(); // или (D)()
Притом, функции эти должны возвращать double-значение и принимать float*:
double (*(*(*Foo)())[10])(float*); // или double (D)(float*)
Если же мы хотим, чтобы вместо double-значения возвращался указатель на функцию, возвращающую double-значние, просто добавим скобок:
/* вместо double* - дополнительно (* и  //соотвественно, закрывающуюся скобку + '()' */
double (* (*(*(*Foo)())[10])(float*) )();

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

воскресенье, 28 марта 2010 г.

QLabel & QPainter. Рисование на картинке

Случилась как-то задача. Нужно было загружать картинку и потом рисовать на ней всякой разное мышкой.
В роли виджета для загрузки картинки я сделал унаследованный от QLabel виджет с переопределенным событием
void RenderingLabel::paintEvent(QPaintEvent* pe){}
Мышка отлично рисовала, но проблема была в том, что фоновая картинка не отображалась. Как позже выяснилось, нужно было вызвать базовое событие paintEvent. Т.е. все должно выглядеть вот так:
void RenderingLabel::paintEvent(QPaintEvent* pe)
{
 QLabel::paintEvent(pe); // базовое событие
 QPainter painter(this);
        // А тут рисуем что хотим
}
Вот и все дела)

воскресенье, 7 марта 2010 г.

Создание собственных виджетов с интеграцией в Qt Designer (Custom widget plugin)

В данном посте я создам виджет,а именно, в нем будет присутствовать QListWidget, кнопки перемещения элемента списка на позицию вверх и вниз, кнопка удаления. Так же в нем присутствует метод привязки данных и добавления отдельного элемента (QString). Такой элемент управления мне понадобился на одном из проектов, причем в нескольких местах, поэтому, я решил оформить его именно как отдельный элемент управления (User Control в .NET). Я не буду приводить код релизации виджета. Вместо этого сконцентрируемся на написании кода, необходимого для оформления виджета как плагина и успешной интеграции в Qt Designer и Qt Creator

Для начала создадим Qt GUI проект. Лично я сначала делаю это в Qt Creator, а потом генерирую проект для Visual Studio 2008 с помощью интегратора.
Поместим на форму QListWidget, три QPushButton, QLabel и скомпилируем и запустим проект. Видим, что все, что мы поместили на форму, отобразилось так, как мы и хотели. Теперь надо оформить все это в плагин.

В Qt Creator выбираем проект пользовательского виджета (в VS2008 - Qt4 Designer plugin).
Допустим в прошлом проекте у нас класс назывался ExtendedListWidget. В проекте плагина укажем такое же имя класса. Получим 4 файла
  • ExtendedListWidget.h
  • ExtendedListWidget.cpp
  • ExtendedListWidgetplugin.h
  • ExtendedListWidgetplugin.cpp

Содержимое *.pro-файла должно быть примерно таким:
CONFIG += designer plugin debug_and_release
TARGET = $$qtLibraryTarget($$TARGET)
TEMPLATE = lib
QT += svg
QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/designer
HEADERS = extendedlistwidgetplugin.h \
ui_extendedlistwidget.h \
extendedlistwidget.h

SOURCES = extendedlistwidgetplugin.cpp \
extendedlistwidget.cpp
RESOURCES = icons.qrc

# install
target.path = $$[QT_INSTALL_PLUGINS]/designer
sources.files = $$SOURCES $$HEADERS *.pro
sources.path = $$[QT_INSTALL_EXAMPLES]/designer/extendedlistwidgetplugin
INSTALLS += target sources

Заменяем ExtendedListWidget.h и ExtendedListWidget.cpp на одноименные файлы из предыдущего проекта.

Чтобы избавиться от сообщений типа
warning C4273: 'staticMetaObject' : inconsistent dll linkage
в заголовке ExtendedListWidget.h пишем макрос перед именем класса

#include<qtdesigner/qdesignerexportwidget> 
class QDESIGNER_WIDGET_EXPORT ExtendedListWidget: public QWidget
{
//......
}
Сами заголовочные файлы добавлять в файл проекта не надо, иначе рискуем получить что-то типа
path/to/program/release/moc_analogclock.cpp:40: error: ExtendedListWidget::staticMetaObject' : definition of dllimport static data member not allowed
Заголовочные файлы нужно просто поместить туда, где они смогут быть найденными препроцессором. Например, рядом с исходниками плагина.
Запускаем, получаем ExtendedListWidgetplugin.dll (нужна release), потому как Qt Creator и Qt Designer собраны (как правило) в release mode.
  • Помещаем полученную библиотеку в $(QTDIR)\..\bin\designer (для Qt Creator)
  • В $(QTDIR)\plugins\designer помещаем libExtendedListWidgetplugin.dll и libExtendedListWidgetplugin.lib (для Qt Designer)
  • А также помещаем libExtendedListWidgetplugin.dll рядом в программой, использующий данный плагин

понедельник, 1 марта 2010 г.

Ogre3D. Сборка из исходников под Microsoft Visual Studio 2008

Сегодня будем собирать Ogre из исходников. Можно конечно скачать уже готовые сборки, но из исходников-то интереснее, да и при разработке можно посмотреть реализацию то или иного метода, чтобы лучше понять механизм работы без документации.

  1. Качаем и устанавливаем DirectX, если не установлен.
  2. Качаем библиотеки для Ogre
  3. Качаем сам Ogre, либо можно скачать из SVN
  4. Качаем CMake
  5. Извлекаем папку ogre. В моем случае - это C:\Libs\cpp\ogre
  6. Извлекаем библиотеки из архива (Dependencies) и помещаем в папку ogre. Получится C:\Libs\cpp\ogre\Dependencies
  7. Переходим в C:\Libs\cpp\ogre\Dependencies\src, запускаем OgreDependencies.VS2008.sln и собираем зависимости
  8. Пропишем пути к библиотекам (Tools->Options->Projects and Solutions->VC++ Directories).

    Show directories for:
    Include files:
    C:\Libs\cpp\ogre\Dependencies\include
    C:\Libs\cpp\ogre\Samples\Common\include
    C:\Libs\cpp\ogre\OgreMain\include
    $(DXSDK_DIR)\Include (где DXSDK_DIR у меня - C:\Libs\cpp\DirectXSDK\)
    Library files:
    C:\Libs\cpp\ogre\Dependencies\lib\Debug
    C:\Libs\cpp\ogre\Dependencies\lib\Release
    $(DXSDK_DIR)\Lib
    Source files:
    C:\Libs\cpp\ogre\OgreMain\src
  9. Запускаем установленную CMake и прописываем следующее:
    Where is the source code: C:/Libs/cpp/ogre
    Where to build the binaries: C:/Libs/cpp/ogre/VCBuild (папку VCBuild создавать не надо)

  10. Жмем "Configure", выбираем свой компилятор (MSVS2008 в моем случае), соглашаемся на создание папки, отмечаем необоходимые опции создания, жмем "Generate", ждем, пока создастся решение с проектами.
  11. После создания заходим в C:\Libs\cpp\ogre\VCBuild, запускаем OGRE.sln. Собираем ogre. У меня сборка заняла приблизительно 20 минут.
  12. Все! Поздравляю, ogre собран.

воскресенье, 17 января 2010 г.

Некоторые особенности QScrollArea

Столкнулся с таким интересным виджетом как QScrollArea. С помощью него можно отображать прокручивающееся содержимое, допустим, картинку или текст, или список виджетов.

Для того, чтобы вовнутрь QScrollArea добавить другой виджет нужно сделать примерно следующее:
// Заранее созданная в setupUi() scrollArea
QLabel *l1 = new QLabel("label1"); 
scrollArea->setWidget(l1);
Метод QScrollArea::setWidget устанавливает наш лейбл l1 в качестве дочернего для scrollArea.

А что, если мы хотим добавить пару лейблов вместо одного.

Вот такой код уже работать правильно не будет - появится только последний лейбл:
QLabel *l1 = new QLabel("label1");
QLabel *l2 = new QLabel("label2"); 
scrollArea->setWidget(l1); 
scrollArea->setWidget(l2);

Для того, чтобы получить два лейбла, нужно использовать слои:
QLabel *l1 = new QLabel("label1"); 
QLabel *l2 = new QLabel("label2");
QVBoxLayout *labelLayout= new QVBoxLayout(scrollArea);
labelLayout->addWidget(l1);
labelLayout->addWidget(l2);
Теперь лейблы будут размещены один под другим.
Либо просто назначить лейблы дочерними по отношению к QScrollArea
QLabel *l1 = new QLabel(scrollArea);
QLabel *l2 = new QLabel(scrollArea);
l1->setText("label1");
l2->setText("label2");
Можно сделать еще и следующим образом:
QLabel *l1 = new QLabel("label1");
QLabel *l2 = new QLabel("label2");
QLabel *l3 = new QLabel("label3");
QVBoxLayout *layout = new QVBoxLayout(scrollArea);
layout ->addWidget(l2);
layout ->addWidget(l3);
ui->scrollArea->setWidget(l1);
Получится 2 лейбла на слое и один не на слое.
Раньше был такой баг - если написать
ui->scrollArea_2->setWidget(l1);
QVBoxLayout *l = new QVBoxLayout(scrollArea);
l->addWidget(l2);
l->addWidget(l3);
то два последних лейбла не отобразятся. Я использую версию Qt4.6, и здесь такого бага уже не наблюдается.

Интересно использование виджета, унаследованного от QScrollArea
Возможно у меня какие-то ошибки, но выяснил следующее...
Допустим имеем:
class RenderArea: public QScrollArea
{
.....
}
то для того, чтобы добавить лейблы надо написать примерно так:
RenderArea::RenderArea(QWidget * pParent) :
    QScrollArea(pParent)
{
    QLabel *l1 = new QLabel(this);
    QLabel *l2 = new QLabel(this);
    l1->setText("label1");
    l2->setText("label2");
    ..............
}
только надо еще разместить их аккуратно, чтобы не перекрывались.
Размещение с помощью setWidget делается как и раньше:
QLabel *l1 = new QLabel("Hello");
this->setWidget(l1);
Либо через слой, если больше одного размещаемого виджета
QLabel *l1 = new QLabel();
QLabel *l2 = new QLabel();
l1->setText("label1");
l2->setText("label2");
QVBoxLayout *layout = new QVBoxLayout(this);
layout ->addWidget(l1);
layout ->addWidget(l2);
и все прекрасно работает.

Очень было б интересно услышать замечания, особенно тех, кто сталкивался с подобным элементом управления и как решал поставленную задачу.

суббота, 16 января 2010 г.

Qt и QPainter::drawPoint

В данный момент работаю с графикой в Qt. Нужно было рисовать точки.
В книге нашел примерно такой пример отрисовки точки (пример упрощу до рисования одной точки)
QPainter painter(this);
painter.setPen(QPen(Qt:black, 3));
painter.drawPoint(QPointf(12, 12));
В данном примере должна рисоваться точка с координатами (x = 12, y = 12). Но должна, да не обязана, как выяснилось. После некоторого время непонимания, решил попробовать поменять толщину пера на 1px.
painter.setPen(QPen(Qt:black, 1));
И точки начали рисоваться. Вот такая вот штука. Хотя здесь один разработчик говорит, что точка всегда рисуется толщиной в 1 пиксель, несмотря на толщину пера.