台風で外出できないのでアプリのユニバーサル化の作業をしていたのですが、シフト JIS を読み込むところで手が止まりました。
ちなみにストア アプリでのコードはこんな感じ、StreamReader に Encoding クラスを指定するだけです。簡単ですね。 ( エラー処理は全無視してます )
using (var req = new HttpClient()) using (var res = await req.GetAsync("http://hogehoge/fugafuga/")) { if (res.IsSuccessStatusCode) { var sjis = Encoding.GetEncoding("Shift_JIS"); using (var stream = await res.Content.ReadAsStreamAsync()) using (var reader = new StreamReader(stream, sjis)) { while (!reader.EndOfStream) { // 読み込み } } } }
Windows Phone 7 の頃はシフト JIS が読み込めなかったので自前で変換テーブルを用意したりしていたのですが、Phone 8 になってこの辺は...あ、改善してないんですね。
でも まったく進歩していない訳ではなく、Windows Phone が持っている機能を呼び出すことができるようになっています。詳細は @biac さんが解説してくださっていますが、懐かしい Win32API を呼び出すことができるようです。*1
まず、ランタイム コンポーネント用のプロジェクトを追加します。
今回はユニバーサル アプリなので [Visual C++] - [ストアアプリ] - [Windows Phone アプリ] の中の「 Windows ランタイム コンポーネント (Windows Phone) 」を選びます。「 Windows ランタイム コンポーネント (Windows Phone Silverlight) 」というのもあるので間違えないようにしましょう。
プロジェクトが追加できたら上記記事の内容で Class1.h と Class1.cpp を書き換えてやります。 ( ファイル名も変えたりします ) ちなみに名前空間 ( プロジェクト名 ) を SjisEncodingComponent 、クラス名を SjisEncoding としました。
SjisEncoding.h
#pragma once #include <ppltasks.h> #define CP_SJIS 932 namespace SjisEncodingComponent { public ref class SjisEncoding sealed { public: static Platform::String^ MultiByteToWideChar(const Platform::Array<byte>^ buff); }; }
SjisEncoding.cpp
#include "pch.h" #include "SjisEncoding.h" using namespace SjisEncodingComponent; using namespace Platform; Platform::String^ SjisEncoding::MultiByteToWideChar(const Platform::Array<byte>^ buff) { LPCSTR pBuff = (LPCSTR)(buff->Data); if (pBuff == NULL) return ref new Platform::String(); // 空文字を返す const int nSize = ::MultiByteToWideChar(CP_SJIS, 0, pBuff, -1, NULL, 0); BYTE* buffUtf16 = new BYTE[nSize * 2 + 2]; ::MultiByteToWideChar(CP_SJIS, 0, pBuff, -1, (LPWSTR)buffUtf16, nSize); Platform::String^ result = ref new Platform::String((LPWSTR)buffUtf16); delete[] buffUtf16; // *で受けたオブジェクトは従来どおり自前で解放する return result; }
このプロジェクトを Windows Phone プロジェクトの参照設定に追加します。
これで、SjisEncodingComponent.SjisEncoding.MultiByteToWideChar() にシフト JIS のバイト列を渡すことで文字コード変換をしてくれるようになります。
...でも、ちょっとストア アプリのコードを大きく書き直さないといけないですね。よろしくないですね。なので、StreamReader クラスに渡せるように Encoding クラスの派生クラスを作ります。
Encoding の派生クラスに必要なメソッドのうち、シフト JIS のバイト列から文字列への変換に必要な部分だけを実装しました。読み込むだけならこれでいけます。*2
class SjisEncoding : Encoding { public override string WebName { get { return "Shift_JIS"; } } // ShiftJIS バイト列 → Unicode 文字列 public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) { var result = SjisEncodingComponent.SjisEncoding.MultiByteToWideChar(bytes.Skip(byteIndex).Take(byteCount).ToArray()); var idx = 0; foreach (var c in result) { chars[charIndex + idx] = c; idx++; } return result.Length; } // 文字数 public override int GetCharCount(byte[] bytes, int index, int count) { var result = SjisEncodingComponent.SjisEncoding.MultiByteToWideChar(bytes.Skip(index).Take(count).ToArray()); return result.Length; } // 最大文字数 (ざっくりバイト数でいいよね?) public override int GetMaxCharCount(int byteCount) { return byteCount; } // 以下、必要になったら public override int GetByteCount(char[] chars, int index, int count) { throw new NotImplementedException(); } public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) { throw new NotImplementedException(); } public override int GetMaxByteCount(int charCount) { throw new NotImplementedException(); } }
ついでに適切な Encoding クラスを返すヘルパを作りました。
Windows Phone なら上記の SjisEncoding を、それ以外なら標準の Encoding のインスタンスを返すだけです。
public class MyEncoding { public static Encoding GetSjisEncoding() { #if WINDOWS_PHONE_APP return new SjisEncoding(); #else return Encoding.GetEncoding("Shift_JIS"); #endif } }
これで元のコードにほとんど手を入れることなく Windows Phone 対応ができました。
using (var req = new HttpClient()) using (var res = await req.GetAsync("http://hogehoge/fugafuga/")) { if (res.IsSuccessStatusCode) { var sjis = MyEncoding.GetSjisEncoding(); using (var stream = await res.Content.ReadAsStreamAsync()) using (var reader = new StreamReader(stream, sjis)) { while (!reader.EndOfStream) { // 読み込み } } } }