昨日 [win8] metro アプリケーションからデスクトップアプリにプロセス間通信する の続き。
# 追記 2012/05/12
# 再度確認したところ、localhost によるループバック接続はパッケージを作った時は駄目で、Visual Studio からデバッグ実行しただけ接続できます。このあたり、hosts 書き換え、ip 指定でも駄目なので、別の方式を考えないと。以下は、参考のため残しておきます。
# 業務的には、別マシンに proxy を立てて localhost->proxy->localohst にすれば ok なんですが、もうちょっとうまい方法を考えますか。ネットワーク負荷がかかるし。
metro アプリで HttpClient クラスを使ってローカルホスト(localhost)に接続できることが分かったのですが、「さて、データ形式はどうしようか」ということで再考しておりました。 やっぱり、データ形式は XML 形式がいいよねと、どうせならばクライアントは WCF 形式で繋げられるとよいよね、と考えた挙句…ああ、WCF で使えばいいよね、とひと巡りして来てしまいました、という話。
「System.Net.HttpListenerException: アクセスが拒否されました。」と表示されてしまう
http://social.msdn.microsoft.com/Forums/ja-JP/wcfja/thread/4b1572df-a780-45b0-9488-cb4e3b95b53f
をよく見ると、実は WCF の話だったのですね。なるほど、というわけで
ServiceContractAttribute クラス (System.ServiceModel)
http://msdn.microsoft.com/ja-jp/library/system.servicemodel.servicecontractattribute.aspx
WCF クライアントを使用したサービスへのアクセス
http://msdn.microsoft.com/ja-jp/library/ms734691.aspx
を参考にしながら、
- desktop アプリで WCF サーバー
- metro アプリで WCF クライアント
を作成していきます。
■desktop アプリ(windows form アプリ)で WCF サーバー
基本は、ServiceContract、OperationContract 属性を使ってサービスで公開するメソッドを作るのと、ホストを ServiceHost クラスで作るところです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | using System.ServiceModel; using System.ServiceModel.Description; namespace SampleWcfMetroServer { public partial class Form1 : Form { public Form1() { InitializeComponent(); } ServiceHost serviceHost; // MetroService host; private void button1_Click( object sender, EventArgs e) { // 開始 serviceHost = new ServiceHost( typeof (MetroService), new Uri( "http://localhost:8082/metro" )); //serviceHost.AddServiceEndpoint( // typeof(IMetroService), // new BasicHttpBinding(), ""); ServiceMetadataBehavior smb = new ServiceMetadataBehavior(); smb.HttpGetEnabled = true ; smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15; serviceHost.Description.Behaviors.Add(smb); // これは無駄 // host = new MetroService(); // host.OnNotiry += host_OnNotiry; // 自分のオブジェクトを static に登録 Form1.me = this ; serviceHost.Open(); listBox1.Items.Add( "サービス開始" ); } void host_OnNotiry( string name) { // イベントを受ける listBox1.Items.Add(name + " " + DateTime.Now.ToString()); } private void button2_Click( object sender, EventArgs e) { serviceHost.Close(); listBox1.Items.Add( "サービス終了" ); } public void OnNotiry( string message ) { this .listBox1.Items.Add( message ); } public static Form1 me; } [ServiceContract] public interface IMetroService { [OperationContract] string GetDate( string name); } public class MetroService : IMetroService { public string GetDate( string name) { // 実行時にオブジェクトが作成されるので、こっちは効かない。 if ( OnNotiry != null ) OnNotiry( "called GetDate" ); // 仕方がないので、static で公開したフォームに送信させる。 Form1.me.OnNotiry( "called GetDate" ); return string .Format( "{0}:{1}" , name, DateTime.Now.ToString()); } // public Action<string> OnNotiry(string name); public delegate void OnNotiryHandler( string name ); public event OnNotiryHandler OnNotiry; } } |
巷のサンプルでは、公開するクラス内で処理を行っていますが、そのままだと ListBox へ表示するなどの GUI 周りが使えないので、元のフォームにイベントメッセージを飛ばします。WCF で利用するクラスは、.NET リモートは違って接続時に動的に作成される(らしい)ので、フォームへのイベント飛ばしがちょっと妙なことになっています。このあたりは、あとで考えるということで。
■テスト用の WCF クライアントを作成
最初は、Windows Form アプリで試してみます。プロジェクトから先の WCF サービスを参照設定させるために、あらかじめ WCF サーバーを起動させておいて、プロジェクトから「サービスの参照を追加」します。WCF サーバーは管理者権限で起動するか、netsh を使ってポートを URL を登録しておきます。
ローカルのサービスの URL を指定して「Go」を押して検索します(このあたりは、Visual Studio 2010 も同じ)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | namespace SampleWcfMetroClient { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click( object sender, EventArgs e) { // 送信 var client = new MetroServiceReference.MetroServiceClient(); string name = textBox1.Text; string res = client.GetDate(name); textBox2.Text = res; } private async void button2_Click( object sender, EventArgs e) { // 非同期送信 var client = new MetroServiceReference.MetroServiceClient(); string name = textBox1.Text; string res = await client.GetDateAsync(name); textBox2.Text = res; } } } |
面白いのは、.NET Framework 4.5 で作っているので自動的に、同期メソッドの GetDate と、非同期メソッドの GetDateAsync が用意されるところです。どちらを使ってもいいのですが、ここでは metro のために非同期版も作っておきます。
で、うまく送信ができれば準備完了。
■metro 版で WCF クライアントを作成
Form 版がテストができたので、本番の metro 版を作ります。Form 版と同じように「サービスの参照を追加」してから、適当にボタンを配置します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | namespace SampleWcfMetro { /// <summary> /// An empty page that can be used on its own or navigated to within a Frame. /// </summary> public sealed partial class BlankPage : Page { public BlankPage() { this .InitializeComponent(); } /// <summary> /// Invoked when this page is about to be displayed in a Frame. /// </summary> /// <param name="e">Event data that describes how this page was reached. The Parameter /// property is typically used to configure the page.</param> protected override void OnNavigatedTo(NavigationEventArgs e) { } private async void Button_Click_1( object sender, RoutedEventArgs e) { // 非同期送信 var client = new MetroServiceReference.MetroServiceClient(); string name = textBox1.Text; string res = await client.GetDateAsync(name); textBox2.Text = res; } } } |
前回の HttpClient と違うのは、Desktop 版でテストをしたコードがそのまま使えるということです。これは違いが大きいですよね。metro アプリの場合、シミュレーター等と使うのでいまいちデバッグがしづらいところがあります。そうなると基本的なコードは desktop アプリで確認しておいて、metro アプリに適宜追加してくという方法をとるほうが効率的にデバッグができます。
実行するとこんな感じ。意外とすんなり動きます。これならばプロセス間通信の代用にしてもよいかなと。