C#の最近のブログ記事
以前、Felicaを使ったシステムを企画してみたいなぁとふいに考えた。で、Felicaを使った(Felicaにアクセスする)開発には「SDK for Felica」という製品が必要だ。SDKにはLiteからEnterpriseまで出来ることの違う製品がいくつかある。当時、いいタイミングで「IC CARD WORLD 2005」というのがビッグサイトでやっていたので、これに出かけていってソニーのブースでSDKの値段やら色々聞いてみた。Liteを使えばフリー領域のみではあるが読み書きが出来るというので、さっそく購入しようと代理店2社に電話した。そしたら「個人の方にはお売りできません」との返事。がっかりだ。
※ホントにシェアとりたいんだったらSDKは個人にも開放して、どんどんアプリやシステムを書かせればいいと思うんだけどね。
それから1.5年ほどたって、ふとオープンソースマガジンを手にとると、自前で解析して読み取りを成功させている人がいるじゃないか。恐ろしいことよ。(「SFCardPeeper」のことは以前から知っていたが、技術情報を整理して雑誌に載せた、というのがすごいと思う)さっそくプロジェクトのページからダウンロードしてSuicaを読ませてみる。自分が持っていたR/WはS310だったので見事に読めず。
やむを得ずビックカメラでS320をゲットしてきた。携帯にもFelicaが載るようになったせいか、S320の読み取り部分はフラットになった。なので、自分のように机の上が雑然としていて、水平にR/Wを置けない環境では、載せたカードがすべり落ちてしまう。この辺はS310の方がよかったなぁ、などと思いつつチャレンジ。S320に変えたらあっさり読めた。じゃあ、早速何か書いてみよう、とlibpasoriを眺めたのだが、unixとソース共用を意識しているせいか開発環境がcygwinやMinGWなんだな。それに最近はC#が好きだし、C#からアクセスできるようにしたいなぁ、ということでlibpasori.NETを書いてみた。
注意:要するにラッパーを書いただけなので、Felicaおよびlibpasoriの技術的な情報は何もありません。libpasoriの情報はlibpasori - RC-S320操作コードを参照
方針
- 目的:libpasoriをC#から使いたい。
- まずVisual C++を使ってlibpasoriとインタフェースするためのDLLを作る。
- このDLLをP/InvokeでC#から呼び出す。
- libpasoriには読み書きのための関数がいくつかあるが、率直な話、プロトコルの解析には興味がないし、セキュアなR/W(特にW)はSDKがないと無理そうだ、と考えた。となれば「カードのIDを読み取る」機能だけがあればいいので、以下の関数だけ呼び出せるようにすることにした。
- pasori_open()
- pasori_init()
- felica_polling()
- pasori_close()
- libpasoriはまだこれから変化していくはずなので、出来れば元のソースはいじらないで済ませる。
準備
- LibUsb-Win32
- Windows版のlibpasoriを動かすのに必要。事前にインストールしておく。
- ちなみに僕のところではこれをインストールしたら、Windowsがシャットダウンしなくなった。(「シャットダウンしています」のままになる。)ので、自己責任で。USBsnoopも使ってるのでこれが競合しているのかもしれない。
- 今回使ったのは「libusb-win32-filter-bin-20060518.exe」。リリース版で上記のようにシャットダウンしなくなったのでsnapshot版ならイケるかな、と思ったらそうでもなかった。
- 上記のファイルをインストールすると、ソースとヘッダファイルもインストールされる。うれしいことにVC用のインポートライブラリも同梱されているのでありがたく利用する。
- 上記のヘッダをインクルードできるよう、上記ライブラリをリンクできるようにVisualStudioのディレクトリ設定を追加する。
- libpasori
- 上記ページからソースをダウンロード。今回使ったのはlibpasori02.tar.bz2
libpasori.NET
- cc.breeze.libpasori.Pasori
- libpasoriのpasori型に相当します。
- cc.breeze.libpasori.Felica
- libpasoriのfelica型に相当
- cc.breeze.libpasori.PasoriNotifier
- 別スレッドを用いて、一定間隔でパソリをポーリングし、完了後にコールバックする。
簡単なサンプル
static void Main(string[] args)
{
Pasori ps = null;
try
{
ps = Pasori.Open();
}
catch (LibPasoriException ex)
{
Console.Error.WriteLine("パソリの初期化でエラー");
return;
}
try
{
Felica fc = ps.Polling(SystemCode.ANY);
Console.Out.WriteLine("IDm:" + fc.IDm_text);
fc.Close();
}
catch (LibPasoriException ex)
{
/*
* 現在のところエラーなのか、何も読めなかったのかは区別がないので
* 例外を無視
*/
}
ps.Close();
}
で、サンプルがこれ。パソリをUSBポートに挿して、Felica(僕のところで試したのはSuicaとauのお財布ケータイ)をかざせば、カード固有のIDを読み取ることができる。libpasori.NET(ソースとサンプルexe:約330KB)
- Visual Studio 2005 on XP Proでコンパイルしたもの。他の環境で動くかどうかわかりません。
- この配布物の使用は自己責任で。何が起こっても作者は責任を負いかねます。
- NOD32アンチウイルス パターン1.1650(20060707)でチェック済み。
- ただのラッパーにライセンスを謳うのも気が引けるけど、ソースの改変、配布は自由です。
- libusb、libpasoriについては、上述の両プロジェクトのページを参照してください。
こういうものです。キャプブラ(Windowsインストーラ形式:約400KB)
- 画面をはみ出る大きなページ、縦長ページを一枚のスクリーンショットにキャプチャ。
- PNG/JPEG/ビットマップで出力
- IEコンポーネントを使っているので、レンダリングはIEと同じ
- 「戻るボタン」も「ツールバー」もない(笑)
- 進む、戻るはそれぞれ「ALT+→」「ALT+←」でしのぐ。
- フリーソフト。無料、無償、無保証。
- .NET Framework 2.0必須。開発環境をVS2005に変えただけ。なぜかセットアップのサイズが大幅に減少(約2MB→約400KB)
- NOD32-パターン1.1551(20060521)でチェック済み
→
サービス→クライアントへの応答をgzipで圧縮し、転送量を小さく。
大雑把には以下の通り。
最も手数の少ない方法は、
大雑把には以下の通り。
- クライアント
- HTTPヘッダにAccept-Encoding: gzipをつけて要求を投げる。
- 応答がContent-Encoding: gzipの場合に、帰ってきた応答を伸張する
- サービス側(必ずしもサービス自身が担当するわけではなく)
- 要求のHTTPヘッダにAccept-Encoding: gzipが含まれる場合、HTTP応答本文をgzipで圧縮して返す
最も手数の少ない方法は、
- クライアント=.NET Framework 2.0(手元の環境をVS2005にしたのでもうこれしか確認できない)
- サービスの応答がクライアントに帰る直前、要はmod_deflateを用いて圧縮する
クライアント編
- Web参照から生成したコードで、サービス呼び出しのスタブにあるEnableDecompressionプロパティにtrueをセットして呼び出すだけ。すばらしいシンプルさ。(06/03/01:と思ったらドキュメントによれば既定値trueらしい。あらら)
- 後は、Accept-Encodingを付けることもHTTP応答本文を伸張することも.NET Frameworkがやってくれる。
サーバ編
- 以下の通り、JkMountでApache→Tomcatに転送しているので、転送範囲の応答はmod_defalteで圧縮しちゃう。今のところウェブサービス以外のリソースはないのでブラウザのことは考えないで圧縮指定。
JkMount /KeitaisoWS/services/KeitaisoKaisekiService* ajp13
<Location "/KeitaisoWS/">
AddOutputFilterByType DEFLATE text/html text/plain text/xml
</Location>
- あとはSIGHUPを送るなどして設定を有効に。
応答を見てみる
見てみると言っても、クライアントコードがメッセージを受け取った時点で伸張されていてシームレスなので、通信内容をパケットキャプチャして確認する。- 応答圧縮を受け入れない場合
≫POST /KeitaisoWS/services/KeitaisoKaisekiService HTTP/1.1
≫User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.42)
≫Content-Type: text/xml; charset=utf-8
≫SOAPAction: ""
≫Host: passing.breeze.cc
≫Content-Length: 557
≫Expect: 100-continue
≫Connection: Keep-Alive
≫
≫HTTP/1.1 100 Continue
≫
≫(要求本文)
≪HTTP/1.1 200 OK
≪Date: Mon, 27 Feb 2006 17:32:20 GMT
≪Server: Apache
≪Vary: Accept-Encoding
≪Connection: close
≪Transfer-Encoding: chunked
≪Content-Type: text/xml;charset=utf-8
≪
≪(以下応答)
- 応答圧縮を受け入れる場合
≫POST /KeitaisoWS/services/KeitaisoKaisekiService HTTP/1.1
≫User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.42)
≫Content-Type: text/xml; charset=utf-8
≫SOAPAction: ""
≫Host: passing.breeze.cc
≫Content-Length: 557
≫Expect: 100-continue
≫Accept-Encoding: gzip
≫
≪HTTP/1.1 100 Continue
≪
≫(要求本文)
≪HTTP/1.1 200 OK
≪Date: Mon, 27 Feb 2006 17:32:30 GMT
≪Server: Apache
≪Vary: Accept-Encoding
≪Content-Encoding: gzip
≪Connection: close
≪Transfer-Encoding: chunked
≪Content-Type: text/xml;charset=utf-8
≪
≪(圧縮された応答)
- 今回の例では、約8000バイトの応答が、約2000バイトになった。
おわりに
次はAxisクライアントで同じことをやってみようと思うが、client-config.wsddでhttpトランスポートを置き換えないとダメなのかな?と考えただけで、面倒くせぇ~という気分になるな。
ここまでで、最終的な公開用エンドポイントでサービスを公開できているはず。
最後は.NETを用いてクライアントを作成する。VS2005でC#を用いた。
準備
- ウィンドウアプリケーションでもコンソールアプリケーションでもよいのでプロジェクト作成
- ソリューションエクスプローラのプロジェクトのコンテキストメニューから「Web参照の追加」
- WSDLのURLを与え、「参照の追加」
- 以上で、スタブコードが生成されるはず
クライアントコード
- フォームに入力した文字列をサービスに渡して表示するだけの単純なもの。
private void button1_Click(object sender, System.EventArgs e) { this.textBox2.Text = ""; cc.breeze.passing.KeitaisoKaisekiService svc = new cc.breeze.passing.KeitaisoKaisekiService(); cc.breeze.passing.KeitaisoToken[] results = svc.analyze( this.textBox1.Text ); for( int i = 0 ; i < results.Length ; i++ ) { this.textBox2.Text += results[i].token + "," + results[i].basicString + "," + results[i].hinshi + "," + results[i].reading + "," + results[i].pronunciation + "," + results[i].surface + "," + results[i].cost + "," + results[i].conjugationalForm + "," + results[i].additionalInfo + "," + "\r\n"; } } - こんな感じ
こういうものです。キャプブラ(Windowsインストーラ形式:約2.0MB)
- 画面をはみ出る大きなページ、縦に長いページを一枚のスクリーンショットにキャプチャ。
- PNG/JPEG/ビットマップで出力
- IEコンポーネントを使っているので、レンダリングはIEと同じ
- 「戻るボタン」も「ツールバー」もない(笑)
- 進む、戻るはそれぞれ「ALT+→」「ALT+←」でしのぐ。
- フリーソフト。無料、無償、無保証。
- .NET Framework 1.1必須。
- NAV2004(パターン2005/09/08)でチェック済み
- デスクトップのサイズをはみ出るぐらい大きなwebページ
- 縦に長いwebページ
見回せばそういう目的のツールが結構あるんだけど、「どうやって実現するのか?」を知りたくて、調べてみた。
まずは「IEコンポーネントをフォームに貼り付け、なんちゃってブラウザを作成。IEコンポーネント内に表示されているwebページをキャプチャする。」
下のやり方で大体目的は達成できるみたい。
- キャプチャする矩形(ピクセル単位)を決める。
- IWebBrowser2::Document → IHTMLDocument2を得る
- IHTMLDocument2.body → IHTMLElement2を得る
- IHTMLDocument2::frames → IHTMLFramesCollection2を得る
- IHTMLDocument2::IHTMLFrameSetElementを得る(ない時もある)
- webページがframeを含む場合は、frameが内包するbodyに対して再帰的に矩形を求める。
- bodyの矩形を求める
- frameの配置(縦横)に応じて加算するなりなんなり。bodyの矩形と比較しつつ最終的なキャプチャ矩形を決める。
- System.Drawing.Bitmapに書き込む
- デスクトップにコンパチなデバイスコンテキストとビットマップを作成する。
- IHTMLDocument2 → IOleObjectを得る
- IOleObject::GetExtent()で、現在のエクステントサイズを保存
- キャプチャ矩形サイズをHIMETRIC単位に変換し、IOleObject::SetExtent()で描画範囲を設定
- IHTMLDocument2 → IViewObjectを得る
- OleDraw()でIViewObject→デバイスコンテキストに描画
- 保存しておいたエクステントサイズで元に戻しておく(フォーム上のIEコンポーネントの描画サイズが変わってしまうから)
- デバイスコンテキストに割りついているビットマップからBitmapオブジェクトを生成
- 後はSystem.Drawing.Bitmapオブジェクトを、クリップボードに貼り付けるなり、任意の画像形式でファイルに落とすなりする。
あまり大きいビットマップを作ろうとするとビットマップの作成がコケる。800×37000ピクセルはアウトだった。何か回避方法があるんだろう。
こんな感じ。
こういう感じで定義して。
[DllImport( "inetcpl.cpl" )]Formからの呼び出しはこんな感じ。
public static extern bool LaunchInternetControlPanel( IntPtr hWnd );
bool ret = LaunchInternetControlPanel( this.Handle );OKを押してもキャンセルを押してもtrueが返ってくる。あくまでダイアログ表示が成功したか否か、を表すみたい。
