IME再変換位置の自動調整 - ImmSetCompositionString APIのSCS_QUERYRECONVERTSTRINGの呼び出し(Marshal版) - C#

IMEの再変換をする際に、テキストが選択されていない場合にはキャレットの位置に対してどこまでを変換範囲にするか決定する必要があります。変換範囲の決定の際には ImmSetCompositionString APIのdwIndexパラメータにSCS_QUERYRECONVERTSTRING を与えて呼び出します。また、lpCompパラメータにはRECONVERTSTRING 構造体を与えます。
RECONVERTSTRING 構造体を与えるlpCompパラメータには構造体の後ろのアドレスにテキスト文を配置する変わった構造になってています。こちらの記事ではunsafeを用いてRECONVERTSTRING 構造体を作成し、ImmSetCompositionString を呼び出しました。今回はunsafeを使わずに(ポインタの操作をせずに)Marshalの機能を使ってImmSetCompositionString APIを呼び出す方法を紹介します。

コード例

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace IMEDriver
{
  public partial class Form1 : Form
  {
    [DllImport("imm32.dll")]
    public static extern int ImmSetCompositionStringW(IntPtr himc, int dwIndex, 
      IntPtr lpComp, int dw, int lpRead, int dw2);
    
    [DllImport("Imm32.dll", SetLastError = true)]
    public static extern IntPtr ImmGetContext(IntPtr hWnd);

    [DllImport("Imm32.dll")]
    public static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);

    //RECONVERTSTRING
    [StructLayout(LayoutKind.Sequential)]
    public struct RECONVERTSTRING
    {
      public uint dwSize;
      public uint dwVersion;
      public uint dwStrLen;
      public uint dwStrOffset;
      public uint dwCompStrLen;
      public uint dwCompStrOffset;
      public uint dwTargetStrLen;
      public uint dwTargetStrOffset;
    }
    
    const int SCS_SETRECONVERTSTRING  = 0x00010000;
    const int SCS_QUERYRECONVERTSTRING= 0x00020000;
    
    public Form1()
    {
      InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {

    }

    private void button5_Click(object sender, EventArgs e)
    {
      string Text="本日は晴天です。プログラミング日和です。";

      uint len = 0;
      RECONVERTSTRING rcs = new RECONVERTSTRING();
      rcs.dwSize = (uint)Marshal.SizeOf(rcs) +(uint)Encoding.Unicode.GetByteCount(Text)+1;
      rcs.dwVersion = 0;
      rcs.dwStrLen = (uint)Text.Length;
      rcs.dwStrOffset = (uint)Marshal.SizeOf(rcs);

      rcs.dwCompStrLen = 0;
      rcs.dwCompStrOffset = len * sizeof(char);

      rcs.dwTargetStrLen = 0;
      rcs.dwTargetStrOffset = len * sizeof(char);

      IntPtr ReconvPtr = Marshal.AllocHGlobal(Marshal.SizeOf(rcs)
        + Encoding.Unicode.GetByteCount(Text)+1);
      Marshal.StructureToPtr(rcs, ReconvPtr, false);

      byte[] data = Encoding.Unicode.GetBytes(Text);
      IntPtr wpos = ReconvPtr + Marshal.SizeOf(rcs);
      Marshal.Copy(data, 0, wpos, data.Length); 

      textBox3.Text += "Before\r\n";
      textBox3.Text += Convert.ToInt16(rcs.dwSize) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcs.dwStrLen) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcs.dwStrOffset) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcs.dwCompStrLen) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcs.dwCompStrOffset) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcs.dwTargetStrLen) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcs.dwTargetStrOffset) + "\r\n";

      IntPtr ImmHandle = ImmGetContext(this.Handle);
      ImmSetCompositionStringW(ImmHandle, SCS_QUERYRECONVERTSTRING, ReconvPtr,
        Marshal.SizeOf(rcs) + Encoding.Unicode.GetByteCount(Text)+1, 0, 0);
      ImmReleaseContext(this.Handle, ImmHandle);

      RECONVERTSTRING rcsr = new RECONVERTSTRING();
      rcsr = (RECONVERTSTRING)Marshal.PtrToStructure(ReconvPtr, rcsr.GetType());

      textBox3.Text += "After\r\n";
      textBox3.Text += Convert.ToInt16(rcsr.dwSize) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcsr.dwStrLen) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcsr.dwStrOffset) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcsr.dwCompStrLen) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcsr.dwCompStrOffset) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcsr.dwTargetStrLen) + "\r\n";
      textBox3.Text += Convert.ToInt16(rcsr.dwTargetStrOffset) + "\r\n";
      int start = (int)(rcsr.dwTargetStrOffset/2);
      string SelectedText= Text.Substring(start, (int)rcsr.dwTargetStrLen);
      textBox3.Text += SelectedText + "\r\n";
    }
  }
}

解説

定義部分

[DllImport("imm32.dll")]
public static extern int ImmSetCompositionStringW(IntPtr himc, int dwIndex, IntPtr lpComp, int dw, int lpRead, int dw2);
[DllImport("Imm32.dll", SetLastError = true)]
public static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("Imm32.dll")]
public static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
は、APIの定義になります。今回はImmGetContext,ImmReleaseContext,ImmSetCompositionStringWの3つを定義しました。
[StructLayout(LayoutKind.Sequential)]
public struct RECONVERTSTRING
{
  public uint dwSize;
  public uint dwVersion;
  public uint dwStrLen;
  public uint dwStrOffset;
  public uint dwCompStrLen;
  public uint dwCompStrOffset;
  public uint dwTargetStrLen;
  public uint dwTargetStrOffset;
}
RECONVERTSTRING構造体の定義になります。メンバが宣言された順番にメモリに配置されるように[StructLayout(LayoutKind.Sequential)]を指定します。

const int SCS_SETRECONVERTSTRING = 0x00010000;
const int SCS_QUERYRECONVERTSTRING= 0x00020000;
SCS_QUERYRECONVERTSTRINGの定数値を定義します。

Button4コード内

RECONVERTSTRING rcs = new RECONVERTSTRING();
RECONVERTSTRING構造体を宣言し、newしてメモリ確保します。

rcs.dwSize = (uint)Marshal.SizeOf(rcs) +(uint)Encoding.Unicode.GetByteCount(Text)+1;
rcs.dwVersion = 0;
rcs.dwStrLen = (uint)Text.Length;
rcs.dwStrOffset = (uint)Marshal.SizeOf(rcs);
rcs.dwCompStrLen = 0;
rcs.dwCompStrOffset = len * sizeof(char);
rcs.dwTargetStrLen = 0;
rcs.dwTargetStrOffset = len * sizeof(char);
RECONVERTSTRING構造体に値を設定します。dwSize にはRECONVERTSTRINGの構造体のサイズ+変換対象文のバイトサイズ+1を設定します。dwVersion には0を、dwStrLen には変換対象文の文字数を設定します。
dwStrOffset には0を、dwCompStrOffset には変換対象文の変換部分のオフセット値をバイト数で与えます。テキストエディタなどの場合にはキャレットの位置からこの値を設定しますが、今回は決め打ちの値(len変数の内容)を入れます。バイト数で指定するためsizeof(char)を掛けています。
dwTargetStrLen は0にdwTargetStrOffset はdwCompStrOffset と同じ値を設定します。

IntPtr ReconvPtr = Marshal.AllocHGlobal(Marshal.SizeOf(rcs) + Encoding.Unicode.GetByteCount(Text)+1);
ImmSetCompositionStringWに与えるRECONVERTSTRING構造体を準備します。まず、Marshal.AllocHGlobalでメモリの割り当てをします。割り当てサイズは、RECONVERTSTRING構造体のサイズ+変換対象文のバイトサイズ+1(ヌルターミネーター分)とします。RECONVERSIONSTRINGのサイズ取得にはMarshal.SizeOf()メソッドを用います。

Marshal.StructureToPtr(rcs, ReconvPtr, false);
最初に用意して値を設定したRECONVERTSTRING構造体 rcsの内容をメモリ確保したReconvPtrにMarshal.StructToPtrを用いてコピーします。

byte[] data = Encoding.Unicode.GetBytes(Text);
変換対象文のバイト列を取得します。UNICODE文字列のバイト列を取得します。

IntPtr wpos = ReconvPtr + Marshal.SizeOf(rcs);
書き込み開始位置のアドレスを算出します。ReconvPtrのアドレスにRECONVERTSTRING構造体のサイズを加算した位置が変換対象文の書き込み開始位置になります。

Marshal.Copy(data, 0, wpos, data.Length);
Marshal.Copyメソッドを用いて変換対象文のバイト配列を指定した書き込み開始位置にコピーします。

ここまでの操作で、ReconvPtr にRECONVERSIONSTRING構造体と同じ情報が書き込めました。

textBox3.Text += "Before\r\n";
textBox3.Text += Convert.ToInt16(rcs.dwSize) + "\r\n";
textBox3.Text += Convert.ToInt16(rcs.dwStrLen) + "\r\n";
textBox3.Text += Convert.ToInt16(rcs.dwStrOffset) + "\r\n";
textBox3.Text += Convert.ToInt16(rcs.dwCompStrLen) + "\r\n";
textBox3.Text += Convert.ToInt16(rcs.dwCompStrOffset) + "\r\n";
textBox3.Text += Convert.ToInt16(rcs.dwTargetStrLen) + "\r\n";
textBox3.Text += Convert.ToInt16(rcs.dwTargetStrOffset) + "\r\n";
ImmSetCompositionString を呼び出す前のRECONVERTSTRING構造体の値をtextBox3に出力しています。(確認のため)

IntPtr ImmHandle = ImmGetContext(this.Handle);
ImmSetCompositionStringW(ImmHandle, SCS_QUERYRECONVERTSTRING, ReconvPtr, Marshal.SizeOf(rcs) + Encoding.Unicode.GetByteCount(Text)+1, 0, 0);
ImmReleaseContext(this.Handle, ImmHandle);
IMEのハンドルを取得し、ImmSetCompositionStringW() APIを呼び出します。

RECONVERTSTRING rcsr = new RECONVERTSTRING();
rcsr = (RECONVERTSTRING)Marshal.PtrToStructure(ReconvPtr, rcsr.GetType());
ImmSetCompositionStringWを呼び出すとReconvPtrのRECONVERSIONSTRING構造体の値が調節されますが、IntPtrの実体を直接操作はできません。このため、まず戻り値用のRECONVERSIONSTRING構造体rcsrを宣言しnewしてメモリ確保します。次に、Marshal.PtrToStructure() メソッドを用い、ReconvPtrの内容をRECONVERSIONSTRING構造体rcsrにコピーします。コピー後、rcsrの内容を参照して、ImmSetCompositionStringWにより調節された値を取得できます。

textBox3.Text += "After\r\n";
textBox3.Text += Convert.ToInt16(rcsr.dwSize) + "\r\n";
textBox3.Text += Convert.ToInt16(rcsr.dwStrLen) + "\r\n";
textBox3.Text += Convert.ToInt16(rcsr.dwStrOffset) + "\r\n";
textBox3.Text += Convert.ToInt16(rcsr.dwCompStrLen) + "\r\n";
textBox3.Text += Convert.ToInt16(rcsr.dwCompStrOffset) + "\r\n";
textBox3.Text += Convert.ToInt16(rcsr.dwTargetStrLen) + "\r\n";
textBox3.Text += Convert.ToInt16(rcsr.dwTargetStrOffset) + "\r\n";
int start = (int)(rcsr.dwTargetStrOffset/2);
string SelectedText= Text.Substring(start, (int)rcsr.dwTargetStrLen);
textBox3.Text += SelectedText + "\r\n";
ImmSetCompositionString 呼び出し後のRECONVERTSTRING構造体の値をtextBox3に出力します。また、再変換の対象となった部分のテキストも合わせてtextBox3に表示します。


実行結果

プログラムを実行後Button5をクリックします。

len=0の場合


len=10の場合


len=6の場合


著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2011-01-06
iPentec all rights reserverd.