Small Application Wizard

This article was released on www.codeproject.com.

Download Installer and Sources - 488 Kb
Download Demo Projects - 248 Kb

Introduction

This is a Small Application Wizard for Visual Studio .NET, it helps you build small executables with just a few clicks. The downloadable installer was designed for Visual C++ 8, I haven't tested it on earlier versions. If you just want to start using this wizard,  install it and click on Small Application in the Visual Studio's New Project window. Just like this:

 

If you're interested in knowing more details about this little project, just keep reading.

Let's consider this article an update to Matt Pietrek's one about his tiny libc. I never really cared about the size of executables produced by Visual Studio, until I had to build some small ones for work. So, basically, I wrote my own small libc including support for unicode, secure functions, x64 and Itanium. When I was done with that job, I didn't want to leave the files getting old on my hard drive. So, I decided to add some functions to my small libc and create a wizard to make the whole task as easy as possible. As you can imagine this article is not to be taken too seriously.

Small LibC

I won't post the code of the libc since it's useless. These are the supported functions:

Header

Functions

stdio.h Ansi: printf puts scanf gets
Ansi Secure: gets_s

Unicode: wprintf _putws wscanf _getws
Unicode Secure: _getws_s

stdlib.h new delete malloc free calloc realloc
string.h memset memcpy memmove memcmp memchr memcpy_s

Ansi: strlen strcpy strncpy strcat strncat strcmp strncmp _stricmp strupr strlwr strchr strstr strtol strtoul _splitpath
Ansi Secure: strcpy_s strncpy_s strcat_s strncat_s _splitpath_s

Unicode: wcslen wcscpy wcsncpy wcscat wcsncat wcscmp wcsncmp _wcsicmp wcsupr wcslwr wcschr wcsstr wcstol wcstuol _wsplitpath
Unicode Secure: wcscpy_s wcsncpy_s wcscat_s wcsncat_s _wsplitpath_s

For best optimization it's not advisable to use functions like scanf. I just put some stdio functions in my libc to make small console projects work.

Basically, I took half of the string functions from Microsoft's SDK. I believe I took splitpath from wine (I don't remember for sure) and strol / strtoul from somewhere on the web. The other functions I had to write by myself.

A lot of string functions are just wrappers to windows apis.

For Example:

extern "C" size_t __cdecl strlen(const char *str)
{

#ifndef AVOID_IF_POSSIBLE_WINAPI

	return (size_t) lstrlenA(str);

#else

	const char *eos = str;

	while (*eos++) ;

	return (eos - str - 1);

#endif
}

If for whatever reason you don't want to use windows apis whenever possible (it can't be always avoided), just define AVOID_IF_POSSIBLE_WINAPI and no external function will be used. This isn't the best to reduce the executable's size, but it might be useful if you want to make the disassembled code a little bit harder to understand or to avoid simple breakpoints on apis. If you didn't understand what I just said, forget the whole point.

Stub Code

Windows executables have always a different entrypoint from what you usually see. Three common entrypoints are:

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow);

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpvReserved);

int _tmain(int argc, _TCHAR* argv[]);
But the actual ones are:
#ifdef UNICODE
extern "C" int WINAPI wWinMainCRTStartup(void)
#else
extern "C" int WINAPI WinMainCRTStartup(void)
#endif

extern "C" BOOL WINAPI _DllMainCRTStartup(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpReserved)

#ifdef UNICODE
extern "C" int WINAPI wmainCRTStartup(void)
#else
extern "C" void __cdecl mainCRTStartup(void)
#endif
In the case of the Win32 Exe and the Console program the actual entrypoint has to get the command line through GetCommandLine and parse it. I used Pietrek's entries, since they were already working, no need to write new ones.

Setting up a Visual Studio Project

C/C++ -> Optimization:

Minimize Size and Favor Small Code are quite easy to understand. What's to say is that I disabled the Whole Program Optimization because it didn't allow me to use my own libc.

C/C++ -> Code Generation:

The Struct Member Alignment is easy to understand. I had to disable the Buffer Security Check in order to use my own libc (and, of course, disabling it reduces size anyway).

Linker -> Input:

Ignore Default Libraries is to ignore the default libc. Additional Dependencies tells the linker to use my small libc. Of course, in case you'll need to compile for x64 or Itanium you've to replace small_libc_x86.lib with small_libc_x64.lib or small_libc_Itanium.lib.

Linker -> Debugging:

I disabled the Debug Info in order to reduce size, but it shouldn't be in a release anyway. It's that information string about the debug info file which is put into your executable:

 Offset    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F   Ascii

00009250  00 00 00 00 00 00 00 00 00 00 00 00 58 AA 40 00  ............X@.
00009260  F0 92 40 00 03 00 00 00 52 53 44 53 87 ED 80 26  @....RSDS&
00009270  95 9A 57 47 8D 75 B2 DA E2 1F F3 4B 02 00 00 00  WGuK...
00009280  63 3A 5C 64 6F 63 75 6D 65 6E 74 73 20 61 6E 64  c:\documents.and
00009290  20 73 65 74 74 69 6E 67 73 5C 6E 74 6F 73 6B 72  .settings\ntoskr
000092A0  6E 6C 5C 64 6F 63 75 6D 65 6E 74 69 5C 76 69 73  nl\documenti\vis
000092B0  75 61 6C 20 73 74 75 64 69 6F 20 70 72 6F 6A 65  ual.studio.proje
000092C0  63 74 73 5C 73 6D 61 6C 6C 20 65 78 65 5C 72 65  cts\small.exe\re
000092D0  6C 65 61 73 65 5C 53 6D 61 6C 6C 20 45 78 65 2E  lease\Small.Exe.
000092E0  70 64 62 00 00 00 00 00 00 00 00 00 00 00 00 00  pdb.............

It doesn't respect the programmer's privacy as well (be aware: it's put by default into all your executables, even .NET ones).

Optimization Results

Here's a small table to compare sizes between minimal exes produced by defualt linking and the ones produced with this wizard:

Project Type Win32 Exe Win32 Dll Console
Default x86 48,00 KB 52,00 KB 48,00 KB
Small x86 2,50 KB 2,00 KB 2,50 KB
Default x64 44,00 KB 45,50 KB 44,00 KB
Small x64 3,00 KB 2,00 KB 3,00 KB
Default Itanium 90,50 KB 95,00 KB 90,00 KB
Small Itanium 4,00 KB 3,50 KB 5,00 KB

This seems pretty acceptable, doesn't it?

The Wizard

This is the J# code of the wizard:


function OnFinish(selProj, selObj)
{
	try
	{
		var strProjectPath = wizard.FindSymbol('PROJECT_PATH');
		var strProjectName = wizard.FindSymbol('PROJECT_NAME');

		selProj = CreateCustomProject(strProjectName, strProjectPath);
		AddConfig(selProj, strProjectName);
		AddFilters(selProj);

		var InfFile = CreateCustomInfFile();
		AddFilesToCustomProj(selProj, strProjectName, strProjectPath, InfFile);
		PchSettings(selProj);
		InfFile.Delete();

		selProj.Object.Save();
	}
	catch(e)
	{
		if (e.description.length != 0)
			SetErrorInfo(e);
		return e.number
	}
}

function CreateCustomProject(strProjectName, strProjectPath)
{
	try
	{
		var strProjTemplatePath = wizard.FindSymbol('PROJECT_TEMPLATE_PATH');
		var strProjTemplate = '';
		strProjTemplate = strProjTemplatePath + '\\default.vcproj';

		var Solution = dte.Solution;
		var strSolutionName = "";
		if (wizard.FindSymbol("CLOSE_SOLUTION"))
		{
			Solution.Close();
			strSolutionName = wizard.FindSymbol("VS_SOLUTION_NAME");
			if (strSolutionName.length)
			{
				var strSolutionPath = strProjectPath.substr(0, strProjectPath.length - strProjectName.length);
				Solution.Create(strSolutionPath, strSolutionName);
			}
		}

		var strProjectNameWithExt = '';
		strProjectNameWithExt = strProjectName + '.vcproj';

		var oTarget = wizard.FindSymbol("TARGET");
		var prj;
		if (wizard.FindSymbol("WIZARD_TYPE") == vsWizardAddSubProject)  // vsWizardAddSubProject
		{
			var prjItem = oTarget.AddFromTemplate(strProjTemplate, strProjectNameWithExt);
			prj = prjItem.SubProject;
		}
		else
		{
			prj = oTarget.AddFromTemplate(strProjTemplate, strProjectPath, strProjectNameWithExt);
		}
		return prj;
	}
	catch(e)
	{
		throw e;
	}
}

function AddFilters(proj)
{
	try
	{
		// Add the folders to your project
		var strSrcFilter = wizard.FindSymbol('SOURCE_FILTER');
		var group = proj.Object.AddFilter('Source Files');
		group.Filter = strSrcFilter;
	}
	catch(e)
	{
		throw e;
	}
}

function AddConfig(proj, strProjectName)
{
	try
	{   
	    var ProjType = wizard.FindSymbol('LST_PROJECT');
	    var bUseUnicode = wizard.FindSymbol('RB_UNICODE');
	    
	    //
		// Debug (x86)
		//
	    
		var config = proj.Object.Configurations('Debug');
		
		config.IntermediateDirectory = '$(ConfigurationName)';
		config.OutputDirectory = '$(ConfigurationName)';
		
		if (ProjType != 'Win32Dll')
		    config.ConfigurationType = 1; // exe
		else
		    config.ConfigurationType = 2; // dll
				
		if (bUseUnicode == true)
		    config.CharacterSet = 1; // unicode
		else
		    config.CharacterSet = 0; // ascii

		var CLTool = config.Tools('VCCLCompilerTool');
		
		CLTool.Optimization = 0;
		CLTool.MinimalRebuild = true;
		CLTool.BasicRuntimeChecks = 3;
		CLTool.RuntimeLibrary = 0;
		CLTool.UsePrecompiledHeader = 0;
		CLTool.WarningLevel = 3;
		CLTool.Detect64BitPortabilityProblems = true;
		CLTool.DebugInformationFormat = 4;
				
		if (ProjType == 'Win32Exe')
		    CLTool.PreprocessorDefinitions = 'WIN32;_DEBUG;_WINDOWS';
		else if (ProjType == 'Console')
		    CLTool.PreprocessorDefinitions = 'WIN32;_DEBUG;_CONSOLE';
		 else 
		    CLTool.PreprocessorDefinitions = 'WIN32;_DEBUG;_WINDOWS;_USRDLL';

		var LinkTool = config.Tools('VCLinkerTool');
		
		LinkTool.LinkIncremental = 2;
		LinkTool.GenerateDebugInformation = true;
		
		if (ProjType != 'Console')
		    LinkTool.SubSystem = 2; // win32
		else
		    LinkTool.SubSystem = 1; // console
		    
		LinkTool.TargetMachine = 1;
		
		//
		// Release (x86)
		//

		config = proj.Object.Configurations('Release');
		
		config.IntermediateDirectory = '$(ConfigurationName)';
		config.OutputDirectory = '$(ConfigurationName)';
		
		if (ProjType != 'Win32Dll')
		    config.ConfigurationType = 1; // exe
		else
		    config.ConfigurationType = 2; // dll
		
		if (bUseUnicode == true)
		    config.CharacterSet = 1; // unicode
		else
		    config.CharacterSet = 0; // ascii
		    
		var CLTool = config.Tools('VCCLCompilerTool');
		
		CLTool.Optimization = 1;
		CLTool.FavorSizeOrSpeed = 2;
		CLTool.WholeProgramOptimization = false;
		CLTool.RuntimeLibrary = 0;
		CLTool.StructMemberAlignment = 1;
		CLTool.BufferSecurityCheck = false;
		CLTool.UsePrecompiledHeader = 0;
		CLTool.WarningLevel = 3;
		CLTool.Detect64BitPortabilityProblems = true;
		CLTool.DebugInformationFormat = 3;
		
		if (ProjType == 'Win32Exe')
		    CLTool.PreprocessorDefinitions = 'WIN32;NDEBUG;_WINDOWS';
		else if (ProjType == 'Console')
		    CLTool.PreprocessorDefinitions = 'WIN32;NDEBUG;_CONSOLE';
		 else 
		    CLTool.PreprocessorDefinitions = 'WIN32;NDEBUG;_WINDOWS;_USRDLL';
		    
		var LinkTool = config.Tools('VCLinkerTool');
		
		LinkTool.AdditionalDependencies = 'small_libc_x86.lib';
		LinkTool.LinkIncremental = 1;
		LinkTool.IgnoreAllDefaultLibraries = true;
		LinkTool.GenerateDebugInformation = false;
		
		if (ProjType != 'Console')
		    LinkTool.SubSystem = 2; // win32
		else
		    LinkTool.SubSystem = 1; // console
		
		LinkTool.OptimizeReferences = 2;
		LinkTool.EnableCOMDATFolding = 2;
		LinkTool.TargetMachine = 1;
	}
	catch(e)
	{
		throw e;
	}
}

function PchSettings(proj)
{
	// TODO: specify pch settings
}

function DelFile(fso, strWizTempFile)
{
	try
	{
		if (fso.FileExists(strWizTempFile))
		{
			var tmpFile = fso.GetFile(strWizTempFile);
			tmpFile.Delete();
		}
	}
	catch(e)
	{
		throw e;
	}
}

function CreateCustomInfFile()
{
	try
	{
		var fso, TemplatesFolder, TemplateFiles, strTemplate;
		fso = new ActiveXObject('Scripting.FileSystemObject');

		var TemporaryFolder = 2;
		var tfolder = fso.GetSpecialFolder(TemporaryFolder);
		var strTempFolder = tfolder.Drive + '\\' + tfolder.Name;

		var strWizTempFile = strTempFolder + "\\" + fso.GetTempName();

		var strTemplatePath = wizard.FindSymbol('TEMPLATES_PATH');
		var strInfFile = strTemplatePath + '\\Templates.inf';
		wizard.RenderTemplate(strInfFile, strWizTempFile);

		var WizTempFile = fso.GetFile(strWizTempFile);
		return WizTempFile;
	}
	catch(e)
	{
		throw e;
	}
}

function GetTargetName(strName, strProjectName)
{
	try
	{
		// TODO: set the name of the rendered file based on the template filename
		var strTarget = strName;

		if (strName == 'readme.txt')
			strTarget = 'ReadMe.txt';
			
		if (strName == 'main.h')
		    strTarget = strProjectName + '.h';
		    
		if (strName == 'main.cpp')
		    strTarget = strProjectName + '.cpp';
		    
		if (strName == 'crt.cpp')
		    strTarget = strProjectName + ' CRT.cpp';
		    
		return strTarget; 
	}
	catch(e)
	{
		throw e;
	}
}

function AddFilesToCustomProj(proj, strProjectName, strProjectPath, InfFile)
{
	try
	{
		var projItems = proj.ProjectItems

		var strTemplatePath = wizard.FindSymbol('TEMPLATES_PATH');

		var strTpl = '';
		var strName = '';

		var strTextStream = InfFile.OpenAsTextStream(1, -2);
		while (!strTextStream.AtEndOfStream)
		{
			strTpl = strTextStream.ReadLine();
			if (strTpl != '')
			{
				strName = strTpl;
				var strTarget = GetTargetName(strName, strProjectName);
				var strTemplate = strTemplatePath + '\\' + strTpl;
				var strFile = strProjectPath + '\\' + strTarget;

				var bCopyOnly = false;  
				var strExt = strName.substr(strName.lastIndexOf("."));
				if(strExt==".bmp" || strExt==".ico" || strExt==".gif" || strExt==".rtf" 
				   || strExt==".css" || strExt==".lib")
					bCopyOnly = true;
				wizard.RenderTemplate(strTemplate, strFile, bCopyOnly);
				
				if (strExt != ".lib")
				    proj.Object.AddFile(strFile);
			}
		}
		strTextStream.Close();
	}
	catch(e)
	{
		throw e;
	}
}
There are already a lot of articles on how to make a Custom Wizard for Visual Studio, so any further explanation is quite useless. I just put the code in the article to let it be ridden by someone who's not interested in downloading the attachment.

The installer just puts all the files in the right directories, there's nothing unusual in the whole process.

Final Considerations

More Optimization can be still reached. I was thinking about a post-build PE optimizer...

I'd like to say a last thing about this whole subject. Reducing executables size isn't that important, most of the times it's just playing around. The default libc makes executables very big, but the added code is not useless code. Reduce your executables size only if necessary.

Goodbye!

Daniel Pistelli