MSBuild による WPF プログラムのビルド
これまでの方法では C# コンパイラ (csc) で直にプログラムをビルドすることによって、WPF プログラムを作りました。
確かに XAML も読み込め、イベントも設定できました。しかし、少々面倒くさいという印象は拭えませんね。もっといい方法はないものでしょうか。
ここでは MSBuild を用いた WPF プログラムのビルド方法を紹介します。
実はこちらの方法が WPF の基本的なビルド方法といってよいものです。
WPF プログラムの基本構成
ここでは次のようなファイルを用意します。
- App.xaml
- App.xaml.cs
- MainWindow.xaml
- MainWindow.xaml.cs
- a.csproj
Application を定義する XAML ファイルとそのコードファイル。ウィンドウを定義する XAML とそのコードファイル。そして、MSBuild のビルド用スクリプト (a.csproj) です。
App.xaml は次のとおりです。
<Application x:Class="TestApp.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> </Application>
この中で StartupUri は MainWindow.xaml で定義されるウィンドウであることが指定されています。
App.xaml.cs は次のとおり。中身は空っぽですが、クラスが partial 指定されていることに注意してください。
using System; using System.Windows; namespace TestApp { public partial class App : Application { } }
「あれれ? Main はどこにいった?」 という疑問は残りますが、次へ行ってみましょう。
MainWindow.xaml は次のようになります。
<Window x:Class="TestApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WPF1" Height="300" Width="300"> <Grid> </Grid> </Window>
ここではウィンドウクラス名 (x:Class)、タイトル (Title)、高さ (Height)、幅 (Width) が指定されています。
中身は Grid が一応書いてますが、実際は何もしてないので空も同然です。
次に MainWindow.xaml.cs は次のとおりです。また partial 指定でクラスが定義されています。
using System; using System.Windows; namespace TestApp { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } }
「InitializeComponent って何だ?どこで定義しているんだろう」 という疑問はありますが、先に進みます。
MSBuild でのビルド
MSBuild (マイクロソフトビルドエンジン) は SDK に付属しているツールで、Visual Studio なしで実行可能なビルドツールです。最近の Visual Studio のプロジェクトファイルは MSBuild のビルドスクリプトになってます。
上記で書いたシンプルな WPF プログラムをビルドするためのシンプルなビルドスクリプトは次のようにかけます。これを a.csproj として保存します。
<?xml version="1.0" encoding="UTF-8"?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build"> <PropertyGroup> <OutputType>WinExe</OutputType> <RootNamespace>TestApp</RootNamespace> <AssemblyName>hello</AssemblyName> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <TargetFrameworkProfile>Client</TargetFrameworkProfile> <AppDesignerFolder>Properties</AppDesignerFolder> <OutputPath>bin\</OutputPath> <DebugSymbols>True</DebugSymbols> <DebugType>Full</DebugType> <Optimize>False</Optimize> <CheckForOverflowUnderflow>True</CheckForOverflowUnderflow> <DefineConstants>DEBUG;TRACE</DefineConstants> </PropertyGroup> <ItemGroup> <Reference Include="PresentationCore"> <RequiredTargetFramework>3.0</RequiredTargetFramework> </Reference> <Reference Include="PresentationFramework"> <RequiredTargetFramework>3.0</RequiredTargetFramework> </Reference> <Reference Include="System" /> <Reference Include="System.Core"> <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> <Reference Include="System.Xaml"> <RequiredTargetFramework>4.0</RequiredTargetFramework> </Reference> <Reference Include="System.Xml" /> <Reference Include="WindowsBase"> <RequiredTargetFramework>3.0</RequiredTargetFramework> </Reference> </ItemGroup> <ItemGroup> <ApplicationDefinition Include="App.xaml" /> </ItemGroup> <ItemGroup> <Compile Include="App.xaml.cs"> <SubType>Code</SubType> <DependentUpon>App.xaml</DependentUpon> </Compile> <Compile Include="MainWindow.xaml.cs"> <SubType>Code</SubType> <DependentUpon>MainWindow.xaml</DependentUpon> </Compile> </ItemGroup> <ItemGroup> <Page Include="MainWindow.xaml" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" /> </Project>
ビルドは次のようにします。
>msbuild a.csproj Microsoft (R) Build Engine Version 4.0.30319.1 [Microsoft .NET Framework, Version 4.0.30319.235] Copyright (C) Microsoft Corporation 2007. All rights reserved. Build started 7/28/2011 2:07:09 AM. Project "C:\src\test\wpf\wpf4\a.csproj" on node 1 (default targets). ResolveAssemblyReferences: A TargetFramework profile exclusion list will be generated. MainResourcesGeneration: Skipping target "MainResourcesGeneration" because all output files are up-to-date with respect to the input files. GenerateTargetFrameworkMonikerAttribute: Skipping target "GenerateTargetFrameworkMonikerAttribute" because all output files are up-to-date with respect to the input files. CoreCompile: Skipping target "CoreCompile" because all output files are up-to-date with respect to the input files. CopyFilesToOutputDirectory: a -> C:\src\test\wpf\wpf4\bin\hello.exe Done Building Project "C:\src\test\wpf\wpf4\a.csproj" (default targets). Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:00.15 >
これによって、作成された hello.exe を実行すると次のようになります。
確かにプログラムが実行できましたね。
裏側で生成されたコードを見る
ではここでいったい何が起きたのか、少し内部に突っ込んでみてみましょう。
上記のビルドによって、次のように obj ディレクトリ以下に中間ファイルが作成されたところに着目します。
この中の App.g.cs を開いてみます。(コメント等を一部省略します。)
#pragma checksum "..\..\App.xaml" "{406ea... ... using System; using System.Diagnostics; using System.Windows; using System.Windows.Automation; using System.Windows.Controls; using System.Windows.Controls.Primitives; ... namespace TestApp { ... public partial class App : System.Windows.Application { public void InitializeComponent() { #line 5 "..\..\App.xaml" this.StartupUri = new System.Uri( "MainWindow.xaml", System.UriKind.Relative); #line default #line hidden } [System.STAThreadAttribute()] [System.Diagnostics.DebuggerNonUserCodeAttribute()] public static void Main() { TestApp.App app = new TestApp.App(); app.InitializeComponent(); app.Run(); } } }
App.g.cs には上記のように Main メソッドがありました。ここからプログラムが起動するのですね。
次に MainWindow.g.cs をみてみます。
#pragma checksum "..\..\MainWindow.xaml" "{406... using System; using System.Diagnostics; using System.Windows; using System.Windows.Automation; using System.Windows.Controls; ... namespace TestApp { public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector { #line 7 "..\..\MainWindow.xaml" ... internal System.Windows.Controls.Button button1; #line default #line hidden private bool _contentLoaded; public void InitializeComponent() { if (_contentLoaded) { return; } _contentLoaded = true; System.Uri resourceLocater = new System.Uri( "/hello;component/mainwindow.xaml", System.UriKind.Relative); #line 1 "..\..\MainWindow.xaml" System.Windows.Application.LoadComponent(this, resourceLocater); #line default #line hidden } void System.Windows.Markup.IComponentConnector.Connect( int connectionId, object target) { switch (connectionId) { case 1: this.button1 = ((System.Windows.Controls.Button)(target)); #line 8 "..\..\MainWindow.xaml" this.button1.Click += new System.Windows.RoutedEventHandler(this.button1_Click); #line default #line hidden return; } this._contentLoaded = true; } } }
InitializeComponent メソッドが出てきました。上記 MainWindow.cs で記述した InitializeComponent は実はこれを呼び出すものだったのです。