はじめに
MSBuildがVisual C++ 2010で導入されたことにより、C++のコンパイルとリンクのプロセスに高度なカスタマイズと拡張の可能性が広がりました。MSBuildが導入されるまで、ビルドプロセスを拡張するとしたら、ビルドの前後にプロセスを追加する以外に方法はありませんでした。このやり方は柔軟性に乏しく、他のビルドプロセスとの統合も不可能です。これとは対照的にMSBuildを利用する方法では、拡張した機能を正確に調整し、ビルドプロセス全体と統合することができます。新しい形式のVisual C++プロジェクトファイル(vcxproj)は、ビルド中に実行すべき任意のタスクを完了するようにカスタマイズできます。
前回のVisual C++.NETの使用に関する記事では、MSBuildの基礎知識としてタスクについて学びました。タスクは、ビルドプロセスを作成するための建築ブロックです。タスクは、渡されたアイテムとプロパティをもとにアクションを完了します。タスクはターゲット内にチェーンされ、このターゲットがMSBuildファイルへのエントリポイントとなります。MSBuildとVisual C++には、あらかじめ定義された多数のタスクが付属します。ただし、MSBuildはその目的が拡張性の提供であることから、MSBuildアセンブリに用意されたヘルパークラスを使って、カスタムタスクの開発を非常に簡単に実行できます。前回の記事の復習として、カスタムMSBuildタスクをC++/CLIでビルドし、Visual C++ 2010プロジェクトで使用する手順から説明します。
ハードリンクタスクを作成する
NTFS(NT File System)にはハードリンクと呼ばれる機能があり、複数のディレクトリエントリを同一のファイルにマップすることができます。ハードリンクには、ファイルを単にコピーした場合と比べて多くの利点があります。最大の利点は、すべてのディレクトリエントリが同一の物理ファイルを指すため、ファイルを更新すると、そのファイルにアクセスするためにどのディレクトリエントリを使ったかに関係なく、ファイルのすべてのコンシューマに更新が自動的に反映されることです。ハードリンクの使用はディスク領域の節約にもなります。同じファイルのコピーがディスクのランダムな場所にいくつも散在することがないからです。
ハードリンクを作成するタスクはMSBuildに付属しません。また、ハードリンクを作成するマネージドAPIもないため、ハードリンクを作成するカスタムのMSBuildタスクを開発することは、ネイティブとマネージドの世界をシームレスに結び付けるC++/CLIの威力を示す理想的な実習です。
ハードリンクを作成するWindows SDK関数は、以下のように簡単なものです。
BOOL WINAPI CreateHardLink( __in LPCTSTR lpFileName, __in LPCTSTR lpExistingFileName, __reserved LPSECURITY_ATTRIBUTES lpSecurityAttributes );
CreateHardLinkを呼び出すときにLPSECURITY_ATTRIBUTESパラメータの値はNULLでなければなりません。したがって、MSBuildカスタムタスクからWindows SDK関数に渡す必要があるパラメータは、リンク元とリンク先のファイル名のみです。この2つのパラメータを指定しないとCreateHardLinkを呼び出せないため、カスタムタスクにこれらの情報を確実に渡す必要があります。カスタムMSBuildタスクでは、省略できないタスクプロパティをRequired属性を使って定義できます。このような要件は、C++/CLIクラスでは次のように定義されます。
public ref class HardLinkTask:
public Microsoft::Build::Utilities::Task
{
public:
virtual bool Execute() override;
[Microsoft::Build::Framework::Required]
property String^ SourceFileName;
[Microsoft::Build::Framework::Required]
property String^ TargetFileName;
};
カスタムタスクを作成するプロセスを簡略化するために、Microsoft::Build::Utilities::Task基底クラスを使用します。カスタムタスクの実際の要件はMicrosoft::Build::Framework::ITaskインターフェイスを実装することですが、その場合にはタスクをビルドプロセスに統合するために決まりきったコードを大量に書く必要があります。Microsoft::Build::Utilities::Taskにはこのような標準インターフェイスメソッドが既に実装されているため、カスタムタスクのExecuteメソッドを実装するだけで作業は終了します。Executeは、タスクの実行時にビルドプロセスから呼び出されるメソッドです。Executeから返されるBoolean値は、タスクの実行が成功したか、失敗したかを示します。
Executeを実装するには、タスクのマネージドSystem::StringプロパティをLPCTSTR変数に変換し、CreateHardLink関数を呼び出す必要があります。また、タスクのエンドユーザーがエラーを診断するために参照できるエラー情報を、MSBuildの標準ログインフラストラクチャを使って記録する必要もあります。このような要件を満たしてExecuteメソッドを実装したのが次のコードです。
using namespace System::Runtime::InteropServices;
using namespace msclr::interop;
bool HardLinkTask::Execute()
{
marshal_context mc;
if (CreateHardLink(
mc.marshal_as<LPCWSTR>(TargetFileName),
mc.marshal_as<LPCWSTR>(SourceFileName), NULL)){
Log->LogMessage(String::Format(
"Hard link successfully created from {0} to {1}",
SourceFileName, TargetFileName));
return true;
}
else{
int errorHresult = Marshal::GetHRForLastWin32Error();
Exception^ ex = Marshal::GetExceptionForHR(errorHresult);
Log->LogErrorFromException(ex);
return false;
}
}
C++/CLIマーシャリングライブラリ(以前の記事を参照)を使って文字列を変換し、エラー時にはSystem::Runtime::InteropServices::Marshalを使ってWin32エラーコードを.NET例外に変換します。.NET例外は、エンドユーザーに表示するためMicrosoft::Build::Utilities::Task.Logヘルパーオブジェクトに直接渡すことができます。前記の2つのコードブロックが、カスタムタスクの定義と実装に必要なコードのすべてです。
