30 августа 2006
Контролы от Microsoft снова рулят
Такими темпами придется заиспользовтаь что-то типа Scintilla или еще хуже - написать собственный editbox. Давно думаю, что дойдет именно до написания собственного.
Экономия на мелочах?
Но на деле есть а) выравнивание данных компилятором, б) размер процессорного слова.
Выравнивание может быть настроено на 4 или 8 байт и структура
struct { signed char x, y; } займет 8 или 16 байт, а не 2.Процессору удобнее общаться с числами определенного размера. 32-битному процессору как правило легче обрабатывать 32-битные данные, для обработки данных меньшего размера скорее всего понадобится столько же такотов, но вероятно потребуются дополнительные префиксы к инструкциям. Например, при вызове функции на стек будет запихиваться 32-разрядное число, хоть значимый там будет всего один байт.
То есть никакой экономии памяти при переводе единичных переменных (не массивов) с int на char скорее всего не будет. Зато возможно будут некоторые проблемы.
Ситуация - чтение данных из текстового потока:
int value;
textstream >> value;При входном потоке
"0" мы получим value == 0. А при замене int на char получим value == 48. Сэкономили несколько байт, а получили лишнюю головную боль.
27 августа 2006
Откуда растут руки у программистов Windows?
Стандартный button позволяет рисовать на кнопке иконку (BS_ICON window style + BM_SETIMAGE message). Все замечательно, пока у вас Windows 2000. Когда у вас Windows XP, то вы понимаете, что кнопка с иконкой не поддерживает темы XP. И поддержку тем можно сделать только вручную. А уж если что-то делать вручную, то лучше уж тогда полностью свою кнопку написать. Чтобы не ожидать очередных подстав со стороны Microsoft.
09 августа 2006
Как закрыть форточку?
Задача: закрыть MDI-child окно.
Решение "в лоб": казалось бы, чего проще:
DestroyWindow(hWnd)И даже окно закрывается, на первый взгляд. Но фокус на другое MDI-child окно не переходит, а должен бы. Но это еще не беда. Беда начинается тогда, когда это окно (child) максимизировано. Тогда происходят вообще чудеса: окно закрывается, но от окна в строке меню остаются значки maximize-minimize-close и значок системного меню! Красота, да и только. Если так закрыть несколько окон, то будет полнейший сюрреализм:
Hint: У MDI-child-а обработчик WM_CLOSE по умолчанию закрывает окно (причем делает это грамотно).
Простой вызов
SendMessage(hWnd, WM_CLOSE) подходит, но не во всех случаях. Представьте себе, вы пишете обработчик WM_CLOSE, в котором пишете такую конструкцию ;) Да мы просто зациклимся.В результате у меня получилась подобная конструкция:
bool CMDIFormImpl::Destroy()
{
// This is temporary solution!!!
// todo: replace it by something more suitable
HWND hWnd(m_hWnd);
// call default MDI-child WM_CLOSE handler: it does MDI-destroying better than us
DefWindowProc(WM_CLOSE, 0, 0);
return !::IsWindow(hWnd);
}
01 августа 2006
Убей родителя, и он убъет тебя
delete this; - дело довольно обычное. Так можно в OnFinalMessage() удалить собственный объект.А теперь представьте ситуацию: WTL-контролы (кнопки, edit box-ы и т.п.) хранятся в контейнере указателей у их Parent-а (диалога, etc.). В своем
OnFinalMessage() родитель делает delete this, а его деструктор грохает все объекты WTL-контролов.Все хорошо до тех пор, пока мы не захотим по нажатию кнопки мыши на контроле закрыть окно родителя. В обработчике контрола по
WM_LBUTTONDOWN (скажем, в CControl::OnLButton()) мы вызываем Parent->DestroyWindow() и... падаем по assert-у, так как parent удаляет наш объект, а мы еще не вышли из CControl::OnLButton(), и наш объект еще может понадобится (не в OnLButton(), а в вызвавшем его коде).Можно, конечно, сделать
PostMessage(Parent, WM_CLOSE или WM_DESTROY), и тогда родитель будет удален уже потом, после выхода из CControl::OnLButton(), так как WM_DESTROY дойдет до него не сразу, а просто встанет в очередь. Но иногда нужно грохнуть родителя здесь и сейчас.Выход из такой ситуации: удалять свой объект должен сам контрол в своем
OnFinalMessage(). Правда, родителю не помешает "подчищать" за своими детьми - если контрол был создан как объект, но не успел создасться как окно (в терминах WinAPI) - мало ли по каким причинам - то убить объект контрола должен родитель.
28 июля 2006
Есть ли жизнь после assert()?
Config = LoadConfig("config.cfg");
assert(Config != NULL);
Config->GetValue(...)Вот в таком случае явно не место assert-у. Как минимум -
throw std::exception("wtf w/config?")
19 июля 2006
Template Template Parameters
В кратце суть шаблонных параметров шаблона:
template <typename T>
class Vector { ... }
template <typename T>
class Deque { ... }
template <template <typename> class Container>
class SomeClass
{
Container<SomeDataType> Data;
}
SomeClass<Vector> VectorObject;
SomeClass<Deque> DequeObject;
13 июля 2006
delete this
delete this; выглядит довольно необычно. Выглядит, как будто мы рубим сук, на котором сидим. Однако, все довольно безопасно, если выполнять меры предосторожности (мыть руки перед едой ;)).Что из себя представляют методы класса? Да это обычные функции, которым неявно передается указатель
this.void vector::remove(ItT it)на самом деле выглядит примерно как
void ::remove(vector* this, ItT it)Кстати,
int vector::size() constвыглядит как
int ::size(const vector* this).Соответственно, внутри метода можно спокойно удалить какой-то объект, а называется он
this или как-то по-другому - это все равно. Главное, не пользоваться этим объектом после удаления. Причем (внимание!) нужно перестать им пользоваться совсем, то есть не только в этом методе после выполнения delete this;, но и там, откуда этот метод вызван.Но это еще не все предосторожности. Перед тем как сделать
delete this; естественно надо убедиться, что объект создан через new, а не на стеке или еще как-то (каким-нибудь нестандартным аллокатором). Как убедиться в этом - вопрос дизайна (design). Можно просто написать в документации: "этот объект нужно создавать через new" или сделать конструкторы приватными и создат static-member-ов для создания объектов. Или вообще использовать boost:shared_ptr, и тогда вместо delete this; будет sp.release(), который удалит объект как ему предписывает назначенный deleter.Ссылка по теме: ATL's CWindowImpl::OnFinalMessage() crash
04 июля 2006
STL and C++ Standard Library
На самом деле STL - библиотека, разработанная SGI, часть из которой включили в стандарт C++. Уже из этого вытекают два факта:
1. STL ≠ стандартная библиотека C++,
2. не все, что есть в STL, есть в стандартной библиотеке C++.
Если объяснять на пальцах: парни, разрабатывающие стандарт C++, в один день подумали - вон какие хорошие вещи есть в STL, давайте скопируем некоторые из них в наш стандарт. Скопировали не всё, например, в стандартной библиотеке C++ нет алгоритма copy_n, хотя он есть в STL.
Третье. Стандартная библиотека C++ намного шире STL: в ней есть потоки ввода-вывода, auto_ptr, и много чего другого. Так что говоря про стандартную библиотеку не стоит называть ее "STL".
Наиболее полную реализацию STL можно найти в STLPort.
PS. В следующем стандарте в стандартную библиотеку C++ будет включено много вещей из Boost-а, но это не повод называть стандартную библиотеку Boost-ом :)
28 июня 2006
ATL::CWindow
21 июня 2006
Coding Standards: Smart pointers
Интеллектуальные указатели
Любое использование ресурсов должно следовать принципу «Resource Acquisition Is Initialization».
Таким образом, любое выделение памяти через new должно инициализировать умный указатель:char *Node = new char[Size];
scoped_array<char> Buffer( new char[Size] );
Это правило относится к любым ресурсам, не только в динамической памяти. Например, если необходимо воспользоваться функциями WinAPI для работы с файлами CreateFile() и CloseFile() необходимо написать класс «оболочку» над этими функциями, который в деструкторе будет закрывать файл. Можно использовать любые классы их используемых библиотек: Обычные указатели можно использовать только в случае когда выполняются все перечисленные условия:XMLNode* CreateNode(CFile File) { return new XMLNode(File); }
auto_ptr<XMLNode> CreateNode(CFile File) { return new XMLNode(File); }
− Standard C++ Library (auto_ptr, fstream, ...)
− boost (shared_ptr, scoped_ptr, ...)
− WTL (CDC, CClientDC, CFont, ...)
− указатель не владеет объектом;
− точно известно, что время жизни объекта, на который ссылается указатель, больше чем время жизни указателя.
12 июня 2006
Указатели
char *Text = new char[TextLength];Почувствуйте разницу:
boost::scoped_array<char> Text(new char [TextLength]);
29 мая 2006
Pessimization is evil
Классический пример:
forЗдесь:
(
iterator It(begin()), End(end());
It != End;
++It
)
{
...
}
1. Использование для инициализации копирующего конструктора вместо присваивания. При написании
iterator It = begin() компилятор может создать временный объект, хотя может и соптимизировать.2. Префиксная форма инкремента. Постфиксный инкремент как правило реализуется через префиксный и временную переменную:
iterator operator++(int)В таком случае получаем создание временных объектов, которыми мы не пользуемся. Так что лучше сразу написать префиксную форму.
{
iterator _Tmp = *this;
++*this;
return (_Tmp);
}
3. Сохранение вычисленного end(). Если написать условие в виде
It != end(), то end() может вычисляться при каждой итерации заново. Поэтому лучше его прокэшировать.Сеегодня нашел еще одну явную пессимизацию, которую я, оказывается, всегда использовал (WinAPI specific):
DC.FillSolidRect(..., WindowColor);То есть заполнял фон, затем писал на нем текст, используя "прозрачный" цвет фона при отрисовке текста.
...
DC.SetBkMode(TRANSPARENT);
DC.SetTextColor(TextColor);
DC.DrawText(...);
А теперь подумаем о том, что сейчас у большинства пользователей Windows включен тот стандартный или ClearType антиалиасинг шрифтов. При такой отрисовке многие выводимые пиксели надписи должны отрисовываться с учетом их предыдущего цвета. DrawText() не знает, что мы до этого всю область окна зарисовали каким-то одним цветом. И при отрисовке текста в режиме TRANSPARENT будет считывать то, что уже отрисовано, накладывать на него текст... А ведь можно от этих мучений легко избавиться:
DC.FillSolidRect(..., WindowColor);
...
DC.SetBkMode(OPAQUE);
DC.SetBkColor(WindowColor);
DC.SetTextColor(TextColor);
DC.DrawText(...);
25 мая 2006
C# beats C++
This is the first C++ release. As I became frustrated with C#’s large .NET framework requirements and users lack of desire to install it, I decided to switch back to the faster language. This release is functionally and visually equivalent to the 0.5 C# release. The C# CVS version had some new features that I will soon add to the C++ branch.
Вобщем, Welcome back! ;)
Auto-sized editbox
Загвоздка вся в том, что editbox - стандартный Windows control, в исходники которого залезть нельзя, а функциональность у него весьма ограниченная.
Задача такая: при изменении содержимого editbox-а установить соответствующую его высоту, чтобы весь текст был виден на экране.
Что имеется у стандартного edit-а:
1. Область format rectangle, которая задает область редактирования. Как правило, чуть меньше чем client area. При изменении размера edit-а, эта format rectangle ставится как ей угодно. (Нет чтобы сделать format margins, которые не меняются...)
2. Сообщение EM_GETLINECOUNT - можно узнать текущее количество строк в контроле.
3. Уведомления EN_UPDATE и EN_CHANGE - можно отслеживать изменения содержимого контрола. EN_UPDATE приходит до прорисовки, EN_CHANGE - после.
При реализации сразу начались проблемы.
Проблема 1. Не приходит уведомление EN_UPDATE. Решилось просто:
EditBox.SendMessage(EM_SETEVENTMASK, 0, ENM_UPDATE);Проблема 2. После изменения размера контрола происходит изменение format rectangle, приходится возвращать его на место:
EditBox.GetClientRect(ClientRect);
EditBox.SendMessage(EM_SETRECTNP, 0, (LPARAM)(&ClientRect));Проблема 3. Самая большая проблема. Суть ее такова: необходимая нам высота client area равна КоличествоСтрок * ВысотаСтроки. Количество строк мы получить можем, а вот как высоту строки от edit control-а получить? Долго пытался использовать разумные значения, но никак не выходило правильной отрисовки во всех случаях. Перекопал весь Google.Groups, советов много, но ни один точно не соответсвует тому, как рисует текст editbox.
Магическая формула оказалась такая:
ТребуемаяВысота = КоличествоСтрок * TEXTMETRIC.tmHeight + 2Нет, представляете - "+ 2". Что это за magic number я не знаю, но его я нашел где-то в ньюсгруппах.
Итого получилось:
void AdjustEditSize()
{
int nLines = (int) EditBox.SendMessage(EM_GETLINECOUNT);
// if (nLines == PrevLines) return;
CRect ClientRect, WinRect;
EditBox.GetClientRect(ClientRect);
EditBox.GetWindowRect(WinRect);
EditBox.SendMessage(EM_GETRECT, 0, (LPARAM)(&FormatRect));
CClientDC DC(EditBox);
DC.SelectFont((HFONT) EditBox.SendMessage(WM_GETFONT));
TEXTMETRIC tm;
DC.GetTextMetrics(&tm);
int LineHeight = tm.tmHeight;
MapWindowPoints(HWND_DESKTOP, EditBox.GetParent(), (LPPOINT)&WinRect, 2);
WinRect.bottom = WinRect.top
+ (WinRect.Height() - ClientRect.Height()) // adding non-client area
+ nLines * LineHeight + 2; // what da hell is "2" here?
EditBox.MoveWindow(WinRect);
// restore fucking format margins. set format rect = client area (i.e. margins=0)
EditBox.GetClientRect(ClientRect);
EditBox.SendMessage(EM_SETRECTNP, 0, (LPARAM)(&ClientRect));
}
15 мая 2006
throw()
Начали с throw, закончили спором на счет оптимизации.
10 мая 2006
Scintilla
PS. Ложка дегтя.
09 мая 2006
BitBlt does clipping. It does it bad.
Так вот при этом clipping-е BitBlt забывает про viewport, и режет как будто его нет.
Пришлось извратиться:
void RenderBuffer(HDC PaintDC, const RECT& SourceRect, const POINT& Destination)
{
// BitBlt does clipping on the destination DC.
// But BitBlt's clipping know nothing about view ports :(
// So, we need to set viewport to (0,0)
// and handle original viewport manually when calling BitBlt
POINT OriginalViewport;
::SetViewportOrgEx(PaintDC, 0, 0, &OriginalViewport);
// perform rendering
::BitBlt(PaintDC,
SourceRect.left,
SourceRect.top,
SourceRect.right - SourceRect.left,
SourceRect.bottom - SourceRect.top,
BufferDC,
Destination.x + OriginalViewport.x,
Destination.y + OriginalViewport.y,
SRCCOPY);
// restore original viewport
::SetViewportOrgEx(PaintDC, OriginalViewport.x, OriginalViewport.y, NULL);
}
06 мая 2006
WTL revisited
1. У него нет класса CIcon. Пришлось хранить HICON и прибивать его (DeleteObject()) собственноручно в деструкторе объекта.
2. У класса CDC нет метода ReleaseDC(). Пришлось делать руками, через WinAPI:
int LogPixelsY;
{
CDCHandle ScreenDC(::GetDC(NULL));
LogPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY);
::ReleaseDC(NULL, ScreenDC.Detach());
}
Сегодня почесав репу и пошерстив Гугл понял, что остал от жизни - вышел уже WTL 7.5, а у меня все 7.1 стоит. Скачал, поставил.
1. CIcon появился.
2. Понял, что CDC не предназначен для ReleaseDC(). Для него есть CClientDC:
int LogPixelsY;
{
CClientDC ScreenDC(NULL);
LogPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY);
}
Жизнь налаживается...
05 мая 2006
Имя файла и его расширение
Пришлось писать свои шаблоны:
template <typename StringType>
typename StringType::const_iterator FindFileName(const StringType& FilePath)
{
typename StringType::const_reverse_iterator rend(FilePath.rend()), rit(FilePath.rbegin());
for (; rit != rend; ++rit)
{
switch (*rit)
{
case '/':
case '\\':
return rit.base();
}
}
return FilePath.begin();
}
template <typename StringType>
typename StringType::const_iterator FindFileExtension(const StringType& FilePath)
{
typename StringType::const_reverse_iterator rend(FilePath.rend()), rit(FilePath.rbegin());
for (; rit != rend; ++rit)
{
switch (*rit)
{
case '/':
case '\\':
return FilePath.end();
case '.':
return rit.base();
}
}
return FilePath.end();
}
template <typename StringType>
StringType ExtractFileName(const StringType& FilePath)
{
return StringType(FindFileName(FilePath), FilePath.end());
}
template <typename StringType>
StringType ExtractFileExtension(const StringType& FilePath)
{
return StringType(FindFileExtension(FilePath), FilePath.end());
}
Использование:
std::wstring Path = L"c:\\foo\\bar.xml";
std::wcout << L"file: " << ExtractFileName(Path) << L", ext: " << ExtractFileExtension(Path) << std::endl;
Используемо с basic_string, vector и другими контейнерами.
Понимает как char, так и wchar_t.