Introduction

This document describes some methods on how to minimize the size of images - be they dll's or exe's. The methods include elimination of the c-runtime stubs, and compiler & linker settings. The compiler and linker I concentrate on is MSVC 6 - most of the tips apply also to MSVC 5. While many of the concepts presented here apply to other development enviroments the actual command line parameters and #pragmas will obviously be different - check your enviroments documentation.

Doing without the C-Runtime

The c-runtime is a library of functions that do stuff for you the programmer. These functions are platform independent, and act as an abstraction layer between your program and the operating system. This has some drawbacks:

If, like me, you find these to be unacceptable tradeoffs, there are a couple of things you must do to eliminate the c-runtime completely from your application


Functions you can use

The following functions are available in intrinsic form by the compiler. Beware though that the instrinsic forms of these may not be as optimised as the library form.




Required Functions

The c++ compiler absolutely requires that you implement __purecall, new and delete. If C++ exception ahndling is enabled more are required that I have no clue how to write. Either don't use C++ exceptions, or find the .obj files that implement these functions and link them into your project.

A simple implementation of these functions:

void* __cdecl operator new(unsigned int cb)
{
  return HeapAlloc(GetProcessHeap(),0,cb);
}

void __cdecl operator delete(void* pv)
{
  if(pv)
    HeapFree(GetProcessHead(),0,pv);
}

extern "C" int _cdecl _purecall(void)
{
  return 0;
}

In addition to the C++ functions listed above you will need to provide your application with a new entry point. Typically an application starts at a functions called main, WinMain, or DllMain. These functions are actually called from the c-runtimes real entry point. Here are the prototypes and real names of an applications entry point:

EXTERN_C int WINAPI mainCRTStartup();

EXTERN_C int WINAPI WinMainCRTStartup();

EXTERN_C BOOL WINAPI _DllMainCRTStartup(
  HINSTANCE hInstDll,         // handle to the DLL module
  DWORD fdwReason,            // reason for calling function
  LPVOID lpvReserved,         // reserved
);

Application Termination

Normally, when the code path leaves the main( or WinMain() functions, the application exits. This is because the default implementation of the above functions calls ExitProcess. If you do not call ExitProcess then your application will not close untill all threads have exited cleanly.

Example Implementation

As a debugging aid it is usefull to implement the entrypoint function in a manner similar to the c-runtimes.

EXTERN_C int WINAPI WinMainCRTStartup()
{
  HINSTANCE hInstance = GetModuleHandle(NULL);
  LPSTR lpszCmdLine = GetCommandLine();
  int r = WinMain(hInstance,NULL,lpszCmdLine,SW_SHOWDEFAULT);
  ExitProcess(r);
  return r; // this will never be reached.
}



Compiler Switches

The following table describes the compiler switches that should be set to ensure successful compilation using MSVC++ 6

Switch Action Description
/GX delete This switch enables C++ exception handling which requires a number of functions related to unwinding the stack.
/GZ delete This switch enables some advanced debugging features. With these features enabled the linker will look for a function called _chkstk.
/Oi add Add this switch to ensure that intrinsic functions are enabled.
/Zl add Usually the compiler embeds a "defaultlib" refrence to the c-runtime in the .obj file. This switch (do not confuse with the /ZI switch) ensures that defaultlib entries are not written to the generated OBJs.



Linker Switches

The following switches are optional if the compiler is set correctly. However, if just one badly compiled obj file is in the project then the c-runtimes standard entry point may be invoked. If you suspect this is the case you might want to set one, or both of these switches.

Switch Action Description
/nodefaultlib add This switch is not really necessary if you compile with /Zl, as there should be no default libraries to ignore. If however you use a 3rd party library, or any old obj files that do include a defaultlib entry, then the linker will silently ignore your custom entry point, unless you use the following switch
/entry:function add Use this if you wish to use a non-standard name for your entry point. This is a good idea if you are linking to a 3rd party library or object code that contains defaultlib directives, as the linker, if given half a chance, will use the runtime libraries entry point if it can find it.
/opt:nowin98 add The MSVC 6 linker defaults to a 4Kb section padding in PE files as an optimizaton to speed load times on Windows 98. Very small projects will benefit with a file size saving of about 16Kb.


More MSVC 6 Linker settings

Microsofts linkers prior to 6.00 produced PE iamges with file level section alignments of 512 bytes. Link 6.00 aligns sections in a file on 4Kb boundries to optimise loading of the image under 98. For compatability reasons 98 must load files with the old alignment (but will do so with a performance hit), and, if you are targetting NT you can use the old 512 byte padding without any performance deficit at all. The linker switch you must add to the link line is: (the second line details how the command might be embedded as a linker option in a .C file.

// linker options can be embedded directly in .cpp code thus:
#if defined(_MSC_VER) && _MSC_VER >= 1200
#pragma comment(linker, "/OPT:NOWIN98" )
#endif