本文是自定义C ++ Visual Studio项目的程序集的指南。 在某种程度上,它是从有关该主题的分散文章的资料中减少的,部分是对Studio标准配置文件进行反向工程的结果。 我写这篇文章主要是因为Microsoft本身在该主题上的文档的实用性趋于零,并且我希望手头有一个方便的参考,以后可以访问并将其发送给其他开发人员。 Visual Studio具有方便而广泛的选项,可以为复杂的项目设置真正方便的工作,我很生气地看到由于令人作呕的文档,这些功能现在很少使用。
举例来说,让我们尝试将Flatbuffer架构添加到Studio中,然后Studio在需要时会自动调用Flatc(在没有更改的情况下不会调用它),并允许您直接通过“文件属性”设置设置

目录
*级别1:爬入.vcxproj文件谈论.props文件但是,为什么还要分开.vcxproj和.props?使项目设置更具可读性我们使连接第三方库变得容易项目模板-自动创建项目* 2级:自定义自定义编译传统方法达到MSBuild目标让我们尝试创建一个目标来构建.proto文件我们想到我们的榜样U2DCheck和日志文件完成我们的自定义.target那CustomBuildStep呢?正确的文件复制*级别3:与Visual Studio中的GUI集成我们从“配置属性”中的.vcxproj的肠道中拉出设置向Studio讲解新文件类型将设置与单个文件相关联*级别4:扩展MSBuild的功能注意:本文中的所有示例均已在VS 2017中进行了测试。据我所知,它们应至少从VS 2012开始在Studio的早期版本中使用,但我不能保证。
级别1:爬入.vcxproj文件
让我们看一下自动生成的典型.vcxproj Visual Studio内部。
它看起来像这样<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup Label="ProjectConfigurations"> <ProjectConfiguration Include="Debug|Win32"> <Configuration>Debug</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> <ProjectConfiguration Include="Release|Win32"> <Configuration>Release</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> <ProjectConfiguration Include="Debug|x64"> <Configuration>Debug</Configuration> <Platform>x64</Platform> </ProjectConfiguration> <ProjectConfiguration Include="Release|x64"> <Configuration>Release</Configuration> <Platform>x64</Platform> </ProjectConfiguration> </ItemGroup> <PropertyGroup Label="Globals"> <VCProjectVersion>15.0</VCProjectVersion> <ProjectGuid>{0D35456E-42DA-418B-87D4-55E32B8E1373}</ProjectGuid> <Keyword>Win32Proj</Keyword> <RootNamespace>protobuftest</RootNamespace> <WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>true</UseDebugLibraries> <PlatformToolset>v141</PlatformToolset> <CharacterSet>Unicode</CharacterSet> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> <PlatformToolset>v141</PlatformToolset> <WholeProgramOptimization>true</WholeProgramOptimization> <CharacterSet>Unicode</CharacterSet> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>true</UseDebugLibraries> <PlatformToolset>v141</PlatformToolset> <CharacterSet>Unicode</CharacterSet> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> <PlatformToolset>v141</PlatformToolset> <WholeProgramOptimization>true</WholeProgramOptimization> <CharacterSet>Unicode</CharacterSet> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <ImportGroup Label="ExtensionSettings"> </ImportGroup> <ImportGroup Label="Shared"> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <LinkIncremental>true</LinkIncremental> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <LinkIncremental>true</LinkIncremental> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <LinkIncremental>false</LinkIncremental> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <LinkIncremental>false</LinkIncremental> </PropertyGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> </ClCompile> <Link> <SubSystem>Console</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> </ClCompile> <Link> <SubSystem>Console</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level3</WarningLevel> <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> </ClCompile> <Link> <SubSystem>Console</SubSystem> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> <GenerateDebugInformation>true</GenerateDebugInformation> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level3</WarningLevel> <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> </ClCompile> <Link> <SubSystem>Console</SubSystem> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> <GenerateDebugInformation>true</GenerateDebugInformation> </Link> </ItemDefinitionGroup> <ItemGroup> <ClInclude Include="pch.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader> </ClCompile> <ClCompile Include="protobuf_test.cpp" /> </ItemGroup> <ItemGroup> <Text Include="test.proto" /> </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> </ImportGroup> </Project>
乱七八糟,不是吗? 这仍然是一个很小且几乎不重要的文件。 让我们尝试将其转变为更具可读性和感知性的东西。
谈论.props文件
为此,我们要注意一个事实,即我们获取的文件是一个普通的XML文档,可以在逻辑上将其分为两部分,第一部分列出了项目设置,第二部分列出了其中包含的文件。 让我们将这些两半分开。 为此,我们需要代码中已经遇到的Import标记,它与#include类似,并且允许您将一个文件包含在另一个文件中。 我们将.vcxproj复制到其他文件,并从中删除与项目中包含的文件相关的所有广告,然后从.vcxproj中,删除与项目中实际包含的文件相关
的广告
以外的所有内容。 带有项目设置但在Visual Studio中没有文件的结果文件通常称为“属性表”,并以扩展名.props保存。 反过来,在.vcxproj中,我们将提供相应的Import
现在,.vcxproj仅描述项目中包含的文件,并且阅读起来更加容易 <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="settings.props" /> <PropertyGroup Label="Globals"> <ProjectGuid>{0D35456E-42DA-418B-87D4-55E32B8E1373}</ProjectGuid> </PropertyGroup> <ItemGroup> <ClInclude Include="pch.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader> </ClCompile> <ClCompile Include="protobuf_test.cpp" /> </ItemGroup> <ItemGroup> <Text Include="test.proto" /> </ItemGroup> </Project>
通过删除不必要的XML元素,可以进一步简化此操作。 例如,属性“ PrecompiledHeader”现在针对不同的配置选项(发布/调试)和平台(win32 / x64)被声明了4次,但是每次声明都是相同的。 另外,这里使用了几个不同的ItemGroups,而实际上一个元素就足够了。 结果,我们得到一个紧凑且易于理解的.vcxproj,它简单列出了1)项目中包含的文件,2)每个文件是什么(加上特定于个别文件的设置)和3)包含指向分别存储的项目设置的链接。
<?xml version="1.0" encoding="utf-8"?><Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="settings.props" /> <PropertyGroup Label="Globals"> <ProjectGuid>{0D35456E-42DA-418B-87D4-55E32B8E1373}</ProjectGuid> </PropertyGroup> <ItemGroup> <ClInclude Include="pch.h" /> <ClCompile Include="pch.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> <ClCompile Include="protobuf_test.cpp" /> <Text Include="test.proto" /> </ItemGroup> </Project>
我们在工作室中重新加载项目,检查装配-一切正常。
但是,为什么还要分开.vcxproj和.props?
由于程序集中没有任何变化,乍一看似乎我们将锥子换成了肥皂,对我们实际上不需要查找的文件进行了无意义的“重构”。 但是,让我们先说一下我们的解决方案包含多个项目。 然后,如您所见,来自不同项目的几个不同的.vcxproj文件可以使用具有设置
的相同 .props文件。 我们已经将解决方案中使用
的汇编规则与源代码分开
,现在我们可以在一处更改
所有相同类型项目的汇编设置。 在绝大多数情况下,这样的组装是一个好主意。 例如,将一个新项目添加到解决方案中,在一个动作中,我们通过这种方式将解决方案中现有项目的所有设置琐碎转移到该项目。
但是,如果我们仍然需要针对不同项目的不同设置怎么办? 在这种情况下,我们可以为不同类型的项目简单地创建几个不同的.props文件。 由于.props文件可以以完全相同的方式导入其他.props文件,因此,从描述解决方案中所有项目的常规设置的文件到指定特殊版本的高度专门版本的文件,构建多个.props文件的“层次结构”是非常容易和自然的。解决方案中仅一个或两个项目的规则。 MSBuild中的一条规则是,如果在输入文件中两次声明相同的设置(例如,首先将其导入到base.props中,然后在从base.props开头导入的derived.props中再次声明),则后面的声明将覆盖前面的声明。 这样,您只需简单地在每个.props文件中覆盖给定.props文件所需的所有设置,而无需担心它们可能已经在其他地方声明的事实,即可轻松方便地设置设置的任意层次结构。 除其他外,在.props中的某个位置导入标准的Studio环境设置是明智的,对于C ++项目,该设置应如下所示:
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
我注意到在实践中,将自己的.props文件与.sln文件放在同一文件夹中非常方便
因为它允许您方便地导入.props,而不管.vcxproj的位置如何 <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ...> <Import Project="$(SolutionDir)\settings.props" /> ... </Project>
使项目设置更具可读性
现在,我们不再需要单独处理每个项目,我们可以更加关注自定义构建过程。 对于初学者,我建议为与使用.props文件的解决方案相关的文件系统中最有趣的对象赋予理智的名称。 为此,我们应该创建一个标记为UserMacros的PropertyGroup标记:
<PropertyGroup Label="UserMacros"> <RepositoryRoot>$(SolutionDir)\..</RepositoryRoot> <ProjectsDir>$(RepositoryRoot)\projects</ProjectsDir> <ThirdPartyDir>$(RepositoryRoot)\..\ThirdParty</ThirdPartyDir> <ProtoBufRoot>$(ThirdPartyDir)\protobuf\src</ProtoBufRoot> </PropertyGroup>
然后,在项目设置中,我们可以简单地编写“ $(ProtoBufRoot)\ protoc.exe”,而不是“ .. \ .. \ .. \ ThirdParty \ protobuf \ src \ protoc.exe”形式的构造。 除了更高的可读性之外,这还使代码更具移动性-我们可以自由移动.vcxproj,而不必担心其设置会飞逝,并且可以通过仅更改.props文件之一中的一行来移动(或更新)Protobuf。
依次声明几个PropertyGroup时,它们的内容将被合并-只有名称与先前声明的名称一致的宏将被覆盖。 这使您可以轻松地在附加的.props文件中补充声明,而不必担心丢失早先已经宣布的宏。
我们使连接第三方库变得容易
在Visual Studio中包括对第三方库的依赖关系的通常过程通常是这样的:

相应设置的过程包括在项目设置的不同选项卡上一次编辑多个参数,因此很无聊。 此外,对于项目中的每个单独配置,通常必须执行多次,因此,经常由于这种操作而导致项目是在Release程序集中而不是在Debug程序集中组装的。 因此,这是一种不舒服且不可靠的方法。 但是,您可能已经猜到了,可以将相同的设置“打包”到props文件中。 例如,对于ZeroMQ库,一个类似的文件可能看起来像这样:
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemDefinitionGroup> <ClCompile> <AdditionalIncludeDirectories>$(ThirdPartyDir)\libzmq\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions>ZMQ_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> <Link> <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">libzmq-v120-mt-sgd-4_3_1.lib;Ws2_32.Lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">libzmq-v120-mt-s-4_3_1.lib;Ws2_32.Lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalLibraryDirectories>$(ThirdPartyDir)\libzmq\lib\x64\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> </Link> </ItemDefinitionGroup> </Project>
请注意,如果我们仅在props文件中定义类型AdditionalLibraryDirectories的标记,它将覆盖所有先前的定义。 因此,使用的结构稍微复杂一些,其中标签以一系列字符结尾;%(AdditionalLibraryDirectories)构成标签与其自身的链接。 在MSBuild的语义中,此宏被扩展为先前的标记值,因此,类似的构造
将参数
附加到存储在AdditionalLibraryDirectories参数中的行的开头。
现在要连接ZeroMQ,只需导入给定的.props文件就足够了。
<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ...> <Import Project="$(SolutionDir)\settings.props" /> <Import Project="$(SolutionDir)\zeromq.props" /> ... </Project>
这就是项目结束时的操作-MSBuild将在Release和Debug程序集中自动包括必要的头文件和库。 因此,花一点时间编写zeromq.props,我们就有机会可靠,准确地将ZeroMQ连接到任何一行中的任何项目。 Studio的创建者甚至为此提供了一个称为“属性管理器”的特殊GUI,以便鼠标爱好者只需单击几下即可执行相同的操作。

的确,与其他Studio工具一样,此GUI将在.vcxproj代码中添加类似内容,而不是可读的单行
这样的代码 <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <Import Project="..\..\..\..\projects\BuildSystem\thirdparty_libraries\PropertySheets\zeromq.props" /> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <Import Project="..\..\..\..\projects\BuildSystem\thirdparty_libraries\PropertySheets\zeromq.props" /> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <Import Project="..\..\..\..\projects\BuildSystem\thirdparty_libraries\PropertySheets\zeromq.props" /> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <Import Project="..\..\..\..\projects\BuildSystem\thirdparty_libraries\PropertySheets\zeromq.props" /> </ImportGroup>
因此,我更喜欢将指向第三方库的链接手动添加到.vcxproj文件。
与之前讨论的内容类似,通过.props文件使用ThirdParty组件可以使将来更新使用的库同样容易。 只需编辑一个zeromq.props文件,整个解决方案的程序集将同步切换到新版本。 例如,在我们的项目中,通过这种机制构建项目已链接到Conan依赖项管理器,后者从依赖项清单中收集了必要的第三方库集,并自动生成了相应的.props文件。
项目模板-自动创建项目
手动编辑Studio创建的.vcxproj文件肯定很无聊(尽管如果您有技巧并且用不了很长时间)。 因此,Studio提供了一个方便的机会来为新项目创建自己的模板,该模板允许您仅手动配置.vcxproj一次,然后在任何新项目中单击即可重复使用。 在最简单的情况下,您甚至不需要手动编辑任何内容-只需打开需要变成模板的项目,然后从菜单中选择Project \ Export Template。 在打开的对话框中,您可以指定几个琐碎的参数,例如模板名称或将在其说明中显示的行,以及选择是否将新创建的模板立即添加到“新建项目”对话框中。 以这种方式创建的模板将创建用于创建它的项目的副本(包括项目中包含的所有文件),仅替换项目名称及其中的GUID。 在相当多的情况下,这绰绰有余。
通过对Studio生成的模板进行更详细的检查,您可以轻松地确保它只是一个zip归档文件,其中包含模板中使用的所有文件以及一个扩展名为.vstemplate的其他配置文件。 该文件包含项目元数据的列表(如使用的图标或描述行)以及创建新项目时必须创建的文件的列表。 举个例子
<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project"> <TemplateData> <Name>C++ console application</Name> <Description>C++ console application for our project</Description> <ProjectType>VC</ProjectType> <ProjectSubType> </ProjectSubType> <SortOrder>1000</SortOrder> <CreateNewFolder>true</CreateNewFolder> ` <DefaultName>OurCppConsoleApp</DefaultName> <ProvideDefaultName>true</ProvideDefaultName> <LocationField>Enabled</LocationField> <EnableLocationBrowseButton>true</EnableLocationBrowseButton> <Icon>ng.ico</Icon> </TemplateData> <TemplateContent> <Project TargetFileName="$projectname$.vcxproj" File="console_app.vcxproj" ReplaceParameters="true"> <ProjectItem ReplaceParameters="false" TargetFileName="$projectname$.vcxproj.filters">console_app.vcxproj.filters</ProjectItem> <ProjectItem ReplaceParameters="false" TargetFileName="main.cpp">main.cpp</ProjectItem> <ProjectItem ReplaceParameters="false" TargetFileName="stdafx.cpp">stdafx.cpp</ProjectItem> <ProjectItem ReplaceParameters="false" TargetFileName="stdafx.h">stdafx.h</ProjectItem> </Project> </TemplateContent> </VSTemplate>
注意参数ReplaceParameters =“ true”。 在这种情况下,它仅适用于vcxproj文件,如下所示:
<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(SolutionDir)\console_app.props" /> <PropertyGroup Label="Globals"> <ProjectGuid>{$guid1$}</ProjectGuid> <RootNamespace>$safeprojectname$</RootNamespace> </PropertyGroup> <ItemGroup> <ClCompile Include="main.cpp" /> <ClCompile Include="stdafx.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="stdafx.h" /> </ItemGroup> </Project>
如您所见,代替GUID和RootNamespace,没有特定的值,但是存根$ guid1 $和$ safeprojectname $。 使用模板时,Studio会浏览标记为ReplaceParamters =“ true”的文件,在其中查找格式为$ name $的存根,并使用特殊字典将其替换为计算值。 默认情况下,Studio
不支持很多参数 ,但是在编写Visual Studio Extensions(稍后再讨论)时,在启动对话框以从模板创建新项目时,可以轻松添加自己计算出的(或由用户输入的)许多参数。 如您在.vstemplate文件中看到的,同一词典也可以用于生成文件名,尤其是,它可以为不同项目的.vcxproj文件生成唯一的名称。 设置ReplaceParameters = false时,将仅复制模板中指定的文件,而无需进行其他处理。
可以使用以下几种方法之一将生成的带有模板的ZIP存档添加到Studio已知的模板列表中。 最简单的方法是简单地将此文件复制到
%USERPROFILE%\ Documents \ Visual Studio XX \ Templates \ ProjectTemplates文件夹。 值得注意的是,尽管在该文件夹中您会找到许多与创建新项目的窗口中的文件夹名称相匹配的子文件夹,但实际上该模板应简单地放在根文件夹中,因为模板在新项目树中的位置由Studio通过ProjectType和ProjectSubType标记确定在.vstemplate文件中。 此方法最适合于创建仅对您而言唯一的“个人”模板,并且如果选中“导出模板”对话框中的“自动将模板导入Visual Studio”复选框,则Studio正是通过将导出过程中创建的zip存档放置在此文件夹中来完成Studio的工作。模式。 但是,通过手动复制与同事共享这样的模板当然不是很方便。 因此,让我们熟悉一个稍微高级的选项-创建一个Visual Studio扩展(.vsix)
要创建VSIX,我们需要安装可选的Studio组件,该组件称为Visual Studio Extensions开发工具:

之后,“ VSIX项目”选项将出现在Visual C#\可扩展性部分中。 请注意,尽管它位于(C#)位置,但它用于创建任何扩展,包括C ++项目模板集。

在VSIX创建的项目中,您可以做很多不同的事情-例如,创建自己的对话框,该对话框将用于配置由模板创建的项目。 但这是一个单独的巨大讨论话题,在本文中我将不再赘述。 要在VSIX中创建模板,一切都非常简单:创建一个空的VSIX项目,打开.vsixmanifest文件,然后直接在GUI中设置该项目的所有数据。 在“元数据”选项卡上输入元数据(扩展名,描述,许可证)。 请注意右上角的“版本”字段-最好正确指定它,因为Studio随后会使用它来确定计算机上安装了哪个版本的扩展。 然后,转到“资产”选项卡,然后选择“添加新资产”,其类型为:Microsoft.VisualStudio.ProjectTemplate,源:文件系统上的文件,路径:(包含模板的zip存档的名称)。 单击确定,重复此过程,直到将所有所需的模板添加到VSIX。

在那之后,剩下的就是选择Configuration:Release并命令Build Solution。 您无需编写代码;也可以手动编辑配置文件。 输出是带有扩展名.vsix的可移植文件,实际上是我们创建的扩展名的安装程序。 创建的文件将在安装了Studio的任何计算机上“启动”,并显示一个对话框,其中包含扩展名和许可证的说明,并提供安装其内容的提示。 允许安装-我们在“创建新项目”对话框中添加模板

通过这种方法,可以轻松地将大量人员的工作统一到一个项目中。 要安装和使用模板,用户仅需单击几次鼠标即可获得任何资格。 可以在“工具\扩展和更新”对话框中查看(和删除)已安装的扩展。

第2级:自定义自定义编译
好的,在这一阶段,我们了解了vcxproj和props文件的组织方式,并了解了如何组织它们。 现在,假设我们要向我们的项目中添加一些.proto方案,用于基于出色的Google协议缓冲区库序列化对象。 让我提醒您这个库的主要思想:您在与平台无关的特殊元语言(.proto文件)中编写对象的描述(“方案”),该元语言由特殊的编译器(protoc.exe)编译成.cpp / .cs / .py / .java /等 这些文件可根据所需的编程语言根据此方案实现对象的序列化/反序列化,并且可以在项目中使用。 因此,在编译项目时,我们首先需要调用protoc,这将为我们创建一组将来将使用的.cpp文件。
传统方法
经典的前额实现非常简单,只需在需要.proto文件的项目的预构建步骤中添加一个protoc调用即可。 像这样:
但这不是很方便:- 需要在命令中明确指定已处理文件的列表
- 如果您更改这些文件,则构建不会自动重建
- 相反,在Studio识别为源代码的项目中更改OTHER文件时,将不必要地执行预构建步骤
- 默认情况下,生成的文件不包含在项目程序集中
- 如果我们手动将生成的文件包括在项目中,则在首次打开该项目时,该项目将生成错误(因为第一个程序集尚未生成文件)。
而是,我们尝试“解释” Visual Studio本身(或者更确切地说,是它使用的MSBuild构建系统)如何处理此类.proto文件。MSBuild targets
MSBuild, build targets, targets. Clean , Compile , Link Deploy. .vcxproj . nix- make «makefile», : .vcxproj XML- makefile.
但是,困惑的读者会说停停停停。怎么了在此之前,我们在一个简单的项目中查看了.vcxproj,并且与经典makefile没有目标或相似之处。那可以讨论什么样的目标呢?事实证明,它们只是在这一行中“隐藏”了,在.vcxproj中包括了一组用于构建C ++代码的标准目标。 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
«» - C++- «» Build, Clean Rebuild «» . toolset toolset , , Clang.
toolset- toolset . ,
.
target-. target MSBuild
- 输入清单
- 输出清单
- 对其他目标的依赖性(依赖性)
- 目标设定
- 目标执行的实际步骤顺序(任务)
例如,ClCompile目标接收项目中的.cpp文件列表作为输入,并通过拖动cl.exe编译器从中生成一组.obj文件。ClCompile目标设置变成传递给cl.exe的编译标志。当我们在.vcxproj文件中写入该行时 <ClCompile Include="protobuf_test.cpp" />
然后将包含文件protobuf_tests.cpp添加到此目标的输入列表中,并在编写时 <ItemDefinitionGroup> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> </ClCompile> </ItemDefinitionGroup>
然后我们将值“ Use”分配给ClCompile.PrecompiledHeader设置,然后该目标将变成传递给cl.exe的/ Yu标志并传递给编译器。让我们尝试创建一个目标来构建.proto文件
使用target标签可实现添加新目标: <Target Name="GenerateProtobuf"> ...steps to take... </Target>
传统上,目标放置在扩展名为.targets的文件中。并不是说这是绝对必要的(vcxproj以及内部的target和props文件都是等效的XML),但这是一个标准的命名方案,我们将继续坚持下去。这样,在.vcxproj文件的代码中,您现在可以编写类似 <ItemGroup> <ClInclude Include="cpp.h"/> <ProtobufFile Include="my.proto" /> <ItemGroup>
我们创建的目标必须添加到AvailableItemName列表中 <ItemGroup> <AvailableItemName Include="ProtobufFile"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> </ItemGroup>
我们还需要描述我们到底想对我们的输入文件做什么以及应该在输出上发生什么。为此,MSBuild使用称为“任务”的实体。任务-这是一些简单的操作,需要在项目的组装过程中完成。例如,“创建目录”,“编译文件”,“运行命令”,“复制内容”。在我们的例子中,我们将使用潜伏Exec运行protoc.exe,并使用潜伏消息在编译日志中显示此步骤。我们还指示此目标的启动应在标准目标PrepareForBuild之后立即进行。结果,我们得到了类似此protobuf.targets文件的内容 <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup> <AvailableItemName Include="ProtobufSchema"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> </ItemGroup> <Target Name="GenerateProtobuf" Inputs="%(ProtobufSchema.FullPath)" Outputs=".\generated\%(ProtobufSchema.Filename).pb.cc" AfterTargets="PrepareForBuild"> <Message Importance="High" Text="Compiling schema %(ProtobufSchema.Identity)" /> <Exec Command="$(Protoc) --cpp_out=.\generated %(ProtobufSchema.Identity)" /> </Target> </Project>
在这里,我们使用了一个相当平凡的运算符“%”(批处理运算符),表示“对于列表中的每个项目”并自动添加了元数据。这里的想法是这样的:当我们编写形式的代码时 <ItemGroup> <ProtobufSchema Include="test.proto"> <AdditionalData>Test</AdditionalData> </ProtobufSchema> </ItemGroup>
«ProtobufSchema» «test.proto» () AdditionalData «Test». «ProtobufSchema.AdditionalData» «Test». AdditionalData, MSBuild
Identity ( ), Filename ( ) FullPath ( ). % MSBuild — .. .proto .
<Import Project="protobuf.targets" Label="ExtensionTargets"/>
在protobuf.props中,我们在.vcxproj-e中的ProtobufSchema标签上重写了我们的原型文件。 <ItemGroup> ... <ProtobufSchema Include="test.proto" /> <ProtobufSchema Include="test2.proto" /> </ItemGroup>
并检查组装1>------ Rebuild All started: Project: protobuf_test, Configuration: Debug x64 ------
1>Compiling schema test.proto
1>Compiling schema test2.proto
1>pch.cpp
1>protobuf_test.cpp
1>protobuf_test.vcxproj -> S:\Temp\msbuild\protobuf_msbuild_integration\x64\Debug\protobuf_test.exe
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
万岁!
赚了!没错,我们的.proto文件现在在项目中不再可见。我们爬入.vcxproj.filters并类推输入 ... <ItemGroup> <ProtobufSchema Include="test.proto"> <Filter>Resource Files</Filter> </ProtobufSchema> <ProtobufSchema Include="test2.proto"> <Filter>Resource Files</Filter> </ProtobufSchema> </ItemGroup> ...
我们重新加载项目-文件再次可见。我们想到我们的榜样
但是,事实上,我有点作弊。如果您没有在构建开始之前手动创建生成的文件夹,则构建实际上会崩溃,1>...\protobuf_test\protobuf.targets(13,6): error MSB3073: The command "...\ThirdParty\protobuf\bin\protoc.exe --cpp_out=.\generated test.proto" exited with code 1.
要解决此问题,请添加一个辅助目标,该目标将创建必要的文件夹 ... <Target Name="PrepareToGenerateProtobuf" Inputs="@(ProtobufSchema)" Outputs=".\generated"> <MakeDir Directories=".\generated"/> </Target> <Target Name="GenerateProtobuf" DependsOnTargets="PrepareToGenerateProtobuf" ...
使用DependsOnTargets属性,我们指示在启动任何GenerateProtobuf任务之前,您必须运行PrepareToGenerateProtobuf,并且@(ProtobufSchema)条目引用整个ProtobufSchema列表,作为单个实体用作此任务的输入,因此它将仅启动一次。重新启动程序集-它起作用了!现在让我们尝试再次进行重建,因此这次可以确定所有Em,但是我们的新任务去了哪里?进行一些调试-我们看到任务实际上是由MSBuild启动的,但是由于生成的文件夹已经在指定的输出文件夹中,因此它们没有执行。简而言之,在Rebuild中,清理为\生成的文件对我们不起作用。通过添加另一个目标来解决此问题1>------ Rebuild All started: Project: protobuf_test, Configuration: Debug x64 ------
1>pch.cpp
1>protobuf_test.cpp
1>protobuf_test.vcxproj -> S:\Temp\msbuild\protobuf_msbuild_integration\x64\Debug\protobuf_test.exe
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
<Target Name="CleanProtobuf" AfterTargets="Clean"> <RemoveDir Directories=".\generated"/> </Target>
— . Clean , Rebuild , Build .
========== Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
C++ , Build
1>------ Build started: Project: protobuf_test, Configuration: Debug x64 ------
1>protobuf_test.cpp
1>protobuf_test.vcxproj -> S:\Temp\msbuild\protobuf_msbuild_integration\x64\Debug\protobuf_test.exe
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
.proto- , protoc , . .proto .
========== Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
MSBuild , UI — MSBuild .pp.cc . - .cpp MSBuild , .props
1>------ Build started: Project: protobuf_test, Configuration: Debug x64 ------
1>Compiling schema test.proto
1>protobuf_test.cpp
1>protobuf_test.vcxproj -> S:\Temp\msbuild\protobuf_msbuild_integration\x64\Debug\protobuf_test.exe
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
?
U2DCheck tlog
Visual Studio MSBuild … « » . U2DCheck , MSBuild . U2DCheck «»
U2DCheck .
U2DCheck
.tlog . intermediate-output (_).tlog U2DCheck read tlog — , U2DCheck — write tlog — .
, target-
... <Exec Command="$(Protoc) --cpp_out=.\generated %(ProtobufSchema.Identity)" /> <WriteLinesToFile File="$(TLogLocation)\protobuf.read.1.tlog" Lines="^%(ProtobufSchema.FullPath)" />
— : .props , up-to-date. write tlog , target .
Visual Studio 2017 update 15.8 MSBuild
GetOutOfDateItems , .target- .tlog .
U2DCheck ProjectCapability
<ItemGroup> <ProjectCapability Include="NoVCDefaultBuildUpToDateCheckProvider" /> </ItemGroup>
但是,在这种情况下,Studio会为此项目以及所有依赖该项目的所有项目驱动MSBuild,是的,添加U2DCheck是有原因的-它的运行速度没有我想要的快。完成我们的自定义.target
我们获得的结果相当实用,但是仍有改进的空间。例如,在MSBuild中,当命令行指示不需要将整个项目作为一个整体进行组装时,仅在其中专门选择的单个文件中,就存在“选择性组装”模式。支持此模式要求目标检查@(SelectedFiles)列表的内容。除此之外,在我们的源代码中,有很多行必须彼此重合。良好的举止建议给所有这些实体提供可读的名称,并通过这些名称进行寻址。为此,通常会创建一个单独的特殊目标,该目标将创建并用将来需要的所有名称填充辅助列表。最后,我们仍然没有意识到一开始就承诺的想法-将生成的文件自动包含在项目中。我们已经可以#包含由protobuf生成的头文件,知道它们将在编译之前自动创建,但是该数字不适用于链接器:)。因此,我们只需将生成的文件添加到ClCompile列表中。protobuf.targets的类似组合实现的示例 <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup> <AvailableItemName Include="ProtobufSchema"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> </ItemGroup> <PropertyGroup> <ProtobufOutputFolder>.\generated</ProtobufOutputFolder> </PropertyGroup> <Target Name="ComputeProtobufInput"> <ItemGroup> <ProtobufCompilerData Include="@(ProtobufSchema)"> <OutputCppFile>$(ProtobufOutputFolder)\%(ProtobufSchema.Filename).pb.cc</OutputCppFile> <OutputPythonFile>$(ProtobufOutputFolder)\%(ProtobufSchema.Filename)_pb2.py</OutputPythonFile> <OutputFiles>%(ProtobufCompilerData.OutputCppFile);%(ProtobufCompilerData.OutputPythonFile)</OutputFiles> </ProtobufCompilerData> <ClCompile Include="%(ProtobufCompilerData.OutputCppFile)"> <PrecompiledHeader>NotUsing</PrecompiledHeader> </ClCompile> </ItemGroup> </Target> <Target Name="PrepareToGenerateProtobuf" Condition="'@(ProtobufSchema)'!=''" Inputs="@(ProtobufSchema)" Outputs="$(ProtobufOutputFolder)"> <MakeDir Directories="$(ProtobufOutputFolder)"/> </Target> <Target Name="GenerateProtobuf" DependsOnTargets="PrepareToGenerateProtobuf;ComputeProtobufInput" Inputs="%(ProtobufCompilerData.FullPath)" Outputs="%(ProtobufCompilerData.OutputFiles)" AfterTargets="PrepareForBuild" BeforeTargets="Compile"> <Message Importance="High" Text="Compiling schema %(ProtobufCompilerData.Identity)" /> <Exec Command="$(Protoc) --cpp_out=$(ProtobufOutputFolder) --python_out=$(ProtobufOutputFolder) %(ProtobufCompilerData.Identity)"> <Output ItemName="GeneratedFiles" TaskParameter="Outputs"/> </Exec> <WriteLinesToFile File="$(TLogLocation)\protobuf.read.1.tlog" Lines="^%(ProtobufCompilerData.FullPath)" /> </Target> <Target Name="CleanProtobuf" AfterTargets="Clean"> <RemoveDir Directories="$(ProtobufOutputFolder)"/> </Target> </Project>
此处的常规设置是在PropertyGroup中进行的,新的目标ComputeProtobufInput填充了输入和输出文件的列表。在此过程中(以演示如何使用输出文件列表),添加了从该计划与python集成的代码生成。我们开始检查一切是否正常 1>------ Rebuild All started: Project: protobuf_test, Configuration: Debug x64 ------ 1>Compiling schema test.proto 1>Compiling schema test2.proto 1>pch.cpp 1>protobuf_test.cpp 1>test.pb.cc 1>test2.pb.cc 1>Generating Code... 1>protobuf_test.vcxproj -> S:\Temp\msbuild\protobuf_msbuild_integration\x64\Debug\protobuf_test.exe 1>Done building project "protobuf_test.vcxproj". ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
那CustomBuildStep呢?
, , CustomBuildStep. .props Custom Build Step

Custom Build Step

.vcxproj-
<ItemDefinitionGroup> <CustomBuildStep> <Command>$(Protoc) --cpp_out=.\generated\%(FileName).pb.cc %(FullPath)</Command> <Message>Generate protobuf files</Message> <Outputs>.\generated\%(FileName).pb.cc</Outputs> </CustomBuildStep> </ItemDefinitionGroup> <ItemGroup> ... <CustomBuild Include="test.proto"/> <CustomBuild Include="test2.proto"/> ... </ItemGroup>
Microsoft.CppCommon.targets CustomBuildStep , -, . GUI clean tlog- :). , :
- CustomBuildStep
- 1
- step .props ThirdParty , ..
- CustomBuildStep - ,
正确的文件复制
一种非常常见的构建目标类型是将一些文件从一个位置复制到另一个位置。例如,将资源文件复制到带有已编译项目的文件夹中,或将第三方DLL复制到已编译二进制文件中。通常,通过在构建后目标中启动xcopy控制台实用程序来“正面”执行此操作。例如,
您不需要这样做,而不必尝试将其他构建目标推入构建后步骤中。相反,我们可以直接告诉Studio它需要复制特定文件。例如,如果文件直接包含在项目中,则只需指定ItemType = Copy
apply . .vcxproj- :
<ItemGroup> ... <ProtobufSchema Include="test2.proto" /> <CopyFileToFolders Include="resource.txt"> <DestinationFolders>$(OutDir)</DestinationFolders> </CopyFileToFolders> </ItemGroup>
« », tlog-. « » Custom Build Step , — () .
CopyFilesToFolder wildcards.
<CopyFileToFolders Include="$(LibFolder)\*.dll"> <DestinationFolders>$(OutDir)</DestinationFolders> </CopyFileToFolders>
将文件添加到CopyFileToFolders列表也许是构建项目时实现复制的最简单方法,包括在包含第三方库的.props文件中。但是,如果您想更好地控制正在发生的事情,那么另一种选择是将专门的复制任务添加到构建目标中。举个例子 <Target Name="_CopyLog4cppDll" Inputs="$(Log4cppDll)" Outputs="$(Log4cppDllTarget)" AfterTargets="PrepareForBuild"> <Message Text="Copying log4cpp.dll..." importance="high"/> <Copy SourceFiles="$(Log4cppDll)" DestinationFiles="$(Log4cppDllTarget)" SkipUnchangedFiles="true" Retries="10" RetryDelayMilliseconds="500" /> </Target>
有点题外话task- MS DownloadFile, VerifyFileHash, Unzip . Copy Retry, hard-link .
不幸的是,R复制任务不支持通配符,也不填充.tlog文件。如果需要,可以手动实现,例如这样 <Target Name="_PrepareToCopy"> <ItemGroup> <MyFilesToCopy Include="$(LibFolder)\*.dll"/> <MyFilesToCopy> <DestinationFile>$(TargetFolder)\%(MyFilesToCopy.Filename)%(MyFilesToCopy.Extension)</DestinationFile> </MyFilesToCopy> </ItemGroup> </Target> <Target Name="_Copy" Inputs="@(MyFilesToCopy)" Outputs="%(MyFilesToCopy.DestinationFile)" DependsOnTargets="_PrepareToCopy" AfterTargets="PrepareForBuild"> <Message Text="Copying %(MyFilesToCopy.Filename)..." importance="high" /> <Copy SourceFiles="@(MyFilesToCopy)" DestinationFolder="$(TargetFolder)" SkipUnchangedFiles="true" Retries="10" RetryDelayMilliseconds="500" /> <WriteLinesToFile File="$(TLogLocation)\mycopy.read.1.tlog" Lines="^%(MyFilesToCopy.Identity)" /> <WriteLinesToFile File="$(TLogLocation)\mycopy.write.1.tlog" Lines="^%(MyFilesToCopy.Identity);%(MyFilesToCopy.DestinationFile)" /> </Target>
但是,使用标准的CopyFileToFolders通常会容易得多。级别3:与Visual Studio GUI集成
make. XML-, , tlog-… - — - . PropertyPageSchema .
.vcxproj Configuration Properties
让我们尝试实现它,以便我们可以从“ protobuf.targets的组合实现”中编辑$(ProtobufOutputFolder)属性,而不是在文件中手动进行,而是直接从IDE中轻松进行编辑。为此,我们需要编写一个带有设置说明的特殊XAML文件。打开文本编辑器并创建一个名称为例如custom_settings.xml的文件 <?xml version="1.0" encoding="utf-8"?> <ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework"> <Rule Name="CustomProperties" PageTemplate="generic" DisplayName="My own properties"> <Rule.DataSource> <DataSource Persistence="ProjectFile"/> </Rule.DataSource> <StringProperty Name="ProtobufOutputFolder" DisplayName="Protobuf Output Directory" Description="Directory where Protobuf generated files are created." Subtype="folder"> </StringProperty> </Rule> </ProjectSchemaDefinitions>
StringProperty «ProtobufOutputFolder» String Subtype=Folder GUI, XML- project file. ProjectFile UserFile — .vcxproj.user ( VCS) . , protobuf.targets PropertyPageSchema
<ItemGroup> <AvailableItemName Include="ProtobufSchema"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> <PropertyPageSchema Include="custom_settings.xml"/> </ItemGroup>
, , project properties …

是的
出现带有设置的页面,Studio正确读取了其默认值。我们尝试更改它,保存项目,请参阅.vcxproj ... <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ProtobufOutputFolder>.\generated_custom</ProtobufOutputFolder> </PropertyGroup>
如传统条件条件所示,默认情况下,设置与特定的构建配置相关联。但是,如果需要,可以通过设置标志DataSource HasConfigurationCondition =“ false”来阻止此操作。没错,在2017 Studio中,存在一个错误,如果其中没有至少一个与某个配置相关联的设置,则可能不会显示项目设置。幸运的是,此设置可能不可见。不绑定配置的选项<?xml version="1.0" encoding="utf-8"?>
<Rule.DataSource>
/>
</Rule.DataSource>
<StringProperty Name="ProtobufOutputFolder"
DisplayName="Protobuf Output Directory"
Description="Directory where Protobuf generated files are created."
Subtype="folder">
<StringProperty.DataSource>
/>
</StringProperty.DataSource>
/>
您可以添加任意数量的设置。可能的类型包括BoolProperty,StringProperty(具有可选的子类型“文件夹”和“文件”),StringListProperty,IntProperty,EnumProperty和DynamicEnumProperty,后者可以从.vcxproj中可用的任何列表中即时填充。在此处阅读有关此内容的更多信息。您也可以将设置分组。例如,让我们尝试添加另一个设置,例如Bool代号 <?xml version="1.0" encoding="utf-8"?> <ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework"> <Rule Name="CustomProperties" PageTemplate="generic" DisplayName="My own properties"> <Rule.DataSource> <DataSource Persistence="ProjectFile"/> </Rule.DataSource> <Rule.Categories> <Category Name="General" DisplayName="General"/> </Rule.Categories> <BoolProperty Name="EnableCommonPCH" Category="General" DisplayName="Enable common precompiled headers" Description="Should we use solution-wide precompiled headers instead of project-specific?"> <BoolProperty.DataSource> <DataSource HasConfigurationCondition="false" /> </BoolProperty.DataSource> </BoolProperty> <StringProperty Name="ProtobufOutputFolder" DisplayName="Protobuf Output Directory" Description="Directory where Protobuf generated files are created." Subtype="folder" Category="General"> <StringProperty.DataSource> <DataSource HasConfigurationCondition="false" /> </StringProperty.DataSource> </StringProperty> <StringProperty Name="Dummy" Visible="false" /> </Rule> </ProjectSchemaDefinitions>
我们重新启动Studio,
编辑设置,保存项目-一切正常。 <PropertyGroup> <EnableCommonPCH>true</EnableCommonPCH> </PropertyGroup> <PropertyGroup> <ProtobufOutputFolder>.\generated_ustom</ProtobufOutputFolder> </PropertyGroup>
向Studio讲解新文件类型
到目前为止,为了将protobuf文件添加到项目中,我们必须在.vcxproj中手动注册该文件。通过将三个标签添加到上述.xml中,可以轻松解决此问题。 <ContentType Name="Protobuf" DisplayName="Google Protobuf Schema" ItemType="ProtobufSchema" /> <ItemType Name="ProtobufSchema" DisplayName="Google Protobuf Schema" /> <FileExtension Name="*.proto" ContentType="Protobuf" />
, .proto

«Google Protobuf Schema». «Add new item», .proto- ( \ Add \ Existing item… ) . « » Item type:

« » « ». DataSource ItemType.
<Rule Name="ProtobufProperties" PageTemplate="generic" DisplayName="Protobuf properties"> <Rule.DataSource> <DataSource Persistence="ProjectFile" ItemType="ProtobufSchema" /> </Rule.DataSource> <Rule.Categories> <Category Name="General" DisplayName="General"/> </Rule.Categories> <StringProperty Name="dllexport_decl" DisplayName="dllexport macro" Description="Add dllexport / dllimport statements controlled by #define with this name." Category="General"> </StringProperty> </Rule>

, .vcxproj
<ProtobufSchema Include="test2.proto"> <dllexport_decl Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MYLIB_EXPORT</dllexport_decl> </ProtobufSchema>
.
Level 4: MSBuild
, , : MSBuild. «» , MSBuild «» UsingTask.
MSBuild , DLL- - :
<UsingTask TaskName="CL" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll" />
这就是Studio提供的大多数“标准”任务的实现方式。但是出于明显的原因携带自定义DLL进行组装通常很不方便。因此,在UsingTask标记中支持一个名为TaskFactory的标记。TaskFactory可以看作是“任务编译器”-我们将一些初始“元代码”的输入传递给它,并且它生成实现它的Task类型的对象。例如,使用CodeTaskFactory,您可以将用C#任务编写的代码粘贴在.props文件中。例如,Qt VS Tools使用了类似的方法 <UsingTask TaskName="GetItemHash" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <Item ParameterType="Microsoft.Build.Framework.ITaskItem" Required="true" /> <Keys ParameterType="System.String[]" Required="true" /> <Hash Output="true" ParameterType="System.String" /> </ParameterGroup> <Task> <Using Namespace="System"/> <Using Namespace="System.Text"/> <Using Namespace="System.IO"/> <Using Namespace="System.IO.Compression"/> <Code Type="Fragment" Language="cs"> <![CDATA[ var data = Encoding.UTF8.GetBytes(string.Concat(Keys.OrderBy(x => x) .Select(x => string.Format("[{0}={1}]", x, Item.GetMetadata(x)))) .ToUpper()); using (var dataZipped = new MemoryStream()) { using (var zip = new DeflateStream(dataZipped, CompressionLevel.Fastest)) zip.Write(data, 0, data.Length); Hash = Convert.ToBase64String(dataZipped.ToArray()); } ]]> </Code> </Task> </UsingTask>
如果有人使用了类似的功能,请在注释中写出有趣的用例。仅此而已。我希望我能够展示在设置MSBuild时如何使在Visual Studio中处理大型项目变得简单和方便。如果您打算实现上述任何一项,我将给您一些建议:对于调试.props,.targets和.vcxproj,将MSBuild设置为“调试”级别的日志记录非常方便,在该级别中,它将非常仔细地描述输入和输出文件的操作。
感谢所有读完本书的人,我希望它变得有趣:)。在评论中分享您的msbuild食谱-我将尝试更新该文章,以便它成为在Studio中配置解决方案的详尽指南。