モノトーンの伝説日記

OBS Studio と Blackmagic Design が大好き。

C++/CX で作成した Windows Runtime 1.3 component (win81/wpa81) と UWP 向け Windows Runtime component を混合して C#/VB/C++ プロジェクトから使える 1 つの NuGet package を作成する方法について

 タイトル長いけどわかったので書きます。

概要

  1. NuGet v2 / v3 の違い
  2. UAP10.0 の NuGet package ロード順
  3. NuGet packgage の作成
  4. まとめ

1. NuGet v2 / v3 の違い

 まず、NuGet package をローカルでテストするわけですけど、まずこの違いに引っ掛かった。v2 時代はソリューション フォルダー直下に packages っていうフォルダーが作られてそこに package が一時保存されるわけですが、v3 時代は %UserProfile%\.nuget\packages\ にインストールされる。

 自分は勘違いしていたのですが、csproj の有無で packages がどこのものを参照するのか変わるのかと思っていたのですが、実は project.json に依存するようです。このあたりテスト中は注意が必要ですね。

2. UAP10.0 の NuGet package ロード順

 UAP10.0 つまり UWP に関しては v3 の挙動をするので、ロード順はめちゃくちゃっぽくて、通常今までのやり方に即していると win81uap10.0 の両方の targets ファイルを読み込んでしまうようです。これにより、両方の winmddll が通ったり、VCLibs が Version = 12 のモノを読み込んでしまってえらいことになります。

 そこでハイブリッド型では win81 側の targets に win81 から読んでも問題ないような細工を施して uap10.0 から問題ないようロードできるようにします。

3. NuGet packgage の作成

3.1 ファイル構成

 新時代基準の構成に伴い win81wpa81 の構成も見直しています。

※ フォルダー下の一部は同一構成なので省略しています

+ build
|   + native
|   |   + XYZ.targets
|   |
|   + win81
|   + wpa81
|   + uap10.0
|
|
+ lib
|   + win81
|   |   + XYZ.winmd
|   |
|   + wpa81
|   + uap10.0
|
+ runtimes
    + win81-x86
    |   + native
    |       + XYZ.dll
    |
    + win81-x64
    + win81-ARM
    + wpa81-x86
    + wpa81-ARM
    + win10-x86
    + win10-x64
    + win10-ARM

3.2 native な targets 実装

 割とシンプルです。このケースは一ファイルに全部記述する感じになります。

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <Mntone-Platform Condition="'$(Platform)' == 'Win32'">x86</Mntone-Platform>
        <Mntone-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Mntone-Platform>
    </PropertyGroup>

    <PropertyGroup Condition="'$(PlatformToolset)'=='v120'" Label="win81">
        <Mntone-FrameworkFolder>win81</Mntone-FrameworkFolder>
    </PropertyGroup>
    
    <PropertyGroup Condition="'$(PlatformToolset)'=='v120_wp81'" Label="wpa81">
        <Mntone-FrameworkFolder>wpa81</Mntone-FrameworkFolder>    
    </PropertyGroup>

    <PropertyGroup Condition="'$(PlatformToolset)'=='v140'" Label="uwp">
        <Mntone-FrameworkFolder>uap10.0</Mntone-FrameworkFolder>
        <Mntone-RuntimeFolder>win10</Mntone-RuntimeFolder>
    </PropertyGroup>

    <PropertyGroup Condition="'$(Mntone-RuntimeFolder)'==''" Label="uwp">
        <Mntone-RuntimeFolder>$(Mntone-FrameworkFolder)</Mntone-RuntimeFolder>
    </PropertyGroup>

    <Target Name="InjectReference" BeforeTargets="ResolveAssemblyReferences">
        <ItemGroup>
            <Reference Include="$(MSBuildThisFileDirectory)..\..\lib\$(Mntone-FrameworkFolder)\Mntone.WinRtLibrary.winmd">
                <Implementation>Mntone.WinRtLibrary.dll</Implementation>
            </Reference>
            <ReferenceCopyLocalPaths Include="$(MSBuildThisFileDirectory)..\..\runtimes\$(Mntone-RuntimeFolder)-$(Mntone-Platform)\native\Mntone.WinRtLibrary.dll" />
        </ItemGroup>
    </Target>
</Project>

3.3 win81 な targets 実装

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="CheckAnyCPU" Condition="'$(Platform)' != 'x64' AND '$(Platform)' != 'x86' AND '$(Platform)' != 'ARM'" BeforeTargets="BeforeBuild">
        <Error Text="Mntone WinRT Library does not support the $(Platform) target platform." />
    </Target>
    <Target Name="CheckJS" Condition=" '$(MSBuildProjectExtension)' == '.jsproj' " BeforeTargets="BeforeBuild">
        <Error Text="Mntone WinRT Library does not support the JavaScript target project." />
    </Target>

    <Target Name="InjectReference" BeforeTargets="ResolveAssemblyReferences">
        <ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'UAP'">
            <Reference Include="$(MSBuildThisFileDirectory)..\..\lib\win81\Mntone.WinRtLibrary.winmd">
                <Implementation>Mntone.WinRtLibrary.dll</Implementation>
            </Reference>
            <ReferenceCopyLocalPaths Include="$(MSBuildThisFileDirectory)..\..\runtimes\win81-$(Platform)\native\Mntone.WinRtLibrary.dll" />
        </ItemGroup>
    </Target>

    <ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'UAP'">
        <SDKReference Include="Microsoft.VCLibs, Version=12.0">
            <Name>Microsoft Visual C++ 2013 Runtime Package for Windows</Name>
        </SDKReference>
    </ItemGroup>
</Project>

 ここで特殊なのは Condition="'$(TargetPlatformIdentifier)' != 'UAP'" という条件を加えていることです。これで UWP における win81\.targets が読み込まれてコンパイルがうまくいかない問題を回避します。

3.4 その他

 3.3 の UAP 判別条件を外したものを適応すればおkです。

4. まとめ

 C++/CX を使った Windows Runtime component を書く人はかなり少ないと思います。ですが、C++/CX を使ってやりたいことは多々あるはずですが、この類のブログ記事ってのはあまり存在しないわけで。

 .NET だけなら構成がもっとシンプルで targets ファイルですら不要なものもあるわけです。

 今回は csproj, vbproj, vcxproj の Windows ストア アプリ、Windows Phone ストア アプリ (Appx-based)、UWP のハイブリッド NuGet package を作るというお話でした。C++/CX を使ってカスタムコントロールを作るとかそういうのにはこのパッケージ作成方法は欠かせないので、頭の片隅に入れておいてもいいと思います。

 以上です。