2012/08/04(Sat)漢字の読みを得るには C#版 - Marshalで遊ぼう

はてブ数 2012/08/04 4:58 プログラミング::C# つーさ

やってることは、かつてHSP3向けに作ったスクリと一緒。
C#ならどれだけ楽かと思って、COM Interopしてみたくなった。
やってみたら実はTypeLibがなくて全然楽じゃなかった。

"冬過ぎて春来るらし朝日さす春日の山に霞たなびく" から、
"ふゆすぎてはるらいるらしあさひさすかすがのやまにかすみたなびく" が得られる。
お、Win8では「かすがのやまに」になってる。が、「はるらいるらし」になってる。
うーん、相変わらずビミョーね。


/* minimum.cs */
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

class JPReverseConv
{
    [ComImport]
    [Guid("019F7152-E6DB-11D0-83C3-00C04FDDB82E")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IFELanguage
    {
        void Open();
        void Close();
        void Dummy5(); /* DO NOT CALL */
        void Dummy6(); /* DO NOT CALL */
        [return: MarshalAs(UnmanagedType.BStr)]
        string GetPhonetic([MarshalAs(UnmanagedType.BStr)] string str, int start, int length);
        void Dummy8(); /* DO NOT CALL */
    }

    [STAThread]
    static void Main(string[] args)
    {
        var fel = Activator.CreateInstance(Type.GetTypeFromProgID("MSIME.Japan")) as IFELanguage;
        fel.Open();
        Console.WriteLine(fel.GetPhonetic("冬過ぎて春来るらし朝日さす春日の山に霞たなびく", 1, -1));
        fel.Close();
    }
}

Windows 8 RP でのみ動作確認。たぶん、Win2000以降はいけるはず。
Marshal大活躍って感じだけど、インターフェースの宣言の仕方にはだいぶ困惑した。
序数で必要なものだけとはいかなくて、メソッド全部宣言しないとだめらしい。
最初HSPみたいにGetPhoneticしか書いてなくて、アクセスバイオレーションで???ってなってた。
Hoge()とか宣言して呼んでも呼べちゃったから、そのとき初めて名前は関係ないんだってことに気づいた。
(それどころかメソッドシグネチャさえも。そりゃそうか。どうやって正当性をチェックするんだ?)

COMのメソッドって基本、戻り値は全部HRESULTで最後が[out]だったりする。
HRESULT要らなかったら、最後のoutナントカという引数の宣言を省略することで、戻り値にMarshalされるらしい。
まぁ、あんまり、自分でinterface宣言することはないはずなのだがね。
http://msdn.microsoft.com/en-us/library/k639e386

AnyCPUでBuildするとx64環境ではIntPtrが64bitなのと、
戻ってくる構造体の中のポインタまでは面倒見てもらえないのとで、
GetJMorphResultについてはうまく動かない。
が、単に読みを得るだけならGetPhoneticでオッケーなので、ことは足りるー。

あと、STAThreadじゃないと動かない。最初 MTAThreadになってて、
as IFELanguageでnull返ってきて原因わからず当惑した。
MTAじゃないと動かないCOMもあるしなぁ、むずかし。


/* そのほか一通り試す.cs */
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

class JPReverseConv
{
    [ComImport]
    [Guid("019F7152-E6DB-11D0-83C3-00C04FDDB82E")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IFELanguage
    {
        void Open();
        void Close();
        IntPtr GetJMorphResult(int dwRequest, int dwCMode, int cwchInput, [MarshalAs(UnmanagedType.LPWStr)] string pwchInput, int pfCInfo);
        int GetConversionModeCaps();
        [return: MarshalAs(UnmanagedType.BStr)]
        string GetPhonetic([MarshalAs(UnmanagedType.BStr)] string str, int start, int length);
        [return: MarshalAs(UnmanagedType.BStr)]
        string GetConversion([MarshalAs(UnmanagedType.BStr)] string str, int start, int length);
    }

    struct MorRslt { public int dwSize; public IntPtr pwchOutput; public short cchOutput; }

    [STAThread]
    static void Main(string[] args)
    {
        // 用意
        string input = "冬過ぎて春来るらし朝日さす春日の山に霞たなびく";
        var ime = Activator.CreateInstance(Type.GetTypeFromProgID("MSIME.Japan")) as IFELanguage;
        ime.Open();

        // GetJMorphResultを試す。
        const int FELANG_REQ_REV = 0x00030000;
        IntPtr pResult = ime.GetJMorphResult(FELANG_REQ_REV, 0, input.Length, input, 0);
        //string revco = Marshal.PtrToStringUni(Marshal.ReadIntPtr(result, 4), Marshal.ReadInt16(result, 8));
        MorRslt result = (MorRslt)Marshal.PtrToStructure(pResult, typeof(MorRslt));
        string revco = Marshal.PtrToStringUni(result.pwchOutput, result.cchOutput);
        Marshal.FreeCoTaskMem(pResult);
        Console.WriteLine(revco);

        // GetPhonetic を使う。これが一番楽い。
        Console.WriteLine(ime.GetPhonetic(input, 1, -1));

        // GetConversion も使ってみる。
        Console.WriteLine(ime.GetConversion("じゅんほうこうのにほんごへんかんもできるということ?", 1, -1));

        // 終わる
        ime.Close();
    }
}

prettyprint使うと行番号つくのはいいけどコピペしにくくてなぁ。
適当なエディタ使ってれば、Alt+Shift+↑↓←→とかで矩形選択して削除なんだけど、めんどくさい。