WTL applications are quite small already. However, using VS 2005 a statically linked application with WTL 9.10 weighs in at 136 kB (139,264 Bytes) for the Win32 configuration.
Looking at the executable I noticed a static import of oleaut32.dll
. A cursory look with dumpbin
shows one import via ordinal.
OLEAUT32.dll
4181C0 Import Address Table
41C9B8 Import Name Table
0 time date stamp
0 Index of first forwarder reference
Ordinal 277
Inspecting oleaut32.dll
one finds that the export is named VarUI4FromStr
.
Digging a little with IDA, I found that VarUI4FromStr
was used by ATL::CRegParser::AddValue
. Following the dependents from there, showed two calls in ATL::CRegParser::RegisterSubkeys
.
Cross-referencing the ATL code of my Visual Studio installation with the findings, I found that the culprit was ATL::CAtlComModule
. It does a lot of TypeLib registration stuff that is simply not needed for my use case.
However, the linker seems to leave all of that in, because it cannot reasonably decide to throw it out.
How can I get rid of this seemingly superfluous import?
Alas, since WTL::CAppModule
derives directly from ATL::CComModule
, including the atlbase.h
header while having _ATL_NO_COMMODULE
defined leads to an error:
Error 1 fatal error C1189: #error : WTL requires that _ATL_NO_COMMODULE is not defined $(ProjectDir)\wtl\Include\atlapp.h 33
The actual culprit that ultimately pulls in ATL::CComModule
is, however, ATL::CAtlComModule
. So our goal is to get rid of both of them.
We'll try to trick atlbase.h
into excluding all the TypeLib registration code by defining _ATL_NO_COMMODULE
anyway, but undefining it right after we're done including it. This way atlapp.h
and other WTL headers won't "notice".
#define _ATL_NO_COMMODULE
#include <atlbase.h>
#undef _ATL_NO_COMMODULE
#include <atlapp.h>
Obviously that gets us into some trouble:
1>atlapp.h(1515) : error C2039: 'CComModule' : is not a member of 'ATL'
1>atlapp.h(1515) : error C2504: 'CComModule' : base class undefined
1>atlapp.h(1524) : error C2653: 'CComModule' : is not a class or namespace name
1>atlapp.h(1543) : error C2653: 'CComModule' : is not a class or namespace name
1>atlapp.h(1625) : error C3861: 'GetModuleInstance': identifier not found
1>atlapp.h(1784) : error C2653: 'CComModule' : is not a class or namespace name
1>atlapp.h(1806) : error C2065: 'm_nLockCnt' : undeclared identifier
However, it looks like we should be able to fix this handful of errors quickly.
First of all let's check where ATL::CComModule
comes from: it's declared and defined in atlbase.h
. We can surely declare our own class and squeeze it in between #include <atlbase.h>
and #include <atlapp.h>
(see above).
Let's start with the basics, based on what can be found in atlbase.h
:
namespace ATL
{
class CComModule : public CAtlModuleT<CComModule>
{
public:
CComModule() {}
};
};
Now we get a different set of errors:
1>atlapp.h(1524) : error C2039: 'Init' : is not a member of 'ATL::CComModule'
1> stdafx.h(31) : see declaration of 'ATL::CComModule'
1>atlapp.h(1625) : error C3861: 'GetModuleInstance': identifier not found
Excellent, so we're in fact only missing two more class functions: GetModuleInstance
and Init
. Let's pull those in as well. For good measure we'll also add GetResourceInstance
:
namespace ATL
{
class CComModule : public CAtlModuleT<CComModule>
{
public:
CComModule() {}
HINSTANCE GetModuleInstance() throw() { return _AtlBaseModule.m_hInst; }
HINSTANCE GetResourceInstance() throw() { return _AtlBaseModule.m_hInstResource; }
HRESULT Init(_ATL_OBJMAP_ENTRY*, HINSTANCE, const GUID*) throw() { return S_OK; }
};
};
A short test proves that this is working fine. And the oleaut32.dll
import is gone along with three more imports from ole32.dll
. Great success!
And as a result we have saved 12 kB on the executable size.
Do you know other cool tricks to shrink WTL application size?
Note that depending on the features of ATL::CComModule
which your code uses, you may have to fall back to using the original. The only other alternative is to extend the above mock version of ATL::CComModule
to fix any compilation errors you may run into.
I have not tested this extensively yet and I'll report back (by editing this answer) if I run into any trouble. However, from looking at it in both the ATL and WTL code as well as in the IDA database prior to my changes, I think this is a safe change.
You may also use the 6001.18002 standalone WDK (for Vista SP1), which supports to target Windows 2000 (SP4) and newer. It includes ATL 7.0 and is therefore suited to build ATL+WTL applications.
To do that you just need to add the following two lines to your sources
file:
USE_STATIC_ATL=1
ATL_VER=70
Thanks to the WDK, which uses msvcrt.dll
, a system DLL, as its default (multithreaded dynamically linked) C runtime, you can then shrink the executable size further by linking dynamically to the C runtime. And in this case, since that particular C runtime is a system DLL (starting with Windows 2000), you can rest assured that it will work.
All of that taken into account you can build standard Windows applications (GUI or console) and DLLs that are really small.
Here is an example sources
file which you can use as a template:
# Name of the program
TARGETNAME=progname
# Prefix used for the intermediate/output paths (e.g. objfre_w2k_x86)
TARGETPATH=obj
# A program, not a driver or DLL or static lib
TARGETTYPE=PROGRAM
# windows == GUI, console == Console, native == no subsystem ...
UMTYPE=windows
# Use Unicode ("wide char") instead of ANSI
UNICODE=1
# By default the WDK build treats warnings as errors - this turns it off
BUILD_ALLOW_ALL_WARNINGS=1
# Link dynamically to msvcrt.dll
USE_MSVCRT=1
# Don't link against any of the ATL DLLs
USE_STATIC_ATL=1
# In the 7600.16385.1 WDK you can use 70 as well, which translates to 71
ATL_VER=70
USE_NATIVE_EH=1
# RTTI is required for some ATL/WTL features
USE_RTTI=1
# GUI programs require these entry points
!IF defined(UNICODE) && $(UNICODE)
UMENTRY=wwinmain
C_DEFINES=$(C_DEFINES) /DUNICODE /D_UNICODE
!ELSE
UMENTRY=winmain
C_DEFINES=$(C_DEFINES) /DMBCS /D_MBCS
!ENDIF
# Include folders
INCLUDES=$(DDK_INC_PATH);$(CRT_INC_PATH);$(SDK_INC_PATH);wtl\Include
# Libraries to link to, not just import libraries
TARGETLIBS=\
$(SDK_LIB_PATH)\kernel32.lib \
$(SDK_LIB_PATH)\user32.lib \
$(SDK_LIB_PATH)\Comctl32.lib \
# Give source files (also resources and .mc) in the current or parent directory
SOURCES=...
The 7600.16385.1 standalone WDK also works for this. The WDKs starting from the Windows 8 WDK now require a Visual C++ into which they can be integrated. This also leads to the binaries requiring the respective C runtime version instead of the msvcrt.dll
from prior WDK versions.