I’ve modified my version of Jim’s test app as follows…
1) Added AdjustWindowRect() functionality so that one can place controls right up against the bottom or right edge of their parent when designing the layout and not have them clipped when used on other operating systems other than the one on which form design took place;
2) Added XP Theme functionality as in the original HelloDDT.bas sample app being emulated by SDK techniques;
3) Added DPI Awareness by calling SetProcessDPIAware() or adding it to the manifest and performing the necessary modifications to window and control sizings.
The smallest sizes I can get the app down to using VC15 and VC19 with various compilation/linkage setups are as follows…
// 9,216 UNICODE, x64, VC19, TCLib, DPI Awareness Set In Manifest (Adds 1024 Bytes!!!)
// 8,192 UNICODE, x64, VC19, TCLib, DPI Awareness Set SetProcessDPIAware() Api
// 7,680 UNICODE, x64, VC15, TCLib
// 8,192 ANSI, x64, VC19, TCLib
// 7,680 ANSI, x64, VC15, TCLib
// 6,144 UNICODE, x64, VC15, TCLib, DPI Aware, But No Dialog Fonts, AdjustWindowsRect(), Or XPThemes
// 90,112 UNICODE, x64, VC19, LIBCMT
// 42,496 UNICODE, x64, VC15, LIBCMT
Interestingly, setting DPI Awareness via a manifest as opposed to calling SetProcessDPIAware() adds 1 k or 1,024 bytes to the executable, i.e., 9,216 bytes verses 8,192 bytes in VC19 (Visual Studio 2015). That kind of surprised me. I was wondering if by setting both DPI Awareness and Themes in the manifest and removing the SetProcessDPIAware() and associated function calls in the Cpp file I could perhaps shave a half kilobyte or full kilobyte from the executable. But it seems to work the other way around. It costs more to do it via the manifest, seemingly.
Here are all my code files…
// XPtheme.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly
xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<assemblyIdentity
name="HelloSdk"
processorArchitecture="amd64"
version="5.1.0.0"
type="win32"/>
<description>Windows Shell</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="amd64"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
Note in the above that the processorArchitecture setting has to be changed to “amd64” from the original “x86” in the PowerBASIC version. Also, if DPI Awareness is set in the manifest (which adds 1,024 bytes), this XPTheme.xml file must be used for it to work on Windows 10 (Win 8 too I guess)…
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly
xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"
>
<assemblyIdentity
name="HelloSdk"
processorArchitecture="amd64"
version="5.1.0.0"
type="win32"
/>
<description>
Windows Shell
</description>
<!-- Compatibility section -->
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below indicates application support for Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!--The ID below indicates application support for Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!--This Id value indicates the application supports Windows 8 functionality-->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!--This Id value indicates the application supports Windows 8.1 functionality-->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!--This Id value indicates the application supports Windows 10 functionality-->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="amd64"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<!-- DPI Aware -->
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
Here is Hello.rc…
// Hello.rc
// Hello.rc
1 24 "xptheme.xml"
// Hello.h
//Hello.h
#ifndef Hello_h
#define Hello_h
#define IDC_TEXT 2000
#define IDC_OK 2005
#define IDC_CANCEL 2010
#define dim(x) (sizeof(x) / sizeof(x[0]))
#define SizX(x) (int)(x * rxRatio) // For DPI Scaling Calculations And Purposes
#define SizY(y) (int)(y * ryRatio) // For DPI Scaling Calculations And Purposes
struct WndEventArgs
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
};
LRESULT fnWndProc_OnCreate (WndEventArgs& Wea);
LRESULT fnWndProc_OnCommand (WndEventArgs& Wea);
LRESULT fnWndProc_OnDestroy (WndEventArgs& Wea);
struct EVENTHANDLER
{
unsigned int iMsg;
LRESULT (*fnPtr)(WndEventArgs&);
};
const EVENTHANDLER EventHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_COMMAND, fnWndProc_OnCommand},
{WM_DESTROY, fnWndProc_OnDestroy}
};
#endif
// HelloSdk.cpp
// HelloSdk.cpp
// cl Hello.cpp /O1 /Os /GS- /GR- /FeHelloSdk.exe TCLib.lib Kernel32.lib User32.lib Gdi32.lib Comctl32.lib HelloRes.obj
// cl Hello.cpp /O1 /Os /GS- /MT /FeHelloSdk.exe Kernel32.lib User32.lib gdi32.lib Comctl32.lib HelloRes.obj
// 8,192 UNICODE, x64, VC19, TCLib
// 7,680 UNICODE, x64, VC15, TCLib
// 8,192 ANSI, x64, VC19, TCLib
// 7,680 ANSI, x64, VC15, TCLib
// 90,112 UNICODE, x64, VC19, LIBC
// 42,496 UNICODE, x64, VC15, LIBC
#define TCLib
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include <commctrl.h>
#ifdef TCLib
#include "stdio.h"
#include "string.h"
#include "tchar.h"
extern "C" int _fltused=1;
#else
#include <cstdio>
#include <string.h>
#include <tchar.h>
#endif
#include "Hello.h"
void SetMyProcessDpiAware()
{
BOOL (__stdcall *pFn)(void);
HINSTANCE hInstance=LoadLibrary(_T("user32.dll"));
if(hInstance)
{
pFn=(BOOL (__stdcall*)(void))GetProcAddress(hInstance,"SetProcessDPIAware");
if(pFn)
pFn();
FreeLibrary(hInstance);
}
}
LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
double dpiX = 0.0;
double dpiY = 0.0;
double rxRatio = 0.0;
double ryRatio = 0.0;
HWND hCtl = NULL;
HFONT hFont = NULL;
HDC hDC = NULL;
RECT rc;
INITCOMMONCONTROLSEX uCC;
Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
uCC.dwSize = sizeof(uCC), uCC.dwICC = ICC_STANDARD_CLASSES;
InitCommonControlsEx(&uCC);
hDC = GetDC(NULL);
dpiX=GetDeviceCaps(hDC, LOGPIXELSX);
dpiY=GetDeviceCaps(hDC, LOGPIXELSY);
rxRatio=(dpiX/96);
ryRatio=(dpiY/96);
ReleaseDC(NULL,hDC);
rc.left = 0, rc.top = 0, rc.right = SizX(240), rc.bottom = SizY(81);
AdjustWindowRect(&rc, WS_CAPTION|WS_VISIBLE, FALSE);
long w = rc.right - rc.left;
long h = rc.bottom - rc.top;
long x = max((GetSystemMetrics(SM_CXSCREEN) - w) / 2, 0);
long y = max((GetSystemMetrics(SM_CYSCREEN) - h) / 2, 0);
MoveWindow(Wea.hWnd,x,y,w,h,FALSE);
hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
hCtl = CreateWindowEx(WS_EX_CLIENTEDGE,_T("Edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_TABSTOP|ES_AUTOHSCROLL,SizX(21),SizY(20),SizX(201),SizY(19),Wea.hWnd,(HMENU)IDC_TEXT,Wea.hIns,NULL);
SendMessage(hCtl, (UINT)WM_SETFONT, (WPARAM)hFont, (LPARAM)0);
SetFocus(hCtl);
hCtl = CreateWindow(_T("Button"),_T("OK"), WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(51),SizY(52),SizX(60),SizY(23),Wea.hWnd,(HMENU)IDC_OK,Wea.hIns,NULL);
SendMessage(hCtl, (UINT)WM_SETFONT, (WPARAM)hFont, (LPARAM)0);
hCtl = CreateWindow(_T("Button"),_T("Cancel"), WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(126),SizY(52),SizX(60),SizY(23),Wea.hWnd,(HMENU)IDC_CANCEL,Wea.hIns,NULL);
SendMessage(hCtl, (UINT)WM_SETFONT, (WPARAM)hFont, (LPARAM)0);
return 0;
}
LRESULT fnWndProc_OnCommand(WndEventArgs& Wea)
{
switch(LOWORD(Wea.wParam))
{
case IDC_OK:
{
TCHAR szText[128];
TCHAR szString[128];
GetWindowText(GetDlgItem(Wea.hWnd,IDC_TEXT),szText,128);
_tcscpy(szString,_T("Hello, "));
_tcscat(szString,szText);
_tcscat(szString,_T("!"));
MessageBox(Wea.hWnd,szString,_T("Greetings!"),MB_OK);
break;
}
case IDC_CANCEL:
{
SendMessage(Wea.hWnd,WM_CLOSE,0,0);
break;
}
}
return 0;
}
LRESULT fnWndProc_OnDestroy(WndEventArgs& Wea)
{
PostQuitMessage(0);
return 0;
}
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea;
for(size_t i=0; i<dim(EventHandler); i++)
{
if(EventHandler[i].iMsg==msg)
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*EventHandler[i].fnPtr)(Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("HelloSdk");
WNDCLASSEX wc;
HWND hWnd;
MSG msg;
SetMyProcessDpiAware();
wc.lpszClassName = szClassName; wc.lpfnWndProc = fnWndProc;
wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0;
wc.hIcon = LoadIcon(NULL,IDI_APPLICATION); wc.hInstance = hIns;
wc.hIconSm = 0; wc.hCursor = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra = 0;
wc.lpszMenuName = NULL; wc.cbClsExtra = 0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,_T("What Is Your Name?"),WS_CAPTION|WS_VISIBLE,0,0,0,0,HWND_DESKTOP,0,hIns,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&msg, NULL, 0, 0))
{
if(!IsDialogMessage(hWnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
To build the version with DPI Awareness set in manifest, comment out the SetProcessDPIAware() in WinMain(), and remove function SetMyProcessDpiAware().
To compile the resource script into a resource file I do this…
rc.exe /v /foHelloRes.res Hello.rc
That creates a HelloRes.res file. Then use cvtres.exe to create an obj file…
cvtres.exe /MACHINE:X64 /v HelloRes.res
That HelloRes.obj file then gets fed into the linker with the rest of the libs/object code as seen in my compilation strings at the top of HelloSdk.cpp.
I think that size is pretty terrible for such a simple app as this. Its 7.5 k with VC15 and 8 k with VC19 – UNICODE or ANSI. As I mentioned its 9 k if DPI Awareness is set in the manifest. For something as trivial as this I’d expect about 5 k as being reasonable with TCLib. Which got me to wondering why it is so big? And I guess I have to accept Jim’s statement that resource editor created apps beat SDK style apps size wise. Or do I?
Well, in ruminating about it for awhile I decided there’s a whole lot of stuff going on in that app that I would never do. Its there because I simply copied code that Jim provided with his test app. For example, all this stuff in my fnWndProc_OnCreate()…
hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
// hCtl = CreateWindowEx( for edit control )
SendMessage(hCtl, (UINT)WM_SETFONT, (WPARAM)hFont, (LPARAM)0);
// hCtl = CreateWindowEx( for OK Button )
SendMessage(hCtl, (UINT)WM_SETFONT, (WPARAM)hFont, (LPARAM)0);
// hCtl = CreateWindowEx( for Cancel Button )
SendMessage(hCtl, (UINT)WM_SETFONT, (WPARAM)hFont, (LPARAM)0);
…which changes the font of the three child window controls. What purpose does that serve? I can’t see any whatsoever. What I’m guessing is that Jim is attempting to exactly emulate the fonts used in resource created dialogs. But why is that the standard to which SDK created windows must conform? By dropping those four Api function calls my 7,680 byte VC15 created app loses 512 bytes and comes in 7,168 bytes. And the resulting app looks better in my opinion than the one emulating resource dialog fonts.
Next in line for abscission would be all the AdjustWindowRect() stuff. I have to admit I’m real glad I now know about that function (and AdjustWindowRectEx() too), but by simply refraining from placing controls right up against the bottom or right hand border of the window one can easily dispense with it to no ill effect. With that gone and all its associated baggage and variables, our fnWndProc_OnCreate() reduces to simply this….
LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
double dpiX = 0.0;
double dpiY = 0.0;
double rxRatio = 0.0;
double ryRatio = 0.0;
HWND hCtl = NULL;
HDC hDC = NULL;
RECT rc;
INITCOMMONCONTROLSEX uCC;
Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
uCC.dwSize = sizeof(uCC), uCC.dwICC = ICC_STANDARD_CLASSES;
InitCommonControlsEx(&uCC);
hDC = GetDC(NULL);
dpiX=GetDeviceCaps(hDC, LOGPIXELSX);
dpiY=GetDeviceCaps(hDC, LOGPIXELSY);
rxRatio=(dpiX/96);
ryRatio=(dpiY/96);
ReleaseDC(NULL,hDC);
MoveWindow(Wea.hWnd,SizX(300),SizY(200),SizX(270),SizY(120),FALSE);
hCtl = CreateWindowEx(WS_EX_CLIENTEDGE,_T("Edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_TABSTOP|ES_AUTOHSCROLL,SizX(21),SizY(15),SizX(220),SizY(22),Wea.hWnd,(HMENU)IDC_TEXT,Wea.hIns,NULL);
SetFocus(hCtl);
hCtl = CreateWindow(_T("Button"),_T("OK"), WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(64),SizY(52),SizX(60),SizY(23),Wea.hWnd,(HMENU)IDC_OK,Wea.hIns,NULL);
hCtl = CreateWindow(_T("Button"),_T("Cancel"), WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(138),SizY(52),SizX(60),SizY(23),Wea.hWnd,(HMENU)IDC_CANCEL,Wea.hIns,NULL);
return 0;
}
…which is 28 lines compared to the original of 39 lines. Unfortunately, I’m still at 7,168 bytes.
Next, for me at least, I’d be happy to settle for just buttons and edit controls without all the XP Theme stuff. Horrified by that comment, and thinking I’m some kind of cave man or something? Completely lacking all aesthetic sensibilities of what is right and beautiful? Maybe So.
By removing the XPTheme manifest file and the calls to InitCommonControlsEx() and that associated baggage (no need for rc or cvtres) we’re down to simply this as a fnWndProc_OnCreate()…
LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
double dpiX = 0.0;
double dpiY = 0.0;
double rxRatio = 0.0;
double ryRatio = 0.0;
HWND hCtl = NULL;
HDC hDC = NULL;
Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
hDC = GetDC(NULL);
dpiX=GetDeviceCaps(hDC, LOGPIXELSX);
dpiY=GetDeviceCaps(hDC, LOGPIXELSY);
rxRatio=(dpiX/96);
ryRatio=(dpiY/96);
ReleaseDC(NULL,hDC);
MoveWindow(Wea.hWnd,SizX(300),SizY(200),SizX(270),SizY(120),FALSE);
hCtl = CreateWindowEx(WS_EX_CLIENTEDGE,_T("Edit"),_T(""),WS_CHILD|WS_VISIBLE|WS_TABSTOP|ES_AUTOHSCROLL,SizX(21),SizY(15),SizX(220),SizY(22),Wea.hWnd,(HMENU)IDC_TEXT,Wea.hIns,NULL);
SetFocus(hCtl);
hCtl = CreateWindow(_T("Button"),_T("OK"), WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(64),SizY(52),SizX(60),SizY(23),Wea.hWnd,(HMENU)IDC_OK,Wea.hIns,NULL);
hCtl = CreateWindow(_T("Button"),_T("Cancel"), WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(138),SizY(52),SizX(60),SizY(23),Wea.hWnd,(HMENU)IDC_CANCEL,Wea.hIns,NULL);
return 0;
}
…which is only 24 lines of code, and our exe is now only 6,144 bytes, is still High DPI Aware, UNICODE, x64 architecture, and will look quite satisfactory on any Windows Operating System (I’ve tested it on everything from Win 2000 on up).
So adding themes, AdjustWindowsRect() and stuff like that only costs two or three kilobytes which is certainly nothing. But after all, this is just an academic exercise to see where bloat comes from!