February 24, 2021
Hot Topics:

Custom MSBuild Task Development with Visual C++

  • By Nick Wienholt
  • Send Email »
  • More Articles »

The introduction of MSBuild in Visual C++ 2010 opens up the C++ compilation and linking process to a high degree of customization and extension. Prior to MSBuild, the build process could only be extended with pre- and post-build steps that offered limited flexibility and were decoupled from the rest of the build process. In contrast, MSBuild allows fine-tuned, integrated extension, and any new-format Visual C++ project file (vcxproj) can be customized to complete whatever task is required during a build.

In my last Using Visual C++.NET article, MSBuild basics were covered, including Tasks, which are the building-blocks of the build process. Tasks take items and properties to complete some action, and tasks are chained together in targets, with targets being the entry point into a MSBuild file. MSBuild and Visual C++ ship with a large number of predefined tasks, but in the spirit of MSBuild extensibility, custom task development is extremely easy with helper classes present in the MSBuild assemblies. The remainder of this article will cover the process of building a custom MSBuild task in C++/CLI and then using this task in a Visual C++ 2010 project.

Developing the Hard Link Task

The NT File System (NTFS) supports a feature known as hard links, which allows multiple directory entries to map to the same underlying file. Hard links have a number of advantages over simply copying a file, with the main advantage being that, because all directory entries point to the same physical file, an update to the file is automatically available to all consumers of the file regardless of which directory entry the file is accessed through. Using hard links also conserves disk space, as multiple copies of the same file are not scattered randomly across a drive.

MSBuild does not ship with a task for creating hard links, and as there is no managed API for creating hard links, the exercise of creating a custom MSBuild task to create hard links is ideally suited to the power of C++/CLI in seamlessly bridging the native and managed worlds.

The Windows SDK function to create a hard link is quite simple:

BOOL WINAPI CreateHardLink(
   __in       LPCTSTR lpFileName,
   __in       LPCTSTR lpExistingFileName,
   __reserved LPSECURITY_ATTRIBUTES lpSecurityAttributes

The LPSECURITY_ATTRIBUTES parameter must be NULL when CreateHardLink is called, so only the two parameters need to be passed from the MSBuild custom task to the Windows SDK function are the source and destination file name. Without the two parameters, calling CreateHardLink is not possible, so it is important that the custom task has these properties passed in. Custom MSBuild tasks can designate that a task property as mandatory with the use of the Required attribute, and these requirements lead to the following C++/CLI class definition:

public ref class HardLinkTask:
   public Microsoft::Build::Utilities::Task
   virtual bool Execute() override;

   property String^ SourceFileName;

   property String^ TargetFileName;

The Microsoft::Build::Utilities::Task base class is used to simplify the process of creating the custom task. The actual requirement for a custom Task is the implementation of the Microsoft::Build::Framework::ITask interface, but this requires a fair amount of boiler-plate code to be written to integrate the task into the build process, and Microsoft::Build::Utilities::Task implements these standard interface methods, leaving only the Execute method of the custom task to be implemented. Execute is the method called by the build process when the task executes, and a Boolean value is returned by Execute to indicate whether the task has been successful.

To implement Execute, the managed System::String properties of the task need to be converted to LPCTSTR variables, the CreateHardLink function needs to be called, and any error information needs to be logged using the standard MSBuild logging infrastructure so error conditions can be diagnosed by the end-users of the task. With these requirements, a simple implementation of Execute would look something like the following:

using namespace System::Runtime::InteropServices;
using namespace msclr::interop;

bool HardLinkTask::Execute()
   marshal_context mc;

   if (CreateHardLink(
      mc.marshal_as<LPCWSTR>(SourceFileName), NULL)){
            "Hard link successfully created from {0} to {1}",
            SourceFileName, TargetFileName));
         return true;
      int errorHresult = Marshal::GetHRForLastWin32Error();
      Exception^ ex = Marshal::GetExceptionForHR(errorHresult);
      return false;

The C++/CLI marshalling library (covered previously in this article) is used for the string conversion, and System::Runtime::InteropServices::Marshal is used to convert from a Win32 error code to a .NET exception in the case of an error. The .NET exception then can be passed directly to the Microsoft::Build::Utilities::Task.Log helper object for display to the end user. The preceding two code snippets are all that is required to define and implement a custom task.

Page 1 of 2

This article was originally published on March 2, 2009

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date