ポータブル・クラス・ライブラリ(PCL)でプラットフォーム依存コードを使うには?[Win 8/WP 8]:WinRT/Metro TIPS
各プラットフォーム共通のライブラリを作れる「ポータブル・クラス・ライブラリ」の中で、プラットフォーム依存のコードを呼び出すためのテクニックを説明する。
powered by Insider.NET
前々回の記事「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内に挿入する
●【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
挿入してもらう場所として、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クラスの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
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
●【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
そして、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
●【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アプリ→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
このときのメソッドの呼び出し関係は、次のようになっている。
●まとめ
DIテクニックを駆使することで、ロジックを途切れることのない形でPCL内に実装することが可能になる。ただし、ここまで複雑なことをしてまで実現すべきことなのかどうかは、プロジェクトごとに慎重に検討すべきであろう。
DIテクニックを使ってPCL内にロジックを作り込む方法について、次の記事が参考になる。
Copyright© Digital Advantage Corp. All Rights Reserved.