ポータブル・クラス・ライブラリ(PCL)でプラットフォーム依存コードを使うには?[Win 8/WP 8]WinRT/Metro TIPS

各プラットフォーム共通のライブラリを作れる「ポータブル・クラス・ライブラリ」の中で、プラットフォーム依存のコードを呼び出すためのテクニックを説明する。

» 2013年01月24日 10時56分 公開
[山本康彦BluewaterSoft]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

「WinRT/Metro TIPS」のインデックス

連載目次

 前々回の記事「Windows 8とWindows Phone 8でコードを共有するには?」でポータブル・クラス・ライブラリ(Portable Class Library、汎用性のあるクラス・ライブラリ*1、以降PCL)を紹介した。その際、PCLからプラットフォーム依存のコードを呼び出せないので、現状ではロジックの全てをPCLで作るのは難しいといったことを述べた。

 しかし、DI(Dependency Injection、依存関係の挿入)テクニックを使えば、プラットフォームに依存するコードをPCL内から呼び出すことも可能になる。本稿では、その方法を解説する*2。本稿のサンプルは「Windows Store app samples:MetroTips #21」からダウンロードできる。

*1 マイクロソフトのドキュメントで翻訳が揺らいでいる。最近のものでは「汎用性のあるクラス ライブラリ」が多いが、多少古い文書やVS 2012のダイアログなどでは「ポータブル クラス ライブラリ」の訳が使われている。


*2 DIというともっぱらDIコンテナによる自動インジェクション(Automatically injected dependency)のことだけを指すイメージがあるが、DIコンテナによらない手動インジェクション(Manually injected dependency)もある。本稿では、DIコンテナは使用しない。


事前準備

 Windows 8(以降、Win8)向けのWindowsストア・アプリを開発するには、Win 8とVisual Studio 2012(以降、VS 2012)が必要である。これらを準備するには、第1回のTIPSを参考にしてほしい。

 Windows Phone 8(以降、WP 8)向けのアプリを開発するには、SLAT対応CPUを搭載したPC上の64bit版Win 8 Pro以上Windows Phone SDK 8.0(無償)が必要となる。

 そして、PCLを作成するには、有償のVS 2012が必要である(PCLのバイナリはExpressでも利用可)。お持ちでない読者も、試用版が提供されているので、ぜひ試してみてほしい。

PCLが依存したい機能を、外部からPCLに挿入する

 前々回の記事で問題として挙げた例は、シフトJISでエンコードされたデータをUnicode文字列に変換できないことであった。つまり、PCL内のコードはシフトJISデータをUnicode文字列に変換する機能に依存したい(=使いたい)のだが、WP 8ではその機能がPCLからアクセスできるところに存在しないのだ。

 通常、PCLは依存される側だ。プロジェクトの参照方向は「Win 8アプリ/WP8 アプリ→PCL」だ。PCLからWin 8アプリ/WP 8アプリ内のメソッドを呼び出すことはできない。

 それならば、逆にWin 8アプリ/WP 8アプリ側からその機能をPCL内に挿入できないものだろうか?

 次のようにすれば、それが可能になる。

  (1) PCL側にインターフェイスを定義する
  (2) PCL内では、そのインターフェイスを使う
  (3) Win 8アプリ/WP 8アプリ側に、そのインターフェイスの実装を置く
  (4) Win 8アプリ/WP 8アプリ側で、そのインターフェイスの実装のインスタンスを作り、PCL内に挿入する

DIテクニック
PCL内で使いたい機能の実装をWin 8アプリ/WP 8アプリ側に置く。Win 8アプリ/WP 8アプリ側でそれをインスタンス化し、PCLに挿入する(挿入ポイントは描いてない)。

【PCL側】依存性を挿入してもらうには?

 インターフェイスを定義し、それを使うようにする。また、インターフェイスの実装を挿入してもらう場所を用意する。

 ここでは、シフトJISデータをUnicode文字列に変換するメソッド(および、それを持つクラスのインスタンス)を挿入してもらえるようにしてみよう。インターフェイス(=接続部)としては、いわゆる“インターフェイス”(=実装を持たないクラス)ではなく「抽象クラス」を使うことにする(次のコード)。挿入してもらう場所も同時に定義できて便利だからだ。

public abstract class Injected
{
  // プラットフォーム依存のインスタンスを、ここにセットする
  public static Injected Current { get; set; }

  // 継承先で、プラットフォーム依存のコードを実装する
  public abstract string DecodeShiftJis(byte[] buff);
}

Public MustInherit Class Injected

  ' プラットフォーム依存のインスタンスを、ここにセットする
  Public Shared Property Current As Injected

  ' 継承先で、プラットフォーム依存のコードを実装する
  Public MustOverride Function DecodeShiftJis(buff As Byte()) As String

End Class

Injected抽象クラス:依存性を挿入してもらうためのインターフェイス定義(上:C#、下:VB)
挿入してもらう場所として、Currentプロパティも一緒に定義した。

【PCL側】挿入してもらった実装を使うには?

 上で定義したInjectedクラスのCurrentプロパティに実装があるものとして、コーディングすればよい。

public class PclClass
{
  public static string GetData()
  {
    byte[] buff = GetSampleData();
    return Injected.Current.DecodeShiftJis(buff); // 挿入された実装を呼び出す
  }

  private static byte[] GetSampleData()
  {
    // サンプル・データ
    byte[] buff = new byte[] {
      0x8E, 0x8E, 0x8C, 0xB1, 0x82, 0xC5, 0x82, 0xB7, 0x00,
      // “試験です”というシフトJIS文字列のバイト列データ
    };
    return buff;
  }
}

Public Class PclClass

  Public Shared Function GetData() As String
    Dim buff() As Byte = GetSampleData()
    Return Injected.Current.DecodeShiftJis(buff) ' 挿入された実装を呼び出す
  End Function

  Private Shared Function GetSampleData() As Byte()
    ' サンプル・データ
    Dim buff() As Byte = { _
      &H8E, &H8E, &H8C, &HB1, &H82, &HC5, &H82, &HB7, &H0 _
    } ' “試験です”というシフトJIS文字列のバイト列データ
    Return buff
  End Function
End Class

PclClassクラス:挿入してもらった実装を使う(上:C#、下:VB)

 何もせずにPclClassクラスのGetDataメソッドを呼び出すと、Currentプロパティには何もセットされていないのでNULL参照例外が出る。以降で説明するようにして、あらかじめ実装を挿入してから呼び出さねばならない。

【Win 8アプリ/WP 8アプリ側】インターフェイスの実装を置くには?

 PCLのプロジェクトへの参照を設定した後、Injected抽象クラスを継承したクラスを作り、実装を与える。

 Win 8アプリ側ではEncodingクラスが使えるので、次のようにシンプルに書ける。

public class InjectedFromWin8 : Pcl.Injected
{
  public override string DecodeShiftJis(byte[] buff)
  {
    return Encoding.GetEncoding("Shift-JIS").GetString(buff, 0, buff.Length);
  }
}

Public Class InjectedFromWin8
  Inherits PclVB.Injected

  Public Overrides Function DecodeShiftJis(buff() As Byte) As String
    Return Encoding.GetEncoding("Shift-JIS").GetString(buff, 0, buff.Length)
  End Function
End Class

InjectedFromWin8クラス:Win 8アプリ側から挿入する実装(上:C#、下:VB)

 WP 8アプリ側ではEncodingクラスが使えないので、代わりに前回の記事で紹介したWin32ApiSampleクラス(=Win32 APIのMultiByteToWideChar関数を使うWindows Phoneランタイム・コンポーネント(以降、WinPRT))を使う。前回の記事で作成したWPRuntimeComponentSampleプロジェクトへの参照を追加してから、次のように実装する。

public class InjectedFromWP8: Pcl.Injected
{
  public override string DecodeShiftJis(byte[] buff)
  {
    return WPRuntimeComponentSample.Win32ApiSample.MultiByteToWideChar(buff);
  }
}

Public Class InjectedFromWP8
  Inherits PclVB.Injected

  Public Overrides Function DecodeShiftJis(buff() As Byte) As String
    Return WPRuntimeComponentSample.Win32ApiSample.MultiByteToWideChar(buff)
  End Function
End Class

InjectedFromWP8クラス:WP 8アプリ側から挿入する実装(上:C#、下:VB)

【Win 8アプリ/WP 8アプリ側】実装のインスタンスを作ってPCLに挿入するには?

 PCL内で呼び出される前に挿入できればどこでもよいのだが、ここではアプリ起動時に実行して確実を期すことにしよう。App.xaml.cs/vbファイルのAppクラスのコンストラクタに次のように記述を追加する。

 まず、Win 8側。

public App()
{
  ……略……

  // PCLにInjectedの実装を与える
  Pcl.Injected.Current = new InjectedFromWin8();
}

Public Sub New()

  ……略……

  ' PCLにInjectedの実装を与える
  PclVB.Injected.Current = New InjectedFromWin8()

End Sub

Win 8のAppクラス:実装のインスタンスを作ってPCLに挿入する(上:C#、下:VB)

 そして、WP 8側。挿入する実装が異なる以外、Win 8と同じである。

public App()
{
  ……略……

  // PCLにInjectedの実装を与える
  Pcl.Injected.Current = new InjectedFromWP8();
}

Public Sub New()

  ……略……

  ' PCLにInjectedの実装を与える
  PclVB.Injected.Current = New InjectedFromWP8()

End Sub

WP 8のAppクラス:実装のインスタンスを作ってPCLに挿入する(上:C#、下:VB)

【Win 8アプリ/WP 8アプリ側】実行するには?

 最後に、アプリから呼び出す部分を書けば完成である。メインページが表示されたときにPCLのメソッドを呼び出し、結果をテキストブロックに表示してみよう。

 まずはWin 8の例。

protected override void LoadState(Object navigationParameter,
                                  Dictionary<String, Object> pageState)
{
  textBlock1.Text = Pcl.PclClass.GetData();
}

Protected Overrides Sub LoadState(navigationParameter As Object, _
                                  pageState As Dictionary(Of String, Object))
  textBlock1.Text = PclVB.PclClass.GetData()
End Sub

Win 8のMainPageクラス:PCLのメソッドを呼び出してみる(上:C#、下:VB)

 このとき、メソッドの呼び出し関係は次のようになっている。

メソッドの呼び出し関係(Win 8の場合)

 「Win 8アプリ→PCL→Win 8アプリ」という呼び出しのチェーンは、通常なら循環参照となってVisual Studioからエラーを出されてしまうところだが、DIによって可能になっている。

 次いで、WP 8の場合。

public MainPage()
{
  ……略……

  textBlock1.Text = Pcl.PclClass.GetData();
}

Public Sub New()

  ……略……

  textBlock1.Text = PclVB.PclClass.GetData()

End Sub

WP 8のMainPageクラス:PCLのメソッドを呼び出してみる(上:C#、下:VB)

 このときのメソッドの呼び出し関係は、次のようになっている。

メソッドの呼び出し関係(WP 8の場合)

まとめ

 DIテクニックを駆使することで、ロジックを途切れることのない形でPCL内に実装することが可能になる。ただし、ここまで複雑なことをしてまで実現すべきことなのかどうかは、プロジェクトごとに慎重に検討すべきであろう。

 DIテクニックを使ってPCL内にロジックを作り込む方法について、次の記事が参考になる。

「WinRT/Metro TIPS」のインデックス

WinRT/Metro TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。