What's so special about "ride.exe" anyway? - windows

I just bumped into an obscure issue, while implementing a prototype for a native Win32 UI using Direct2D/DirectWrite for high performance text rendering. Things went fine and looked promising: Resizing the application window was devoid of any lag, and the entire text rendering was as solid as one could expect.
That is, until I decided to rename the binary to "ride.exe". At that point, the OS opted to force my application into "glitch mode". Resizing suddenly introduced a very noticeable lag, combined with gaps when sizing up, that would only eventually fill again. Plus, text output got all jumpy, with the perceived font size varying. Which is odd considering that I only ever create the font resource at application startup.
All those artifacts instantly go away, if I rename the binary to anything else, and instantly return, when I choose to call it "ride.exe" again. No recompilation or linking involved, it's literally just a rename operation.
I'm lost at this point, though it looks a lot like the OS is using some heuristic that involves the image name to decide, how to execute the code. So, what's going on here?
This is a complete repro to illustrate the issue:
#include <Windows.h>
#include <d2d1.h>
#include <d2d1_1helper.h>
#include <dwrite.h>
#pragma comment(lib, "D2d1.lib")
#pragma comment(lib, "Dwrite.lib")
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void create_graphics_resources(HWND const hwnd) noexcept;
void discard_graphics_resources() noexcept;
void resize_render_target(HWND const hwnd) noexcept;
void create_dw_resources() noexcept;
void discard_dw_resources() noexcept;
ID2D1Factory1* pd2d_factory { nullptr };
ID2D1HwndRenderTarget* prender_target { nullptr };
ID2D1SolidColorBrush* ptext_color_brush { nullptr };
IDWriteFactory* pdw_factory { nullptr };
IDWriteTextFormat* pdw_text_format { nullptr };
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow)
{
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
WNDCLASSEXW wcex {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszClassName = L"d2d_mcve";
RegisterClassExW(&wcex);
auto const hWnd { CreateWindowExW(0, L"d2d_mcve", L"D2D MCVE", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT,
0, nullptr, nullptr, hInstance, nullptr) };
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg {};
// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CoUninitialize();
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE: {
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, D2D1_FACTORY_OPTIONS {}, &pd2d_factory);
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(pdw_factory),
reinterpret_cast<IUnknown**>(&pdw_factory));
}
break;
case WM_DESTROY:
discard_dw_resources();
discard_graphics_resources();
pdw_factory->Release();
pd2d_factory->Release();
PostQuitMessage(0);
break;
case WM_SIZE:
resize_render_target(hWnd);
InvalidateRect(hWnd, nullptr, FALSE);
break;
case WM_PAINT: {
PAINTSTRUCT ps {};
BeginPaint(hWnd, &ps);
create_graphics_resources(hWnd);
prender_target->BeginDraw();
prender_target->Clear(D2D1::ColorF(D2D1::ColorF::Black));
create_dw_resources();
auto const target_size { prender_target->GetSize() };
prender_target->SetTransform(D2D1::Matrix3x2F::Identity());
auto const& placeholder_text { L"Lorem ipsum dolor sit amet, consectetur adipiscing elit." };
prender_target->DrawTextW(placeholder_text, ARRAYSIZE(placeholder_text) - 1, pdw_text_format,
D2D1::RectF(0.0f, 0.0f, target_size.width, target_size.height),
ptext_color_brush);
auto const hr { prender_target->EndDraw() };
if (hr == D2DERR_RECREATE_TARGET)
{
discard_graphics_resources();
InvalidateRect(hWnd, nullptr, FALSE);
}
EndPaint(hWnd, &ps);
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
void create_graphics_resources(HWND const hwnd) noexcept
{
if (prender_target == nullptr)
{
RECT rc {};
GetClientRect(hwnd, &rc);
auto const size { D2D1::SizeU(static_cast<UINT32>(rc.right), static_cast<UINT32>(rc.bottom)) };
pd2d_factory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hwnd, size), &prender_target);
prender_target->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 1.0f), &ptext_color_brush);
}
}
void discard_graphics_resources() noexcept
{
if (ptext_color_brush != nullptr)
{
ptext_color_brush->Release();
ptext_color_brush = nullptr;
}
if (prender_target != nullptr)
{
prender_target->Release();
prender_target = nullptr;
}
}
void resize_render_target(HWND const hwnd) noexcept
{
if (prender_target != nullptr)
{
RECT rc {};
GetClientRect(hwnd, &rc);
auto const size { D2D1::SizeU(static_cast<UINT32>(rc.right), static_cast<UINT32>(rc.bottom)) };
prender_target->Resize(size);
}
}
void create_dw_resources() noexcept
{
if (pdw_text_format == nullptr)
{
pdw_factory->CreateTextFormat(L"Courier New", nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL, 48.0f, L"en-US", &pdw_text_format);
}
}
void discard_dw_resources() noexcept
{
if (pdw_text_format != nullptr)
{
pdw_text_format->Release();
pdw_text_format = nullptr;
}
}
Save the source code as "main.cpp".
Compile the code using the following command line:
cl.exe /O2 /EHsc /D "NDEBUG" /D "UNICODE" /MD main.cpp user32.lib ole32.lib
Create a directory named "choosing_a_trident_for_a_buttplug_is_not_prudent" anywhere in the filesystem and copy "main.exe" into that directory.
Launch "main.exe" and observe the behavior of the application when resizing the main window.
Rename the copy of "main.exe" under "choosing_a_trident_for_a_buttplug_is_not_prudent" to "ride.exe", launch "ride.exe" and observe the behavior when resizing the main window.
The last step exhibits all sorts of visual glitches on my system (Windows 10 19041.330). The application instantly returns to normal operation when either renaming the executable image to anything but "ride.exe", or renaming every parent directory to no longer contain the phrase "ride". Casing doesn't make a difference.

I haven't done a thorough analysis on what exactly is happening, yet it appears that the system is using a - dare I say - somewhat (?) crude strategy to fix broken applications. As usual, the Hall of Shame is to be found in the registry:
On my system there's a key named 8e89aac5-6105-47bd-bfb3-6ed01bda07b0 under HKU\<sid>\System\GameConfigStore\Children. Leaving the obvious WTF aside for a minute, that GameConfigStore is seemingly a System-vital key for my user account, that key has values named ExeParentDirectory and Flags, with associated data RIDE (REG_SZ) and 0x11 (REG_DWORD), respectively.
The way I read this is: If a user identified by <sid> attempts to launch a binary called ride.exe (hardcoded, somewhere), that resides under a directory whose name contains RIDE, first apply the flags 0x11 to something, for sports, just to get their attention, before allowing the primary thread to execute. This is to ensure that broken app X continues to appear not broken, when <user> decides to change the environment that would otherwise exhibit the brokenness.
Even if this were to break other, properly written applications.
Now this may sound bad, but you have to give Microsoft credit where credit is due: Out of all possible combinations of characters that form a valid registry key name to communicate with us, devs, they have opted to keep this down to earth, matter of fact, professional, and use GUIDs.
I mean...
They could have just as well chosen to following:
Quality doesn't matter, ROI does
What do you mean, you did RTFM?!
Dude, what contractual guarantees? LOL, n00b
This is just a friendly FU. But no, we don't care about devs that actually TRY not to suck
But they didn't! So... thanks? I guess.

Related

Preview handler only works when application is invoked from Explorer

I made a simple application that creates a window and then uses preview handlers to display a preview of a file passed as an argument to the application, see code below.
This works perfectly fine when just drag'n'dropping the file to preview onto the .exe file (or alternatively just hard coding the file path instead and then launching the .exe directly from explorer), but fails for almost all preview handlers when invoking it from a terminal (powershell, cmd, whatever) with e.g. ./main.exe testfile.docx. It also fails when trying to invoke it from other processes.
The issue i described happens (among others) for the following preview handlers:
MS Office preview handler for excel files, CLSID {00020827-0000-0000-C000-000000000046}
MS Office preview handler for word files, CLSID {84F66100-FF7C-4fb4-B0C0-02CD7FB668FE}
Edge preview handler for pdf files, CLSID {3A84F9C2-6164-485C-A7D9-4B27F8AC009E}
MS Office preview handler for power point files, CLSID {65235197-874B-4A07-BDC5-E65EA825B718}. For the power point preview handler, the initialize call to the IInitializeWithFile interface fails hresult 0x86420003
It works fine for the following preview handlers:
Windows provided preview handler for text files, CLSID {1531d583-8375-4d3f-b5fb-d23bbd169f22}
Windows provided preview handler for html files, CLSID {f8b8412b-dea3-4130-b36c-5e8be73106ac}
When i say it "fails", i mean that it just doesn't display a preview, none of the function calls fail. See images:
test.xlsx excel file dragged onto .exe file, same result when hardcoding path to test.xlsx and just starting the application from explorer:
Path to test.xlsx passed as command line argument (./main.exe ./test.xlsx in powershell):
I am not sure, what would cause this or where to even start looking or what to search for, so any input on this would be appreciated.
Note: I asked a similar question a few days ago, but that was a seperate issue.
Compile with cl /std:c++20 /EHsc main.cpp, expects path to a file to preview as first command line parameter. Since i used a bit of winrt, this requires windows 10.
#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "WindowsApp.lib")
#pragma comment(lib, "Ole32.lib")
#include <iostream>
#include <string>
#include <string_view>
#include <array>
#include <filesystem>
#include <Windows.h>
#include <shlwapi.h>
#include <winrt/base.h>
#include <ShObjIdl.h>
class Preview{
winrt::com_ptr<IPreviewHandler> mPreviewHandler;
bool mIsActive = false;
bool mIsInitialized = false;
std::filesystem::path mFilePath;
HWND mHwnd;
RECT mRect;
public:
Preview(const std::filesystem::path& filePath, const HWND hwnd, const RECT& rect):
mFilePath(filePath), mHwnd(hwnd), mRect(rect){
createPreviewHandler();
initialize();
};
void setRect(const RECT& rect){
mPreviewHandler->SetRect(&rect);
mRect = rect;
}
void setWindow(const HWND hwnd, const RECT& rect){
mPreviewHandler->SetWindow(hwnd, &rect);
mHwnd = hwnd;
setRect(rect);
}
void startPreview(){
if(!mIsActive){
if(!mIsInitialized){
initialize();
}
mPreviewHandler->DoPreview();
setRect(mRect);
mIsActive = true;
}
}
void stopPreview(){
if(mIsActive){
mPreviewHandler->Unload();
mIsActive = false;
mIsInitialized = false;
}
}
private:
void createPreviewHandler(){
CLSID previewHandlerId = getShellExClsidForType(mFilePath.extension());
mPreviewHandler.capture(CoCreateInstance, previewHandlerId, nullptr, CLSCTX_LOCAL_SERVER);
}
CLSID getShellExClsidForType(const std::wstring& extension){
winrt::hresult res;
DWORD size;
std::array<wchar_t, 39> interfaceIdWstr;
size = StringFromGUID2(IID_IPreviewHandler, interfaceIdWstr.data(), interfaceIdWstr.size());
if(!size){
winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError()));
}
std::array<wchar_t, 39> exIdWstr;
res = AssocQueryStringW(ASSOCF_INIT_DEFAULTTOSTAR,
ASSOCSTR_SHELLEXTENSION,
extension.c_str(),
interfaceIdWstr.data(),
exIdWstr.data(),
&size);
winrt::check_hresult(res);
CLSID exId;
res = IIDFromString(exIdWstr.data(), &exId);
winrt::check_hresult(res);
return(exId);
}
void initialize(){
initializeFromPath(mFilePath);
setWindow(mHwnd, mRect);
mIsInitialized = true;
}
void initializeFromPath(const std::filesystem::path& filePath){
if(!initWithFile(filePath) && !initWithStream(filePath)){
winrt::throw_hresult(E_NOINTERFACE);
}
}
bool initWithFile(const std::filesystem::path& filePath){
if(!mPreviewHandler.try_as<IInitializeWithFile>()){
return(false);
}
winrt::check_hresult(mPreviewHandler.as<IInitializeWithFile>()->Initialize(filePath.c_str(), STGM_READ));
return(true);
}
bool initWithStream(const std::filesystem::path& filePath){
if(!mPreviewHandler.try_as<IInitializeWithStream>()){
return(false);
}
winrt::com_ptr<IStream> stream;
stream.capture([](LPCWSTR pszFile, DWORD grfMode, REFIID riid, void **ppstm)
{return(SHCreateStreamOnFileEx(pszFile, grfMode, 0, false, nullptr, reinterpret_cast<IStream**>(ppstm)));},
filePath.c_str(), STGM_READ | STGM_SHARE_DENY_WRITE | STGM_FAILIFTHERE);
winrt::check_hresult(mPreviewHandler.as<IInitializeWithStream>()->Initialize(stream.get(), STGM_READ));
return(true);
}
};
HMODULE getCurrentModule(){
HMODULE hModule;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(LPCTSTR)getCurrentModule,
&hModule);
return(hModule);
}
LRESULT windowProc(HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam){
LPARAM ret = 0;
switch(msg){
case WM_CLOSE:
{
PostQuitMessage(0);
ret = 0;
}break;
default:
{
ret = DefWindowProc(hwnd, msg, wParam, lParam);
}break;
}
return(ret);
}
HWND createTestWindow(){
WNDCLASSW wndClass;
wndClass.style = 0;
wndClass.lpfnWndProc = windowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = getCurrentModule();
wndClass.hIcon = nullptr;
wndClass.hCursor = nullptr;
wndClass.hbrBackground = nullptr;
wndClass.lpszMenuName = nullptr;
wndClass.lpszClassName = L"test";
ATOM wndAtom = RegisterClassW(&wndClass);
if(!wndAtom){
winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError()));
}
HWND window = CreateWindowExW(0,
L"Test",
L"",
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
wndClass.hInstance,
nullptr);
if(!window){
winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError()));
}
ShowWindow(window, SW_NORMAL);
return(window);
}
int main(int argc, char *argv[]){
try{
winrt::check_hresult(CoInitialize(nullptr));
if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)){
winrt::throw_hresult(E_FAIL);
}
if(argc != 2){
return(1);
}
std::filesystem::path filePath(argv[1]);
HWND window = createTestWindow();
RECT rect;
GetClientRect(window, &rect);
Preview preview(filePath, window, rect);
preview.startPreview();
MSG msg;
while(GetMessageW(&msg, nullptr, 0, 0) != 0){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}catch(winrt::hresult_error err){
std::wcout << "0x" << std::hex << err.code() << " " << static_cast<std::wstring_view>(err.message());
}catch(...){}
}
Call GetFullPathName on argv[1]. The shell functions can't parse a relative path into a pidl.

How to programmatically start an application on a specific monitor on Windows 10?

I want to write a program that needs sometimes to start processes of another applications (mainly Sumatra PDF) on Windows 10, version 1803 (April 2018 Update).
These applications should be started on a specific monitor. I also want to be able to close the processes when needed.
The preferred languages are C# and Java, but any help is appreciated.
EDIT
I've tried to use the ShellExecuteExW function suggested by IInspectable in C++ code directly, but it doesn't work, as applications appear on the main monitor. I must have certainly made a mistake as I am absolutely new to WinAPI and know very little C++.
#include <Windows.h>
HMONITOR monitors[2]; // As it's only a test and I have currently only two monitors.
int monitorsCount = 0;
BOOL CALLBACK Monitorenumproc(HMONITOR hMonitor, HDC hdc, LPRECT lprect, LPARAM lparam)
{
monitors[monitorsCount] = hMonitor;
monitorsCount++;
return TRUE;
}
int main()
{
EnumDisplayMonitors(NULL, NULL, Monitorenumproc, 0);
_SHELLEXECUTEINFOW info;
ZeroMemory(&info, sizeof(info));
info.cbSize = sizeof(info);
info.fMask = SEE_MASK_HMONITOR;
//info.lpVerb = L"open";
info.lpFile = L"C:\\Windows\\System32\\cmd.exe";
info.nShow = SW_SHOW;
info.hMonitor = monitors[1]; // Trying to start on the second monitor.
ShellExecuteExW(&info);
return 0;
}
As suggested by others, this is intended behavior of Windows and for the good reasons.
Also, you can not rely on default window placement atleast for SumatraPDF, since it surely does not use CW_USEDEFAULT, and instead stores these values in :
%USERPROFILE%\AppData\Roaming\SumatraPDF\SumatraPDF-settings.txt
There are multiple options though:
Use third party tools that monitor top level windows and based on pre-configured rules moves them to specified display. E.g. DisplayFusion, etc.
Use ligher weight solutions like AutoHotkey/AutoIt.
Try and do this in code itself. Following is a working solution. I smoke tested on my box.
Disclaimer: I have not written the entire code, for saving time I pulled it up from couple of sources, tweaked it, glued it together, and tested with SumantraPDF. Do also note, that this code is not of highest standards, but solves your problem, and will act as a can-be-done example.
C++ code: (scroll down for C# code)
#include <Windows.h>
#include <vector>
// 0 based index for preferred monitor
static const int PREFERRED_MONITOR = 1;
struct ProcessWindowsInfo
{
DWORD ProcessID;
std::vector<HWND> Windows;
ProcessWindowsInfo(DWORD const AProcessID)
: ProcessID(AProcessID)
{
}
};
struct MonitorInfo
{
HMONITOR hMonitor;
RECT rect;
};
BOOL WINAPI EnumProcessWindowsProc(HWND hwnd, LPARAM lParam)
{
ProcessWindowsInfo *info = reinterpret_cast<ProcessWindowsInfo*>(lParam);
DWORD WindowProcessID;
GetWindowThreadProcessId(hwnd, &WindowProcessID);
if (WindowProcessID == info->ProcessID)
{
if (GetWindow(hwnd, GW_OWNER) == (HWND)0 && IsWindowVisible(hwnd))
{
info->Windows.push_back(hwnd);
}
}
return true;
}
BOOL CALLBACK Monitorenumproc(HMONITOR hMonitor, HDC hdc, LPRECT lprect, LPARAM lParam)
{
std::vector<MonitorInfo> *info = reinterpret_cast<std::vector<MonitorInfo>*>(lParam);
MonitorInfo monitorInfo = { 0 };
monitorInfo.hMonitor = hMonitor;
monitorInfo.rect = *lprect;
info->push_back(monitorInfo);
return TRUE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
// NOTE: for now this code works only when the window is not already visible
// could be easily modified to terminate existing process as required
SHELLEXECUTEINFO info = { 0 };
info.cbSize = sizeof(info);
info.fMask = SEE_MASK_NOCLOSEPROCESS;
info.lpFile = L"C:\\Program Files\\SumatraPDF\\SumatraPDF.exe";
info.nShow = SW_SHOW;
std::vector<MonitorInfo> connectedMonitors;
// Get all available displays
EnumDisplayMonitors(NULL, NULL, Monitorenumproc, reinterpret_cast<LPARAM>(&connectedMonitors));
if (ShellExecuteEx(&info))
{
WaitForInputIdle(info.hProcess, INFINITE);
ProcessWindowsInfo Info(GetProcessId(info.hProcess));
// Go though all windows from that process
EnumWindows((WNDENUMPROC)EnumProcessWindowsProc, reinterpret_cast<LPARAM>(&Info.ProcessID));
if (Info.Windows.size() == 1)
{
// only if we got at most 1 window
// NOTE: applications can have more than 1 top level window. But at least for SumtraPDF this works!
if (connectedMonitors.size() >= PREFERRED_MONITOR)
{
// only move the window if we were able to successfully detect available monitors
SetWindowPos(Info.Windows.at(0), 0, connectedMonitors.at(PREFERRED_MONITOR).rect.left, connectedMonitors.at(PREFERRED_MONITOR).rect.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}
CloseHandle(info.hProcess);
}
return 0;
}
To emphasize one of my comments in code. This code will only work if the process in question is not already running. You can tweak the code as per your requirements otherwise.
Update: Added C# code below, as I realized OP prefers C#. This code also has the termination logic cooked in.
C# code:
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;
private const int PREFERRED_MONITOR = 1;
static void Main(string[] args)
{
// NOTE: you will have to reference System.Windows.Forms and System.Drawing (or
// equivalent WPF assemblies) for Screen and Rectangle
// Terminate existing SumatraPDF process, else we will not get the MainWindowHandle by following method.
List<Process> existingProcesses = Process.GetProcessesByName("SumatraPDF").ToList();
foreach (var existingProcess in existingProcesses)
{
// Ouch! Ruthlessly kill the existing SumatraPDF instances
existingProcess.Kill();
}
// Start the new instance of SumantraPDF
Process process = Process.Start(#"C:\Program Files\SumatraPDF\SumatraPDF.exe");
// wait max 5 seconds for process to be active
process.WaitForInputIdle(5000);
if (Screen.AllScreens.Length >= PREFERRED_MONITOR)
{
SetWindowPos(process.MainWindowHandle,
IntPtr.Zero,
Screen.AllScreens[PREFERRED_MONITOR].WorkingArea.Left,
Screen.AllScreens[PREFERRED_MONITOR].WorkingArea.Top,
0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}
SEE_MASK_HMONITOR is only a request. Applications can choose their own window placement. SEE_MASK_HMONITOR only works when the executed application relies on default window placement, i.e. it creates its first top-level window with CW_USEΒ­DEFAULT
So what you want is not generally possible. Your code is as good as you can get, if you don't control the launched application.

GDI - Can I use the new Windows 10 Segoe UI Emoji colored font with DrawText?

I'm creating a c++ project using Embarcadero RAD Studio (10.2 Tokyo starter) and the Windows GDI to draw text, via the DrawText() function.
I recently saw that Windows 10 provides a new "Segoe UI Emoji" font, that potentially allows text functions to draw colored emojis. I found several examples using Direct2D, but none with pure GDI functions.
I also tried a simple code, like this:
HDC hDC = ::GetDC(Handle);
std::auto_ptr<TCanvas> pCanvas(new TCanvas());
pCanvas->Handle = hDC;
pCanvas->Brush->Color = clWhite;
pCanvas->Brush->Style = bsSolid;
pCanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));
const std::wstring text = L"Test πŸ˜€ 😬 😁 πŸ˜‚ πŸ˜ƒ πŸ˜„ πŸ˜… πŸ˜†";
TRect textRect(10, 10, ClientWidth - 10, ClientHeight - 10);
hFont = ::CreateFont(-40,
0,
0,
0,
FW_DONTCARE,
FALSE,
FALSE,
FALSE,
DEFAULT_CHARSET,
OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY,
VARIABLE_PITCH,
L"Segoe UI Emoji");
::SelectObject(hDC, hFont);
::DrawTextW(hDC,
text.c_str(),
text.length(),
&textRect,
DT_LEFT | DT_TOP | DT_SINGLELINE);
::DeleteObject(hFont);
The output result sounds good in terms of symbols, but they are drawn in black&white, without colors, as you can see on the screenshot below:
I could not find any additional options that may allow the text to be drawn using colored symbols instead of black&white. Is there a way to activate the support of the color in GDI DrawText() function, and if yes, how to do that? Or only Direct2D may draw colored emojis?
EDITED on 30.10.2017
As the GDI cannot do the job (unfortunately, and as I thought) I publish here the Direct2D version of the above code, that worked for me.
const std::wstring text = L"Test πŸ˜€ 😬 😁 πŸ˜‚ πŸ˜ƒ πŸ˜„ πŸ˜… πŸ˜†";
HDC hDC = ::GetDC(Handle);
std::auto_ptr<TCanvas> pGDICanvas(new TCanvas());
pGDICanvas->Handle = hDC;
pGDICanvas->Brush->Color = clWhite;
pGDICanvas->Brush->Style = bsSolid;
pGDICanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));
::D2D1_RECT_F textRect;
textRect.left = 10;
textRect.top = 10;
textRect.right = ClientWidth - 10;
textRect.bottom = ClientHeight - 10;
std::auto_ptr<TDirect2DCanvas> pCanvas(new TDirect2DCanvas(hDC, TRect(0, 0, ClientWidth, ClientHeight)));
// configure Direct2D font
pCanvas->Font->Size = 40;
pCanvas->Font->Name = L"Segoe UI Emoji";
pCanvas->Font->Orientation = 0;
pCanvas->Font->Pitch = System::Uitypes::TFontPitch::fpVariable;
pCanvas->Font->Style = TFontStyles();
// get DirectWrite text format object
_di_IDWriteTextFormat pFormat = pCanvas->Font->Handle;
if (!pFormat)
return;
pCanvas->RenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
::D2D1_COLOR_F color;
color.r = 0.0f;
color.g = 0.0f;
color.b = 0.0f;
color.a = 1.0f;
::ID2D1SolidColorBrush* pBrush = NULL;
// create solid color brush, use pen color if rect is completely filled with outline
pCanvas->RenderTarget->CreateSolidColorBrush(color, &pBrush);
if (!pBrush)
return;
// set horiz alignment
pFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
// set vert alignment
pFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
// set reading direction
pFormat->SetReadingDirection(DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
// set word wrapping mode
pFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
IDWriteInlineObject* pInlineObject = NULL;
::DWRITE_TRIMMING trimming;
trimming.delimiter = 0;
trimming.delimiterCount = 0;
trimming.granularity = DWRITE_TRIMMING_GRANULARITY_NONE;
// set text trimming
pFormat->SetTrimming(&trimming, pInlineObject);
pCanvas->BeginDraw();
pCanvas->RenderTarget->DrawText(text.c_str(), text.length(), pFormat, textRect, pBrush,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
pCanvas->EndDraw();
Of course this code will draw colored emojis only on the currently most recent versions of Windows 10, and above. On previous versions the text will be drawn as above (and the code may not compile).
Bonus Reading
MSDN: Color Fonts with DirectWrite, Direct2D, and Win2D
GDI does not support color fonts (even if you go the full Uniscribe route), you have to use Direct2D if you want color font support. It makes sense that the simpler GDI APIs don't support color fonts as color fonts require using OpenType tags and none of DrawText/TextOut provide that level of control, Uniscribe allows for such tags but has simply not been extended to support color fonts.
You can use DirectWrite to draw colored emojis onto a bitmap in memory DC, then BitBlt() to your destination DC.
Basically, you need to implement a custom IDWriteTextRenderer class and call IDWriteTextLayout::Draw() with your renderer, then copy the result.
In your class, you retrieve IDWriteGdiInterop from IDWriteFactory and call IDWriteGdiInterop::CreateBitmapRenderTarget() to get the bitmap render target; call IDWriteFactory::CreateMonitorRenderingParams() to get the rendering parameters, and call IDWriteFactory::CreateTextFormat() to set up your text format.
The only significant method is DrawGlyphRun(), where you get IDWriteColorGlyphRunEnumerator with IDWriteFactory2::TranslateColorGlyphRun() and with each color run, call IDWriteBitmapRenderTarget::DrawGlyphRun() to do the work for you.
Just remember to update the render target/parameters when the window size/position changes.
You may reference this MSDN documentation:
Render to a GDI Surface
https://msdn.microsoft.com/en-us/library/windows/desktop/ff485856(v=vs.85).aspx
As mentioned by #SoronelHaetir's answer above, the win32 graphics device interface (GDI) that is used when working with static window components (via WC_STATIC) doesn't support colorized fonts. In order to display colored emojis and/or "fancier" text (i.e. colored text, etc.), you'll need to use the Direct2D API.
The example above provided by original poster (OP) #Jean-Milost Reymond isn't something that is immediately able to be compiled and tried out by the reader. Also, it uses the TCanvas class, which isn't strictly needed when working directly with the Win32 API.
For people looking for a complete example that works on the bare-metal Win32 API and can be immediately copied and pasted and compiled, then here is the code that will compile in Visual Studio:
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <wchar.h>
#include <math.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>
#include <string>
#include <cassert>
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "Dwrite.lib")
HWND WindowHandle = nullptr;
IDWriteFactory * DWriteFactory = nullptr;
ID2D1Factory * Direct2dFactory = nullptr;
ID2D1HwndRenderTarget * RenderTarget = nullptr;
ID2D1SolidColorBrush * TextBlackBrush = nullptr;
const std::wstring DISPLAY_TEXT = L"Test πŸ˜€ 😬 😁 πŸ˜‚ πŸ˜ƒ πŸ˜„ πŸ˜… πŸ˜†";
template<class Interface>
inline void SafeRelease (Interface ** ppInterfaceToRelease);
LRESULT CALLBACK WndProc (HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam);
HRESULT CreateDeviceIndependentResources ();
HRESULT InitInstance (HINSTANCE hInstance, int nCmdShow);
void DiscardDeviceResources ();
HRESULT OnRender ();
HRESULT CreateDeviceResources ();
template<class Interface>
inline void SafeRelease (Interface ** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release ();
(*ppInterfaceToRelease) = NULL;
}
}
HRESULT OnRender ()
{
HRESULT Result = S_OK;
D2D1_SIZE_F RenderCanvasArea = { 0 };
IDWriteTextFormat * TextFormat = nullptr;
D2D1_RECT_F TextCanvasArea = { 0 };
Result = CreateDeviceResources ();
if (SUCCEEDED (Result))
{
RenderTarget->BeginDraw ();
RenderCanvasArea = RenderTarget->GetSize ();
RenderTarget->Clear (D2D1::ColorF (D2D1::ColorF::White));
if (SUCCEEDED (Result))
{
Result = DWriteFactory->CreateTextFormat (L"Segoe UI",
nullptr,
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
25.0f,
L"en-us",
&TextFormat);
TextFormat->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_LEADING);
TextFormat->SetParagraphAlignment (DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
TextFormat->SetReadingDirection (DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
TextFormat->SetWordWrapping (DWRITE_WORD_WRAPPING_WRAP);
if (SUCCEEDED (Result) &&
TextFormat != nullptr)
{
TextCanvasArea = D2D1::RectF (0,
0,
RenderCanvasArea.width,
RenderCanvasArea.height);
RenderTarget->DrawTextW (DISPLAY_TEXT.c_str (),
static_cast <UINT32> (DISPLAY_TEXT.size ()),
TextFormat,
TextCanvasArea,
TextBlackBrush,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
}
}
Result = RenderTarget->EndDraw ();
}
if (Result == D2DERR_RECREATE_TARGET)
{
DiscardDeviceResources ();
Result = S_OK;
}
return Result;
}
HRESULT CreateDeviceResources ()
{
HRESULT Result = S_OK;
RECT rc = { 0 };
if (!RenderTarget)
{
GetClientRect (WindowHandle,
&rc);
D2D1_SIZE_U size = D2D1::SizeU (rc.right - rc.left,
rc.bottom - rc.top);
// Create a Direct2D render target.
Result = Direct2dFactory->CreateHwndRenderTarget (D2D1::RenderTargetProperties (),
D2D1::HwndRenderTargetProperties (WindowHandle, size),
&RenderTarget);
if (SUCCEEDED (Result))
{
// Create a blue brush.
Result = RenderTarget->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black),
&TextBlackBrush);
}
}
return Result;
}
void DiscardDeviceResources ()
{
SafeRelease (&RenderTarget);
SafeRelease (&TextBlackBrush);
}
HRESULT InitInstance (HINSTANCE hInstance,
int nCmdShow)
{
HRESULT Result = S_OK;
// Create the window.
WindowHandle = CreateWindow (L"D2DTextDemo",
L"Direct2D Text Demo Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
600,
200,
nullptr,
nullptr,
hInstance,
nullptr);
if (WindowHandle == nullptr)
{
Result = E_POINTER;
}
else
{
ShowWindow (WindowHandle,
nCmdShow);
UpdateWindow (WindowHandle);
}
return Result;
}
HRESULT CreateDeviceIndependentResources ()
{
HRESULT Result = S_OK;
Result = D2D1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED,
&Direct2dFactory);
if (SUCCEEDED (Result))
{
Result = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
__uuidof (IDWriteFactory),
reinterpret_cast <IUnknown **> (&DWriteFactory));
}
return Result;
}
LRESULT CALLBACK WndProc (HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
LRESULT Result = 0;
switch (message)
{
case WM_SIZE:
{
UINT width = LOWORD (lParam);
UINT height = HIWORD (lParam);
if (RenderTarget != nullptr)
{
// Note: This method can fail, but it's okay to ignore the
// error here, because the error will be returned again
// the next time EndDraw is called.
RenderTarget->Resize (D2D1::SizeU (width,
height));
}
}
break;
case WM_DISPLAYCHANGE:
{
InvalidateRect (hwnd, nullptr, FALSE);
}
break;
case WM_PAINT:
{
OnRender ();
ValidateRect (hwnd,
nullptr);
}
break;
case WM_DESTROY:
{
PostQuitMessage (0);
Result = 1;
}
break;
default:
{
Result = DefWindowProc (hwnd,
message,
wParam,
lParam);
}
break;
}
return Result;
}
int APIENTRY wWinMain (_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER (hInstance);
UNREFERENCED_PARAMETER (hPrevInstance);
UNREFERENCED_PARAMETER (lpCmdLine);
UNREFERENCED_PARAMETER (nCmdShow);
HRESULT ExitCode = S_OK;
MSG NextMessage = { 0 };
WNDCLASSEX wcex = { 0 };
ATOM WindowClassId = 0;
wcex.cbSize = sizeof (WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof (LONG_PTR);
wcex.hInstance = hInstance;
wcex.hbrBackground = nullptr;
wcex.lpszMenuName = nullptr;
wcex.hCursor = LoadCursor (nullptr, IDI_APPLICATION);
wcex.lpszClassName = L"D2DTextDemo";
if (SUCCEEDED (CoInitialize (nullptr)))
{
WindowClassId = RegisterClassEx (&wcex);
if (WindowClassId == 0)
{
ExitCode = HRESULT_FROM_WIN32 (GetLastError ());
}
if (SUCCEEDED (ExitCode))
{
ExitCode = CreateDeviceIndependentResources ();
}
if (SUCCEEDED (ExitCode))
{
ExitCode = InitInstance (hInstance,
nCmdShow);
}
if (SUCCEEDED (ExitCode))
{
while (GetMessage (&NextMessage,
nullptr,
0,
0))
{
TranslateMessage (&NextMessage);
DispatchMessage (&NextMessage);
}
}
CoUninitialize ();
SafeRelease (&Direct2dFactory);
SafeRelease (&DWriteFactory);
SafeRelease (&RenderTarget);
}
return ExitCode;
}
(The above example doesn't have perfect error handling, so it's important to audit this example code if the following is used in any project the reader is working on.)
Before attempting to compile this in Visual Studio, make sure your project has the "SubSystem" linker option set to Windows /SUBSYSTEM:WINDOWS.
Once compiled successfully, the following application window will appear:
I tested this coded example in Visual Studio 2022 Community Edition on Windows 11 with success.
Reference(s):
Creating a Simple Direct2D Application
Tutorial: Getting Started with DirectWrite

How to call and use UnregisterClass?

Using Visual Studio 2013
I have an application that could potentially use up to about 20 window classes, but not all at the same time. In order to save on space I decided to Unregister those not needed any more before starting another batch of window classes, but I could not make the UnregisterClass function work.
I called Unregister at WM_DESTROY and/or WM_NCDESTROY but it always returned error message 1412 'Class still has open window'. Perhaps the Unregister call in WM_DESTROY failed because the window had not been destroyed yet, but I did not expect the call in WM_NCDESTROY to fail since this message is sent after destruction of the window.
The only way I could make UnregisterClass work was to call PostQuitMessage at either WM_DESTROY or WM_NCDESTROY. Then UnregisterClass would work after the message loop just before the whole application exits, but I want to start another batch of classes from inside the application, not to have to start it all over.
I am submitting a test program that shows the problem. It is Win32Project7, a program provided by Visual Studio 2013 with two tiny additions by myself - wrapped Messagebox (mbox) and a procedure to call unregister (tryunreg).
One extreme would be to register 20 window classes just to have them ready when needed, another would be to use a single windowclass and multiplex on HWND. Not too keen on any of these.
Questions:
Have I made mistakes or wrong assumptions in this program?
Is there a way to make unregisterclass work without having to close the program?
how much space would a typical windowclass register need? Is it likely to be KB or MB? Any way to experiment to find out?
Have Googled on this. Did not find anything that is not already in the documentation, e.g. like unregister is automatic on exit from application. Stackoverflow has two posts similar to this, but with no answers.
The code:
I placed the code like this:
<pre>
program fragments
</pre>
enclosed between html pre tags
but the post was not sent. Error message said the text was formatted like
a program but the indentation was not 4 spaces. Originally it wasn't but I changed it, but it still was not sent.
Have never sent questions on this forum, so I am doing something wrong. What?
Here is the code I did not know how to send in my original post.
Better late than never.
// Win32Project7.cpp : Defines the entry point for the application.
#include "stdafx.h"
#include "Win32Project7.h"
#define MAX_LOADSTRING 100
// Global Variables:
HINSTANCE hInst;
TCHAR szTitle[MAX_LOADSTRING];
TCHAR szWindowClass[MAX_LOADSTRING];
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
static void mbox(const wchar_t * msg) // added
{
int errcode;
const wchar_t * caption = L"Info";
int res = MessageBox(NULL, msg, caption, 0);
if (res == 0)
{
errcode = GetLastError();
return; // was setting breakpoint, but never got here
// but mbox does not give any output after postquit
}
}
static void tryunreg(const wchar_t * where) // added
{
int errcode;
wchar_t outmsg[100];
BOOL b = UnregisterClass(szWindowClass, hInst);
if (!b)
{
errcode = GetLastError();
wsprintf(outmsg, L"%s: Unreg failed for classname %s errcode %d",
where, szWindowClass, errcode);
}
else
{
wsprintf(outmsg, L"%s: Unreg worked", where);
}
mbox(outmsg);
}
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
MSG msg;
HACCEL hAccelTable;
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WIN32PROJECT7, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance,
MAKEINTRESOURCE(IDC_WIN32PROJECT7));
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
tryunreg(L"After message loop" ); // added this
return (int) msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance,
MAKEINTRESOURCE(IDI_WIN32PROJECT7));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WIN32PROJECT7);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance,
MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global
// variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
BOOL b;
int errcode;
wchar_t msg[100];
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX),
hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_CLOSE: // added
// mbox(L"#wm_close before destroywindow");
DestroyWindow(hWnd);
break;
case WM_DESTROY:
tryunreg(L"#wm_destroy before postquit"); // added
PostQuitMessage(0); // in original MS code
tryunreg(L"#wm_destroy after postquit"); // added
break;
case WM_NCDESTROY: // added
tryunreg(L"#wm_NCdestroy before postquit"); // added
//PostQuitMessage(0);
tryunreg(L"#wm_NCdestroy after postquit"); // added
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
The time that UnregisterClass is needed is a dynamically loaded DLL that registers a window class. Such a library needs to ensure that the class is unregistered before it unloads, otherwise a CreateWindow for that class would make a call to code that is no longer present.
If you do choose to unregister window classes a delay can be introduced by using QueueUserAPC, however that does require changing the message loop (to one based around MsgWaitForMultipleObjectsEx and an embedded PeekMessage loop). Or you could use a thread message.
I prefer the APC because it allows for decoupling the code to be invoked from the rest of the program. For example in MFC using a thread message would require changing the message map for the thread class (the CWinApp in most cases).

Why does my Win32 application nearly crash my computer when I try rendering OpenGL to a static control?

I have a Win32 application with a static control that I want to render to using OpenGL. The problem is when I run the application, my computer comes to a screeching halt and more than once now I've had to hard-reboot it. It takes less than a second and it's really brutal. Most of my code here is ripped off of the internet or from my OpenGL bluebook. I have tried doing this several other ways, some with the same results and some with different but no more desirable results, including making the static control owner-draw, subclassing the static control, and just rendering to the control in it's parent's (the main window's) WM_PAINT message. I'm not even rendering any geometry yet, I just want to see the clear color. Most if not all of the resources I've found online either don't cover rendering to a control or using MFC instead of native Win32 API calls.
I have determined that it only happens because I am calling glClear(). If I don't call it, I don't experience the slow down, but I also don't see anything in my static control.
Some more information:
Visual Studio 2010 Ultimate
Windows SDK v7.0a
GLEW 1.7
nVidia Quadro 1600M with the latest drivers
Windows 7 x64 Ultimate
Here is my code:
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#define GLEW_STATIC
#include <windows.h>
#include <gl\glew.h>
#include <gl\wglew.h>
#include <Uxtheme.h>
#include <commctrl.h>
#include <tchar.h>
#include "resource.h"
//#define WIN32_LEAN_AND_MEAN
#define IDC_THE_BUTTON 9001
#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
struct GlobalSection
{
HINSTANCE app;
HWND wnd;
HWND button;
HWND panel;
HWND text;
HGLRC glrc;
HDC fdc;
} g;
bool initWindow(int cmdShow);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
void Init(HWND wnd);
void Render();
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nCmdShow)
{
g.app = hInst;
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(iccex);
iccex.dwICC = ICC_BAR_CLASSES|ICC_COOL_CLASSES|ICC_LISTVIEW_CLASSES|ICC_PROGRESS_CLASS|ICC_STANDARD_CLASSES|ICC_TAB_CLASSES;
InitCommonControlsEx(&iccex);
initWindow(nCmdShow);
MSG m;
ZeroMemory(&m, sizeof(m));
while(m.message != WM_QUIT)
{
Render();
if(PeekMessage(&m, NULL, 0, 0, PM_REMOVE) && !IsDialogMessage(g.wnd, &m))
{
TranslateMessage(&m);
DispatchMessage(&m);
}
}
}
bool initWindow(int cmdShow)
{
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.lpszClassName = _T("Window Party");
wc.hInstance = g.app;
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
wc.hIcon = LoadIcon(g.app, MAKEINTRESOURCE(IDI_ICON2));
wc.hIconSm = wc.hIcon;
RegisterClassEx(&wc);
g.wnd = CreateWindowEx(WS_EX_CLIENTEDGE, _T("Window Party"), _T("Window Party"), WS_SYSMENU|WS_CLIPCHILDREN|WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, 1280, 800, NULL, NULL, g.app, NULL);
ShowWindow( g.wnd, cmdShow ); // technically should be nCmdShow
return true;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
switch(message)
{
case WM_CREATE:
Init(hwnd);
return DefWindowProc(hwnd, message, wparam, lparam);
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_COMMAND:
{
int wmId = LOWORD(wparam);
int wmEvent = HIWORD(wparam);
switch(wmId)
{
case IDM_FILE_CLOSE:
PostQuitMessage(0);
break;
case IDOK:
case IDC_THE_BUTTON:
{
MessageBox(hwnd, _T("You have pressed the button!"), _T("Congraturation"), MB_OK);
}
break;
default:
return DefWindowProc(hwnd, message, wparam, lparam);
}
}
break;
case WM_KEYDOWN:
{
TCHAR buff[64] = {0};
wsprintf(buff, _T("Key Pressed: 0x%X"), (unsigned)wparam);
MessageBox(hwnd, buff, _T("YOYOYO"), MB_OK);
break;
}
default:
return DefWindowProc(hwnd, message, wparam, lparam);
}
return 0;
}
void Init(HWND wnd)
{
//Creating controls
RECT cr;
GetClientRect(wnd, &cr);
g.panel = CreateWindow(_T("STATIC"), NULL, WS_CHILDWINDOW|WS_VISIBLE|CS_OWNDC|CS_VREDRAW|CS_HREDRAW, 0, 0, cr.right, cr.bottom-26, wnd, NULL, g.app, NULL);
g.button = CreateWindow(_T("BUTTON"), _T("The Button"), WS_CHILDWINDOW|BS_DEFPUSHBUTTON|WS_VISIBLE, cr.right-160, cr.bottom-26, 160, 26, wnd, (HMENU)IDC_THE_BUTTON, g.app, NULL);
g.text = CreateWindowEx(WS_EX_CLIENTEDGE, _T("EDIT"), NULL, WS_CHILDWINDOW|ES_LEFT|WS_VISIBLE, 0, cr.bottom-26, cr.right-160, 26, wnd, NULL, g.app, NULL);
//Setting fonts
HFONT hf = NULL;
hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
SendMessage(g.button, WM_SETFONT, (WPARAM)hf, TRUE);
SendMessage(g.text, WM_SETFONT, (WPARAM)hf, TRUE);
//Creating wgl context
g.fdc = GetDC(g.panel);
PIXELFORMATDESCRIPTOR pfd = {0};
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 16;
pfd.cStencilBits = 8;
SetPixelFormat(g.fdc, 1, &pfd);
g.glrc = wglCreateContext(g.fdc);
wglMakeCurrent(g.fdc, g.glrc);
RECT pcr;
GetClientRect(g.panel, &pcr);
glViewport(0, 0, pcr.right, pcr.bottom);
glewInit();
glClearColor(0.0, 0.0, 0.0, 1.0);
UpdateWindow(wnd);
}
void Render()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
SwapBuffers(g.fdc);
}
You must not call Render() for every single window message! Only call Render() for WM_PAINT messages.
This sounds like a memory leak somewhere. The computer will come to a screeching stop if the PF usage gets too high.
Open up your task manager and look at the PF Usage before and then again during running the program. If it jumps up .5GB or more and keeps climbing then you've got a memory leak on your hands.
If it behaves in the manner described above the most likely culprit is that you are either making a new object (or texture) multiple times causing Windows to need to allocate more PF space for you, or you could have your VSYNC turned off.
I probably don't have to tell you what happens with a memory leak, but with the VSYNC turned off the program is going to try to run as fast as the CPU lets it. It will max out your CPU and it will do it fast.
Hope this helps.

Resources