This project was started to gain and share experience in programming applications for ReactOS. It integrates many small things I've done already - like choosing the right IDE, preparing for source code documentation and developing user controls.
- Download ReactOS/CodeBlocks souce and debug compilation V0.6 (C#/C++)
Older versions: V0.5 (C++), V0.4 (C++), V0.3 (C++), V0.2 (C++), V0.1 (C++) - Download Windows/VisualStudio souce and debug compilation V0.6_(C#/C++)
Older versions: V0.5 (C++), V0.4 (C++), V0.3 (C++), V0.2 (C++), V0.1 (C++) - Download documentation generated with DoxyGen V0.6_Doxygen_Editor.zip and V0.6_Doxygen_OGWW.zip;
Older versions: V0.5_Doxygen_Editor.zip, 0.4_Doxygen_Editor.zip and V0.5_Doxygen_OGWW.zip, V0.4_Doxygen_OGWW.zip
Contents
ReactOS is an open source alternative to the Windows operation system. Even if the first version of ReactOS dates back to 1998, there is still no 'stable' version of ReactOS. Maybe, the most important reason is a lack of attention.
Driven by the tip, Introduction to Embedded Icons without Resource on ReactOS, I wanted to dive deeper into the topic of Windows icons and looked for a free icon editor for ReactOS, that was simple enough to use without a long study of the docs. The best match I've found has been the Junior Icon Editor. This icon editor works fine on all Microsoft Windows versions I've tested and it can be easily installed and started on ReactOS as well. The only drawback is that the color palet for 4 bpp icons (16 color icons) does not work (tested on ReactOS version 0.4.11 with Junior Icon Editor version 4.39).
The Junior Icon Editor's color palet for a 8 bpp icon (256 color icon) on ReactOS: |
| The Junior Icon Editor's color palet for a 4 bpp icon (16 color) on ReactOS: |
|
My primary focus is on 4 bpp icons. Although it is more likely that the Junior Icon Editor's color palette problem is caused by a missing 4bpp default palette in ReactOS than a bug in the Junior Icon Editor itself, it is hardly possible to create or edit 4bpp icons.
Because of this drawback and since I wanted to test the findings from the tips, Introduction to OpenGL with C/C++ on ReactOS and Introduction to C# on ReactOS on a real application, I started to program my own basic icon editor - the ReactOS Icon Editor. This application is inspired by the simplicity and intuitive handling of the Visual Studio Icon Editor.
Version 0.2
- New: The 16 colors of the 4bpp color palette can be individualized. A double click on the color opens the color dialog of ReactOS and the color value can be set individually. Advantage: The 4bpp palette can also be used to create appealing icons. Disadvantage: Not all icon editors support individualized colors and reset the colors to the default palette - e.g. the Visual Studio icon editor.
- New: The Pipette Tool is implemented. Now colors can also be selected within the image.
- New: The menu items now also support icons (13x13 pixels) including transparency. They are automatically switched to ownerdraw. Advantage: The native support of menu item bitmaps is originally intended for 1bpp images and does not support transparency for color images. With icons, transparency is also supported for colored images. Disadvantage: The switch to ownerdraw can lead to inconsistent results when using themes.
- Fix: The "Save as" dialog works now correctly / generates no longer a segmentation fault (wrong parameter 4 in call to
wcsncpy_s()
fixed). - Fix: Segmentation faults have been fixed, that occurred when working with icons whose width/height is not a multiple of 8 (e.g. icons with 13x13 pixels for menu items).
- Upgrade: Improved
OWNERDRAW
menus, which support accelerators now. Provision of accelerators for many menu items.
Version 0.3
This update took quite a long time - but that has to do with my learning curve. Firstly, I found the wonderful Win32++ from David Nash (whose simplicity and clarity impressed me as much as the preemption of inheritance and object orientation in the K&R C Athena Widget Set more than 30 years ago) and secondly, this was the first time I seriously looked into DoxyGen.
You may ask me: Why is my icon editor not based on Win32++?
- I think Win32++ is cool, but I discovered Win32++ very late.
- I am seriously considering to switch to Win32++ - especially because of the already existing stability - but I am not entirely happy with the implementation of the
MenuBar
and ToolBar
in mainframe window. - The design of Win32++ impresses me with its clarity and simplicity - but the need for synchronization when developing own controls is still a bit daunting.
Maybe this will change the more professional - and thus the more similar in the program structure to Win32++ - my icon editor becomes.
- NEW: The fill tool is implemented now .
- NEW: All code comments in the whole
Ogww
Library are revised in such a way that a meaningful documentation can be generated with DoxyGen. I use the DoxyBlocks plugin from Code::Blocks (and I'm very pleased about the resulting documentation). See also the section "Adding a documentation generator" in my tip Introduction to OpenGL with C/C++ on ReactOS for details on this. - FIX: Memory violation when using
GetDIBits()
has been fixed - at least on Microsoft Windows. Sometimes (based on my observation on color depths that require a color palette) GetDIBits()
writes a minimum color palette into the BITMAPINFOHEADER
. According to the Microsoft Forum, they are 3 colors (3* sizeof(WORD)
). If this is not taken into account, memory violations occur.
Version 0.4
- FIX: The overlapping display (outdated position) of the image buttons with wrong size/position after opening an icon file has been fixed, which must formerly be rectified manually by resizing the window. Fixed by the handover of the window size to
::SendMessage(hWnd, WM_SIZE, (WPARAM)SIZE_RESTORED, ...)
after opening an icon file. - FIX: The display of the old color (outdated RGB value) in
ColorPicker
and PixelEdit
after changing a color of the color palette was fixed, which must formerly be rectified manually by resizing the window. Fixed by a re-paint of the ColorPicker
and PixelEdit
with ::InvalidateRect(_pweakColorPicker->GetHWnd(), NULL, TRUE)
and ::InvalidateRect(_pweakPixelEdit->GetHWnd(), NULL, TRUE)
. - NEW: Added memory leak detection. I've read the great article A Cross-Platform Memory Leak Detector and wondered how good my application is in terms of memory management. See also the section "Memory leak detection" in this article.
- NEW: Added palette color change to the undo/redo chain.
- NEW: All code comments in the whole
IconEditor
is revised in such a way that a meaningful documentation can be generated with DoxyGen. I use the DoxyBlocks plugin from Code::Blocks (and I'm very pleased about the resulting documentation). See also the section "Adding a documentation generator" in my tip Introduction to OpenGL with C/C++ on ReactOS for details on this. - NEW: Ownerdraw buttons support
BS_RADIOBUTTON
and BS_AUTORADIOBUTTON
. This is a function which is supported by the original window controls for autodraw buttons only (because the bit mask of BS_OWNERDRAW
overlaps other button styles and can't be used simultaneously with BS_CHECKBOX
, BS_AUTOCHECKBOX
, BS_RADIOBUTTON
and BS_AUTORADIOBUTTON
). - NEW: Added initial handling of multi-image icons: Load/save/save as for multi-image icons work. Images can be added to an existing icon (but currently not removed) and the image order can be changed.
Version 0.5
- NEW: Delete image from icon is implemented (the last remaining image can't be removed).
- NEW: Tool bar buttons support tool tips.
- IMPROVED: Methods, that return dynamically allocated strings, no longer use
StringMediator*
but String
. This ensures an automatic garbage collection as before and the code is immediately transparent for every C++ programmer. For details, see tip How to return a string class from a C++ function/method. - NEW: As an alternative to the simple
ToolBar
, it is now possible to use several ToolBar
s within one ReBar
. The first ToolBar
of the ReBar
is automatically created as a default ToolBar
. - IMPROVED: The image of disabled menu items and toolbar buttons are calculated as a gray scale image now, instead of an gray/white image with cast shadow.
- FIXED: The short cuts for UNDO (Ctrl + z) and REDO (Ctrl + y) work now.
- NEW: The select (lasso) tool has been added to capture a rectangle area of pixels as selected (the images below this list show the tool bar button and a sample rectangle area of selected pixels).
- NEW: Basic COPY (Ctrl + c) of the current icon image (e.g., to Paint) and basic PASTE (Ctrl + v) into the current icon image (e.g., from Paint) are added. If a rectangle area of pixels is selected, PASTE affects the selection only. For details, see tip Approach to Paste a Bitmap into an Icon Image on ReactOS (and Consequently on Windows XP and Newer Versions, using Win32 API).
Version 0.6
- FIXED: The methods
CDDBitmap::CopyTo4bppColors()
and CDDBitmap::CopyTo8bppColors()
work now (they have sometimes thrown exceptions during redurction of the color vector for the final color table). - NEW: Small and big icons can be set now for the application farme window.
- IMPROVED: The
C
API has been cleaned up to support C#
P/Invoke (interop) for the whole API. - NEW: A
C#
equivalent to the C++
application hast been added for ReactOS (editor: Notepad++ with NppExec plugin, compiler: MONO installation mono-4.3.2.467-gtksharp-2.12.30.1-win32-0.msi, see tip Introduction to C# on ReactOS for details) and Windows (Visual Studio). - IMPROVED: The undo/redo chains is no longer connected to the pixel editor control, and thus generic for all icon images, but every icon image has now a local undo/redo chains. In addition, the redo/undo chains remain intact when the icon image is changed.
- IMPROVED: The application now has its own icon - the first meaningful icon created with the application itself.
- FIXED: A memory leak during PASTE (Ctrl + v) into the current icon image has been eliminated.
- NEW: The selected rectangle area of pixels can be scaled now.
The ReactOS Icon Editor is based on the OpenGL Windows Wrapper (Ogww) DLL, introduced by the tip Introduction to OpenGL with C/C++ on ReactOS. Meanwhile, the DLL has evolved to meet significantly more requirements on a professional UI, but the DLL is still far away from a release state. However, it is still designed to support application development with C/C++ and C#.
Now, the inclined reader will ask: Why Ogww
- yet another wrapper around the Win32 API?
Simple answer: Because I discovered Win32++ only later.
Win32++ does a great job! I will try to replace Ogww
as completely as possible by Win32++ bit by bit. Although I already know that there will be challenges: CString
is not based on CObject
and CObject
offers no typeof()
operator / no GetType()
method or any alternative easy access to RTTI.
The ReactOS Icon Editor is currently very limited but designed to be developed step by step into a full-fledged icon editor. The current limitations are:
- support for 4bpp (16 colors) mode only
- support for pen, eraser, fill and pipette tool only (copy/paste tools are planned)
And that's how it currently looks:
Starting with version 0.5, it's possible to choose between a simple ToolBar
and a ReBar
, that provides the option to handle multiple ToolBar
s. This is the code, that shows both opportunities at once:
void CIconEditMainFrame::AddToolBar(HWND hWnd)
{
HBITMAP hColorBmp = NULL;
HBITMAP hMaskBmp = NULL;
#ifdef USE_REBAR
LPVOID pweakReBarImp = ReBarCreateAndRegister(hWnd, TOOL_BAR_DEFAULT_ID, 16, 5);
CReBar aReBar(pweakReBarImp);
_pweakToolBar = new CToolBar(aReBar.GetFistToolBarImplementation());
#else
LPVOID pweakToolBarImp = ToolBarCreateAndRegister(hWnd, TOOL_BAR_DEFAULT_ID, 16, 5);
_pweakToolBar = new CToolBar(pweakToolBarImp);
#endif
CIcon::LoadIconBitmapsFromBytes(ICO_NEW2_16_Bytes(), ICO_NEW2_16_ByteCount(),
16, 16, true, &hColorBmp, &hMaskBmp);
_pweakToolBar->AddButton(hColorBmp, hMaskBmp, MENU_FILE_NEW_ID,
TBSTATE_ENABLED, TBSTYLE_BUTTON);
::DeleteObject(hColorBmp);
::DeleteObject(hMaskBmp);
_pweakToolBar->SetButtonToolTip(MENU_FILE_NEW_ID, _(L"MAINFRAME|Toolbar",
L"New\n\nStart with a new\ninitial icon file.", L"Tool bar item", __FILE__));
...
_pweakToolBar->Show();
}
I have added the new window class PixelEditWindow
to the OpenGL Windows Wrapper (Ogww) DLL - the picture above shows the control in action, right in the center of the application. It is based on the ICONIMAGE
structure (see chapter "The inside of an icon" below) and designed to display and edit the image pixels.
Masked pixels (pixels that are not displayed) are shown in the defined color but crossed out. Unmasked pixels (pixels that are displayed) are shown in the defined color.
The masking is set with the erase tool - it also sets the color.
The pen tool sets the color and deletes the masking.
I have decided
- to set the defined color when masking a pixel, and
- to show masked pixels in the defined color and not in the complementary color or any fixed color.
Pencil tool and erase tool work when the left mouse button is held down - as long as the mouse button is held down, multiple pixels can be set via mouse move.
| Since disabled toolbar buttons and disabled menu items typically display their images gray/white with cast shadow, toolbar and menu item images should always be designed to keep the bottom and right pixel stripes unused.
The image to the left illustrates the procedure by which disabled images (second row) are automatically calculated from normal images (first row).
|
Update with version 0.5: I changed the look of the disabled icons from the Win32 standard (gray/white with cast shadow) to user specific with a more attractive look (greyscale).
| The image to the left illustrates the new procedure by which disabled images (second row) are automatically calculated from normal images (first row).
|
I've developed the ReactOS Icon Editor entirely using Code::Blocks on ReactOS. The solution currently has the following structure:
| The download (at the top of this article) contains a folder structure with the three folders OGWW, OGWW_Wrapper and ReactOS_Icon_Editor.
The Code::Blocks solution consists of these two projects
- OGWW (the OpenGL Windows Wrapper DLL), completely contained in folder OGWW and
- ReactOS Icon Editor (the icon editor application), ivided into the folders OGWW_Wrapper (contains the glue code, which makes OGWW usable for ReactOS Icon Editor) and WWOG (the application itself).
To open the projects on a new environment for the first time, these two files must be opened:
- ...\OGWW\OGWW.cbp
- ...\ReactOS_Icon_Editor\ReactOS_Icon_Editor.cbp
The OGWW project uses a sub-folder structure to organize the code files.
- The folder Images contains
BYTE[] of icons and bitmaps, that are provided by the DLL to applications. - The folder Layouter contains all layouter classes.
- The folder Tests contains rudimentary unit and performance tests.
- The folder Windows contains all classes, that are based on a Win32 window (have an own
HWND ).
The ReactOS Icon Editor project also integrates the folder OGWW_Wrapper, that holds all wrapper classes to provide an object oriented interface of the OGWW DLL.
|
In order to check whether the current build environment is well configured, here is an example call to g++:
mingw32-g++.exe -Wall -std=c++11 -pg -g -D_UNICODE -DUNICODE -D__MSVCRT__ -Wall -g -DBUILD_DLL
-c C:\Projects\CodeBlocks\OGWW\Console.cpp -o obj\Debug\Console.o
The -std=c++11
switch lifts the g++ to the ANSI C++2011 standard. This enables among other thing the use of
snwprintf()
instead of swprintf()
, std::chrono::high_resolution_clock::now()
instead of GetTickCount()
and auto
.
The -D_UNICODE
and -DUNICODE
switch ensure the general use of wchar_t
instead of char
.
The -D__MSVCRT__
switch announces the availability of msvcrt.dll
, that enables among other things, the use of _wgetenv()
instead of getenv()
.
The most important class of the ReactOS Icon Editor is the class OgwwIconData
. This class provides convenient access to the current image and manages the selected mask as well as the selected color. It is derived from the class OgwwIcon
, that manages the icon as a whole. The basic structure of an icon is:
● Icon directory | stored to ICONDIR structure |
● Icon directory entries | stored to ICONDIRENTRY[] within ICONDIR structure |
□ Image directory entry 1...n | stored to ICONDIRENTRY structure |
● Images | stored to ICONIMAGE[] |
□ Image 1...n | stored to ICONIMAGE structure |
◦ Bitmap info header | stored to BITMAPINFOHEADER within ICONIMAGE structure |
◦ Bitmap color palet | stored to AARRGGBB[] within ICONIMAGE structure |
◦ Color bitmap bytes | stored to BYTE[] within ICONIMAGE structure |
◦ Mask bitmap bytes | stored to BYTE[] within ICONIMAGE structure |
Only BITMAPINFOHEADER
is a standard Windows data structure. I would like to briefly introduce the other structures and types ICONDIR
, ICONDIRENTRY
and ICONIMAGE
:
typedef struct tagICONDIR
{
WORD idReserved;
WORD idType;
WORD idCount;
LPICONDIRENTRY idEntries;
} ICONDIR, *LPICONDIR;
typedef struct tagICONDIRENTRY
{
BYTE deWidth;
BYTE deHeight;
BYTE deColorCount;
BYTE deReserved;
WORD dePlanes;
WORD deBitCount;
DWORD deBytesInRes;
DWORD deImageOffset;
} ICONDIRENTRY, *LPICONDIRENTRY;
The size of an image (deBytesInRes
) can be calculated this way: sizeof(BITMAPINFOHEADER) + sizeof(ICONIMAGE::iiColors) + sizeof(ICONIMAGE::iiXOR) + sizeof(ICONIMAGE::iiAND)
typedef DWORD AARRGGBB;
#define AARRGGBBtoCOLORREF(v) ((DWORD) (((0xFF000000 & ((DWORD)v)) >> 0) | \\
((0x00FF0000 & ((DWORD)v)) >> 16) | \\
((0x0000FF00 & ((DWORD)v)) << 0) | \\
((0x000000FF & ((DWORD)v)) << 16)) )
#define AARRGGBBtoLUMINANCE(v) ((WORD) (((0x00FF0000 & ((DWORD)v)) >> 16) + \\
((0x0000FF00 & ((DWORD)v)) >> 8) + \\
((0x000000FF & ((DWORD)v)) >> 0)) ) \\
typedef struct tagICONIMAGE
{
BITMAPINFOHEADER iiHeader;
AARRGGBB* iiColors;
BYTE* iiXOR;
BYTE* iiAND;
} ICONIMAGE, *LPICONIMAGE;
To get full control over all data of an icon, I implemented my own methods for reading OgwwIcon::ConstructFromFile(LPCWSTR wszFilePath)
and writing OgwwIcon::SaveAs(LPCWSTR wszFilePath)
icons.
At first, I focused on supporting 16 color images. A further development to 256 and 16777216 colors is already planned.
There are two essential problem areas regarding the flickering:
- Redrawing of the controls (e.g., during resize)
- Editing the image pixels (window class
PixelEditWindow
/ control OgwwPixelEdit
)
Since redrawing the controls happens relatively rarely, my focus is on editing the image pixels. To edit the image pixels, the control OgwwPixelEdit
is used, which is an owner-drawn control. Either the WS_EX_COMPOSITED
window style does not solve the problem or it does not work as expected on ReactOS (double-buffering). The solution to this problem is an optimized implementation of the message handlers for WM_ERASEBKGND
and WM_PAINT
, as shown below:
case WM_ERASEBKGND:
{
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
::BeginPaint(hWnd, &ps);
RECT clientRect;
::GetWindowRect(hWnd, &clientRect);
clientRect.right -= clientRect.left; clientRect.left = 0;
clientRect.bottom -= clientRect.top; clientRect.top = 0;
OgwwPaintArgs paintArgs(&ps, clientRect);
OnPaint(paintArgs);
::EndPaint(hWnd, &ps);
return 0;
}
I moved the message handler for WM_PAINT
to the OnPaint()
method:
void OgwwPixelEdit::OnPaint(OgwwPaintArgs& paintArgs)
{
RECT rcClientRect = paintArgs.GetClientRect();
LPBITMAPINFOHEADER bmiHeader = NULL;
COLORREF crBackground = ::GetSysColor(COLOR_BTNFACE);
HBRUSH hbrush = CreateSolidBrush(crBackground);
if (_pIconImage == NULL)
{
::FillRect(paintArgs.GetDC(), &rcClientRect, hbrush);
}
else
{
bmiHeader = &(_pIconImage->iiHeader);
if (_rcPadding.left > 0)
{
RECT bgClipLeftArea = rcClientRect;
bgClipLeftArea.right = _rcPadding.left;
bgClipLeftArea.bottom = bmiHeader->biHeight * (1 + _nZoom) + 1 + _rcPadding.top;
::FillRect(paintArgs.GetDC(), &bgClipLeftArea, hbrush);
}
if (_rcPadding.top > 0)
{
RECT bgClipTopArea = rcClientRect;
bgClipTopArea.bottom = _rcPadding.top;
bgClipTopArea.right = bmiHeader->biWidth * (1 + _nZoom) + 1 + _rcPadding.left;
::FillRect(paintArgs.GetDC(), &bgClipTopArea, hbrush);
}
RECT bgClipBottomArea = rcClientRect;
bgClipBottomArea.top = bmiHeader->biHeight * (1 + _nZoom) + 1 + _rcPadding.top;
bgClipBottomArea.right = bmiHeader->biWidth * (1 + _nZoom) + 1 + _rcPadding.left;
if (bgClipBottomArea.top < bgClipBottomArea.bottom)
::FillRect(paintArgs.GetDC(), &bgClipBottomArea, hbrush);
RECT bgClipRightArea = rcClientRect;
bgClipRightArea.left = bmiHeader->biWidth * (1 + _nZoom) + 1 + _rcPadding.left;
if (bgClipRightArea.left < bgClipRightArea.right)
::FillRect(paintArgs.GetDC(), &bgClipRightArea, hbrush);
}
::DeleteObject(hbrush);
if (_pIconImage == NULL)
return;
if (bmiHeader->biWidth > 0 && bmiHeader->biHeight > 0)
{
HPEN oldPen = (HPEN)::SelectObject(paintArgs.GetDC(), _hDarkMaskPen);
int x = rcClientRect.left + _rcPadding.left;
int y = rcClientRect.top + _rcPadding.top;
for (int nRowCount = 0; nRowCount < bmiHeader->biHeight; nRowCount++)
{
for (int nColCount = 0; nColCount < bmiHeader->biWidth; nColCount++)
{
WORD luminance = 0;
HBRUSH currentBrush = NULL;
if ((bmiHeader->biBitCount == 4) && (_pIconImage->iiColors != NULL))
{
DWORD colorIndex = OgwwIcon::BmpXORgetColor4bpp (_pIconImage->iiXOR,
nColCount, nRowCount, bmiHeader->biWidth, bmiHeader->biHeight);
luminance = AARRGGBBtoLUMINANCE(_pIconImage->iiColors[colorIndex]);
currentBrush = ::CreateSolidBrush(
AARRGGBBtoCOLORREF(_pIconImage->iiColors[colorIndex]));
}
else if ((bmiHeader->biBitCount == 8) && (_pIconImage->iiColors != NULL))
{
DWORD colorIndex = OgwwIcon::BmpXORgetColor8bpp (_pIconImage->iiXOR,
nColCount, nRowCount, bmiHeader->biWidth, bmiHeader->biHeight);
luminance = AARRGGBBtoLUMINANCE(_pIconImage->iiColors[colorIndex]);
currentBrush = ::CreateSolidBrush(
AARRGGBBtoCOLORREF(_pIconImage->iiColors[colorIndex]));
}
else
{
int pixelIndex = nRowCount * bmiHeader->biWidth + nColCount;
luminance = AARRGGBBtoLUMINANCE(_pIconImage->iiXOR[pixelIndex]);
currentBrush = ::CreateSolidBrush(
AARRGGBBtoCOLORREF(_pIconImage->iiXOR[pixelIndex]));
}
RECT currentRC;
currentRC.left = x; currentRC.top = y;
currentRC.right = x + (1 + _nZoom); currentRC.bottom = y + (1 + _nZoom);
currentRC.left += 1;
currentRC.top += 1;
::FillRect(paintArgs.GetDC(), ¤tRC, currentBrush);
currentRC.left -= 1;
currentRC.top -= 1;
::DeleteObject(currentBrush);
bool bMasked = OgwwIcon::BmpXORgetMask(_pIconImage->iiAND, nColCount,
nRowCount, bmiHeader->biWidth, bmiHeader->biHeight);
if (bMasked)
{
if (luminance < 192)
::SelectObject(paintArgs.GetDC(),_hLightMaskPen);
else if (luminance < 384)
::SelectObject(paintArgs.GetDC(),(HGDIOBJ)::GetStockObject(WHITE_PEN));
else if (luminance < 576)
::SelectObject(paintArgs.GetDC(),(HGDIOBJ)::GetStockObject(BLACK_PEN));
else
::SelectObject(paintArgs.GetDC(),_hDarkMaskPen);
::MoveToEx(paintArgs.GetDC(), x, y, NULL);
::LineTo (paintArgs.GetDC(), x + (1 + _nZoom), y + (1 + _nZoom));
if (_nZoom >= 6)
{
int nHalf = _nZoom / 2;
::MoveToEx(paintArgs.GetDC(), x + nHalf, y, NULL);
::LineTo (paintArgs.GetDC(), x + (1+_nZoom), y - nHalf +(1+_nZoom));
::MoveToEx(paintArgs.GetDC(), x, y + nHalf, NULL);
::LineTo (paintArgs.GetDC(), x - nHalf + (1+_nZoom), y +(1+_nZoom));
}
}
x += (1 + _nZoom);
}
x = rcClientRect.left + _rcPadding.left;
y += (1 + _nZoom);
}
int nLineWidth = 1 + (1 + _nZoom) * bmiHeader->biWidth;
int nLineHeight = 1 + (1 + _nZoom) * bmiHeader->biHeight;
HPEN newPen = GetStockPen(BLACK_PEN);
::SelectObject(paintArgs.GetDC(), newPen);
x = rcClientRect.left + _rcPadding.left;
y = rcClientRect.top + _rcPadding.top;
for (int nRowCount = 0; nRowCount <= bmiHeader->biHeight; nRowCount++)
{
::MoveToEx(paintArgs.GetDC(), x, y, NULL);
::LineTo(paintArgs.GetDC(), x + nLineWidth, y);
y += (1 + _nZoom);
}
x = rcClientRect.left + _rcPadding.left;
y = rcClientRect.top + _rcPadding.top;
for (int nColCount = 0; nColCount <= bmiHeader->biWidth; nColCount++)
{
::MoveToEx(paintArgs.GetDC(), x, y, NULL);
::LineTo(paintArgs.GetDC(), x, y + nLineHeight);
x += (1 + _nZoom);
}
::SelectObject(paintArgs.GetDC(), oldPen);
}
}
I have added comments at the three most important points of the optimization:
- DON'T DRAW on
WM_ERASEBKGND
(integrate background drawing into WM_PAINT
instead) - DRAW BACKGROUND (only outside the pixel field - it will be updated later)
- DRAW PIXEL FACE (without any overlap with the borders)
There is a good article "Flicker free drawing of any control" about this topic, which I used as a basis for my solution. The use of clip regions as described in the article "A Guide to WIN32 Clipping Regions" could be an equivalent alternative.
After I accidentally came across the article, A Cross-Platform Memory Leak Detector, I wondered how good my application is in terms of storage management. The answer was disillusioning. I almost lost faith in myself and in C++. But C++ wouldn't be C++ if it couldn't pull itself out of this mess.
First and foremost: Respect to all programmers who write stable professional applications in C or C++, which do a hard job for days, weeks or months without complaint and without having to be restarted!
If my Icon Editor (before I started to revise the memory management) would be an application that is in use continuously for a long time and processes large amounts of data, the operating system would certainly run out of breath at some point. As it turned out that it was mainly about small omissions, the memory leaks were cleared away when the application was terminated and were not noticed any further. In the long run, however, the memory leaks would still cause considerable memory consumption.
I've learned the following things while searching for memory leaks:
- You definitely need easy-to-use yet effective tools. The on-board tools of gcc or VS are not enough. Special versions (with logging/tracing) of
::GlobalAlloc()
and ::GlobalFree()
as well as new
and delete
are highly recommended. - Make a simple determination for using
::GlobalAlloc()
and ::GlobalFree()
respective new
and delete
- and stick to it! I used new
and delete
only for objects that have constructor(s) / destructors. In all other cases, I used ::GlobalAlloc()
and ::GlobalFree()
(because I'll never know whether I have to pasds (parts) of the memory block to Window API calls). - Design all dynamic memory usage intuitively! Any method that allocates dynamic memory should have a natural/intuitive counterpart to release the dynamic memory and both should be found instinctively.
- Never stop improving memory management. Even 2 months after I first thought all memory leaks were fixed, I still found new ones.
I finally ended up with 4 macros and one class, with which I could detect all memory leaks - fixing them was a breeze afterwards. They are much simpler and far from being as beautiful as described in A Cross-Platform Memory Leak Detector - but they do their job well. Here is a screenshot from the final phase of my fixes:
The macros
ALLOC_REGISTRATION_CALL___DBG
(wraps ::GlobalAlloc()
) / FREE_CODE_BLOCK___DBG
(replaces ::GlobalFree()
) and NEW_REGISTRATION_CALL___DBG
(wraps new
) / DELETE_CODE_BLOCK___DBG
(replaces delete
)
are declared in MemoryDebug.hpp and OgwwAPI.hpp and are used for all requests or releases of dynamic memory. And here exemplary use cases for ::GlobalAlloc()
/::GlobalFree()
and new
/delete
:
...
BYTE* pbIconBytes = (BYTE*)ALLOC_REGISTRATION_CALL___DBG(::GlobalAlloc(GPTR, dwBufferSize));
...
if (pbIconBytes != NULL)
FREE_CODE_BLOCK___DBG(pbIconBytes)
pbIconBytes = NULL;
...
...
CColLayouter* pColLayouter = NULL;
NEW_REGISTRATION_CALL___DBG(pColLayouter = new CColLayouter());
...
if (pColLayouter != NULL)
DELETE_CODE_BLOCK___DBG(pColLayouter)
pColLayouter = NULL;
...
I have written the DLL and the application completely in Code::Blocks 17.12 on ReactOS 0.4.11. Now I was wondering if this can be transferred 1:1 to Microsoft World with Windows 10 and Visual Studio 2017. After some initial difficulties, the creation of full compatibility for the entire source code of the DLL and the application took one day. For those of you who have something similar in mind, here are some hints on how to make the necessary adjustments.
1. First, I created a new solution with Visual Studio and two new projects. One project based on the template "Other Languages | Visual C++ | Windows Desktop Application - Visual C++" for the application and one project based on the template "Other Languages | Visual C++ | Dynamic-Link Library (DLL) - Visual C++" for the DLL.
1.1. Any new Visual Studio C++ project is prepared to use precompiled headers. This is a feature I don't use with Code::Blocks. To remove this feature, the project properties must be edited for both projects.
Switch the "Configuration Properties | C/C++ | Precompiled Headers" property "Precompiled Headers" from "Create (/Yc)" to "Not Using Precompiled Headers" and empty the properties "Precompiled Header File" and "Precompiled Header Output File" for "All Configurations".
1.2. To resolve all external references, two libraries must be added to both new solutions: opengl32.lib;comctl32.lib;
This is done via the "Configuration Properties | Linker | Input" property "Additional Dependencies" for "All Configurations".
2. All code and resource files except dllmain.cpp, generated with the two project templates, must be deleted from the projects and all code files from Code::Blocks 17.12 must be registered to the appropriate project. Within dllmain.cpp, the #include "stdafx.h"
must be replaced by #include "windows.h"
.
3. Unfortunately, Code::Blocks comes with an older version of MinGW that does not yet include the security enhancements of string functions. However, Visual Studio demands the use of these functions and therefore a solution must be found. A local deactivation using "#pragma warning (disable: 4996)
" does not work for Code::Blocks. And I couldn't warm up for a global deactivation. So I decided to emulate the security enhancements for the demanded string functions in Code::Blocks.
These are the additional declarations within SimpleWString.hpp:
#if defined(__GNUC__) || defined(__MINGW32__)
typedef int errno_t;
errno_t wcsncpy_s(wchar_t* wszDst, size_t nDstSize, const wchar_t* wszSrc, size_t nCopyCnt);
errno_t wcscpy_s(wchar_t* wszDst, size_t nDstSize, const wchar_t* wszSrc);
#define swprintf_s(wszDst, nDstSize, wszFormat, ...) swprintf(wszDst, wszFormat, __VA_ARGS__)
#endif
And this is how the additional implementations look like within SimpleWString.cpp:
#if defined(__GNUC__) || defined(__MINGW32__)
errno_t wcsncpy_s(wchar_t* wszDst, size_t nDstSize, const wchar_t* wszSrc, size_t nCopyCnt)
{
if (wszDst == NULL)
return ENOMEM;
if (&(wszDst[nDstSize]) > wszSrc && wszDst < &(wszSrc[nCopyCnt]))
return EPERM;
if (&(wszSrc[nCopyCnt]) > wszDst && wszSrc < &(wszDst[nDstSize]))
return EPERM;
if (nDstSize > nCopyCnt)
{
wcsncpy(wszDst, wszSrc, nCopyCnt);
for (size_t nIndex = nCopyCnt; nIndex < nDstSize; nIndex++)
wszDst[nIndex] = (wchar_t)0;
return 0;
}
if (nDstSize == nCopyCnt && wszSrc[nCopyCnt] == (wchar_t)0)
{
wcsncpy(wszDst, wszSrc, nCopyCnt);
return 0;
}
return ERANGE;
}
errno_t wcscpy_s(wchar_t* wszDst, size_t nDstSize, const wchar_t* wszSrc)
{
return wcsncpy_s(wszDst, nDstSize, wszSrc, wcslen(wszSrc));
}
#endif
4. What remains now is diligent work to remove the warnings within Visual Studio because the Microsoft compiler checks the syntax much stricter than MinGW.
After years of exclusive programming in C#, it was once again great fun to program in C++ and to have full control over every bit and every function call - even if you have to act more carefully than in C#.
It is a pity that the C/C++ Windows libraries are either over-engineered (e.g. MFC) or/and not equipped with a comfort comparable to C# (e.g. LINQ).
- 12th December, 2019: Initial version
- 9th January, 2020: Update to version 0.2
- 10th June, 2020: Update to version 0.3
- 10th October, 2020: Update to version 0.4
- 27th November, 2020: Update to version 0.5
- 31st January, 2021: Update to version 0.6