小型应用程序向导






4.26/5 (9投票s)
一个用于 Visual Studio .NET 的向导,只需点击几下即可构建小型应用程序。
引言
这是一个适用于 Visual Studio .NET 的小型应用程序向导。它能帮助您只需点击几下即可构建小型可执行文件。可下载的安装程序是为 Visual C++ 8 设计的,我尚未在早期版本上进行测试。如果您只想开始使用此向导,请安装它,然后在 Visual Studio 的“新建项目”窗口中点击“小型应用程序”。就像这样。
如果您对这个小项目有更多了解的兴趣,请继续阅读。
我们将本文视为对 Matt Pietrek 关于他的 tiny libc 文章的更新。我以前从未真正关心 Visual Studio 生成的可执行文件的大小,直到我必须为工作构建一些小型文件。因此,我基本上编写了自己的小型 libc,包括对 Unicode、安全函数、x64 和 Itanium 的支持。当我完成这项工作后,我不想让文件在硬盘上闲置。因此,我决定在我的小型 libc 中添加一些函数,并创建一个向导,使整个任务尽可能轻松。正如您所能想象的,这篇文章不必过于严肃。
小型 LibC
我不会发布 libc 的代码,因为它没有用。以下是支持的函数:
标题 |
函数 |
stdio.h |
|
stdlib.h |
|
string.h |
|
为了获得最佳优化效果,不建议使用 scanf
等函数。我只是在我的 libc 中放了一些 stdio 函数,以使小型控制台项目能够正常工作。
基本上,我从 Microsoft 的 SDK 中获取了一半的字符串函数。我相信我从 wine 中获取了 splitpath
(我不确定)以及从网络上的某个地方获取了 strol
/ strtoul
。其他函数我必须自己编写。
许多字符串函数只是 Windows API 的包装器。
例如
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 }
如果由于任何原因您不想尽可能多地使用 Windows API(有时无法避免),只需定义 AVOID_IF_POSSIBLE_WINAPI
,就不会使用任何外部函数。这对于减小可执行文件的大小不是最好的方法,但如果您想让反汇编代码稍微难以理解或避免对 API 进行简单的断点,可能会有用。如果您不理解我刚才说的,就忽略这一点。
存根代码
Windows 可执行文件始终有一个与您通常看到的不同的入口点。三个常见的入口点是:
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[]);
但实际的入口点是:
#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
对于 Win32 EXE 和控制台程序,实际入口点必须通过 GetCommandLine
获取命令行并对其进行解析。我使用了 Pietrek 的入口点,因为它们已经可以正常工作,无需编写新的。
设置 Visual Studio 项目
C/C++ -> 优化
最小化大小 和 倾向于小代码 都很容易理解。需要说明的是,我禁用了 整程序优化,因为它不允许我使用自己的 libc。
C/C++ -> 代码生成
结构成员对齐 很容易理解。我必须禁用缓冲区安全检查才能使用我自己的 libc(当然,禁用它也能减小尺寸)。
链接器 -> 输入
忽略默认库 是为了忽略默认的 libc。附加依赖项 告诉链接器使用我的小型 libc。当然,如果您需要为 x64 或 Itanium 编译,您必须将 small_libc_x86.lib 替换为 small_libc_x64.lib 或 small_libc_Itanium.lib。
链接器 -> 调试
我禁用了调试信息以减小尺寸,但无论如何它都不应该出现在发布版本中。它是关于调试信息文件的信息字符串,该字符串会放入您的可执行文件中。
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 •šWGu²ÚâóK...
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.............
它也不尊重程序员的隐私(请注意:默认情况下,它会被放入您所有的可执行文件中,包括 .NET 的)。
优化结果
这是一个小型表格,用于比较默认链接生成的最小 EXEs 和使用此向导生成的 EXEs 的大小。
项目类型 | Win32 EXE | Win32 DLL | 控制台 |
默认 x86 | 48.00 KB | 52.00 KB | 48.00 KB |
小型 x86 | 2.50 KB | 2.00 KB | 2.50 KB |
默认 x64 | 44.00 KB | 45.50 KB | 44.00 KB |
小型 x64 | 3.00 KB | 2.00 KB | 3.00 KB |
默认 Itanium | 90.50 KB | 95.00 KB | 90.00 KB |
小型 Itanium | 4.00 KB | 3.50 KB | 5.00 KB |
这看起来很可以接受,不是吗?
向导
这是向导的 J# 代码。
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;
}
}
关于如何为 Visual Studio 创建自定义向导的文章已经有很多了,所以任何进一步的解释都显得多余。我只是将代码放在文章中,以便那些不感兴趣下载附件的人也可以阅读。
安装程序只是将所有文件放到正确的目录中,整个过程没有什么不寻常的。
最终思考
还可以实现更多优化。我正在考虑一个构建后 PE 优化器……
我想就这个主题说最后一句话。减小可执行文件的大小并不是那么重要;大多数时候,这只是玩玩而已。默认的 libc 会使可执行文件非常大,但添加的代码并不是无用的代码。只有在必要时才减小可执行文件的大小。
再见!