65.9K
CodeProject 正在变化。 阅读更多。
Home

小型应用程序向导

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.26/5 (9投票s)

2006年8月23日

CPOL

5分钟阅读

viewsIcon

41247

downloadIcon

542

一个用于 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
  • ANSI: printf puts scanf gets
  • ANSI 安全: gets_s
  • Unicode: wprintf _putws wscanf _getws
  • Unicode 安全: _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 安全: 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 安全: wcscpy_s wcsncpy_s wcscat_s wcsncat_s _wsplitpath_s

为了获得最佳优化效果,不建议使用 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.libsmall_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  •šWGu²Úâó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 会使可执行文件非常大,但添加的代码并不是无用的代码。只有在必要时才减小可执行文件的大小。

再见!

© . All rights reserved.