Condividi tramite


Creating a VSIX Deployable C++ project template.

The problem:

Recently I had a customer inquire about an issue they had encountered with a custom C++ project template they had created. Using a
vstemplate based project template, they were able to successfully create a C++ project, but noticed a discrepancy in the project’s General Property Page. Specifically, the “Target Platform”, and “Windows SDK Version” settings appeared to be incorrect. For example:

ProjectProperties

Whereas, if they created a new C++ DLL project using the standard  DLL template that comes with Visual Studio:

NewStockDllProject

The resulting projects “Target Platform” and “Windows SDK Version” were set to:

ProjectProperties2

Typically, whenever I see issues like these, the first thing I do is compare the .vcxproj files to see what might be different. And sure enough, the .vcxproj generated by the standard template included the following:

   <PropertyGroup Label="Globals">
    <VCProjectVersion>15.0</VCProjectVersion>
    <ProjectGuid>{C56BA256-602E-4F31-AC6D-8453D7BFBCB3}</ProjectGuid>
    <Keyword>Win32Proj</Keyword>
    <RootNamespace>MyStandardDLLProject</RootNamespace>
     < WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion> 
  </PropertyGroup>

To confirm, I removed the above <WindowsTargetPlatformVersion> tag from the .vcxproj, reloaded the project, and observed, the “Target Platform” and “Windows SDK Version” settings changed to “Windows” and “8.1” respectively. So it was pretty obvious the culprit was a missing <WindowsTargetPlatformVersion> entry in the .vcxproj. But upon examining the .vcxproj files used by various VC++ templates (located under the C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\VC\VCWizards directory), none of these .vcxproj files contained a <WindowsTargetPlatformVersion> entry.

So it was pretty safe to assume this entry was programmatically added after the fact. Searching the files under “C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\VC\VCWizards” for “WindowsTargetPlatformVersion” quickly revealed the StampWindowsTargetPlatformVersion method in the “C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\VC\VCWizards\1033\common.js” file:

 function StampWindowsTargetPlatformVersion(oProj)
{     
   var strLatestWindowsSDKVersion = GetLatestWindowsSDKVersion(oProj);
   if (strLatestWindowsSDKVersion)
   {
      //Stamp the latest Windows SDK version found in the disk - It is a global property, so enough to do it for any one config
      var commonConfigRule = oProj.Object.Configurations(1).Rules("ConfigurationGeneral");
      commonConfigRule.SetPropertyValue("WindowsTargetPlatformVersion", strLatestWindowsSDKVersion);
   }
}

And subsequently, the GetLatestWindowsSDKVersion method:

 function GetLatestWindowsSDKVersion(oProj)
{     
   return oProj.Object.LatestTargetPlatformVersion;
}

The solution:

Given that we want to use .VSIX deployment and consequently a .vstemplate based template, it’s fairly obvious we’ll need to implement and associate a custom IWizard implementation to the template. To set the WindowsTargetPlatformVersion property, you can use the VCProject.LatestTargetPlatformVersion property, and the IVCRulePropertyStorage.SetPropertyValue method in an IWizard.ProjectFinishedGenerating method similar to the following:

 public void ProjectFinishedGenerating(Project project)
{
   // use VCProject.LatestTargetPlatformVersion property, which is what the stock wizards use.    
   VCProject vcProject = (VCProject)project.Object;    
   string wtpv = vcProject.LatestTargetPlatformVersion;    
   if (wtpv != null)    
   {
      // we only have to do this for a single config, as the property in question is global.
      IVCCollection configs = (IVCCollection)vcProject.Configurations;
      VCConfiguration firstConfig = (VCConfiguration)configs.Item(1);
      IVCRulePropertyStorage rule = (IVCRulePropertyStorage)firstConfig.Rules.Item("ConfigurationGeneral");
      rule?.SetPropertyValue("WindowsTargetPlatformVersion", wtpv);
   }
}

The final issue that needed to be addressed was a missing .vcxproj.filters file. The project template contained a SimpleDLL.vcxproj.filters file, which was also referenced as a ProjectItem in the templates .vstemplate manifest. And while I could see the SimpleDLL.vcxproj.filters file was included in the .VSIX, it wasn’t showing up along side the SimpleDLL.vcxproj. This is due to a limitation of the IDE’s project template wizard implementation. Files included via the projects’ .vstemplate manifest are copied to a temp directory when the template is first unpacked, but only the files referenced by the project file are then copied to the final destination directory. Because .vcxproj.filters files aren’t referenced in the .vcxproj file, these will need to be programmatically copied from the temp directory, to the final destination directory.  And given we already have a custom IWizard to handle setting the WindowsTargetPlatformVersion, this can be easily added to IWizard.RunStarted method similar to the following:

 public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
{
   _dte = (DTE)automationObject;

   // manually copy over the SimpleDLL.vcxproj.filters file because it isn't
   // referenced by the .vcxproj, and the wizard engine doesn't copy it to
   // the project's destination directory.

   // note, customParams[0] is the full path to the .vstemplate file, so we'll
   // use it's folder to find the needed SimpleDLL.vcxproj.filters file.
   string sourceFile = Path.Combine(Path.GetDirectoryName((string)customParams[0]), "SimpleDll.vcxproj.filters");
   string destFile = Path.Combine(replacementsDictionary["$destinationdirectory$"], string.Format("{0}.vcxproj.filters", replacementsDictionary["$projectname$"]));
   File.Copy(sourceFile, destFile);
}

So there you have it. To create a VSIX deployable C++ project template, that properly sets the WindowsTargetPlatformVersion property, and includes a .vcxproj.filters file, you can implement and associate a custom IWizard assembly with your project template as described above. A working example can be found in the VSXArcana github repo at : https://github.com/EdDore/VSXArcana/tree/master/CppDLLProjectTemplate.