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

概要

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

コード例

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 button4_Click(object sender, EventArgs e)
    {
      unsafe {
        string Text="本日は晴天です。プログラミング日和です。";

        uint len = 0;
        RECONVERTSTRING* reconv = (RECONVERTSTRING*)Marshal.AllocHGlobal(
          sizeof(RECONVERTSTRING) + Encoding.Unicode.GetByteCount(Text)+1);
        
        char* paragraph = (char*)((byte*)reconv + sizeof(RECONVERTSTRING));

        reconv->dwSize
          = (uint)sizeof(RECONVERTSTRING) + (uint)Encoding.Unicode.GetByteCount(Text)+1;
        reconv->dwVersion = 0;
        reconv->dwStrLen = (uint)Text.Length;
        reconv->dwStrOffset = (uint)sizeof(RECONVERTSTRING);

        reconv->dwCompStrLen = 0;
        reconv->dwCompStrOffset = len * sizeof(char);

        reconv->dwTargetStrLen = 0;
        reconv->dwTargetStrOffset = len * sizeof(char);

        for (int i = 0; i < Text.Length; i++) {
          paragraph[i] = Text[i];
        }

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

        IntPtr ImmHandle = ImmGetContext(this.Handle);
         ImmSetCompositionStringW(ImmHandle, SCS_QUERYRECONVERTSTRING, (IntPtr)reconv, 
          sizeof(RECONVERTSTRING)+Encoding.Unicode.GetByteCount(Text)+1, 0, 0);
        ImmReleaseContext(this.Handle,ImmHandle);

        textBox3.Text += "After\r\n";
        textBox3.Text += Convert.ToInt16(reconv->dwSize) + "\r\n";
        textBox3.Text += Convert.ToInt16(reconv->dwStrLen) + "\r\n";
        textBox3.Text += Convert.ToInt16(reconv->dwStrOffset) + "\r\n";
        textBox3.Text += Convert.ToInt16(reconv->dwCompStrLen) + "\r\n";
        textBox3.Text += Convert.ToInt16(reconv->dwCompStrOffset) + "\r\n";
        textBox3.Text += Convert.ToInt16(reconv->dwTargetStrLen) + "\r\n";
        textBox3.Text += Convert.ToInt16(reconv->dwTargetStrOffset) + "\r\n";

        textBox3.Text += "Start:" + Convert.ToInt16(reconv->dwTargetStrOffset / 2) + "\r\n";
        textBox3.Text += "End:" 
          + Convert.ToInt16(reconv->dwTargetStrOffset / 2 + reconv->dwTargetStrLen) + "\r\n";

        int start = (int)(reconv->dwTargetStrOffset / 2);
        string SelectedText= Text.Substring(start, (int)reconv->dwTargetStrLen);
        textBox3.Text += SelectedText+"\r\n";
      }
    }
  }
}

解説

定義部分

次のコードがAPIの定義になります。今回はImmGetContext,ImmReleaseContext,ImmSetCompositionStringWの3つを定義しました。
  [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)]を指定します。
  [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;
  }

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

Button4コード内

ポインタ操作をするため、全体をunsafeで囲みます。また、デフォルトのプロジェクト設定ではunsafeは許可されていないためプロジェクトの設定を変更する必要があります。
ソリューションエクスプローラからプロジェクトのノードを選択し、右クリックのポップアップメニューから[プロパティ]メニューをクリックします。プロジェクトのプロパティが表示されますので左のタブから[ビルド]タブを選択します。ビルドタブ内の[アンセーフ コードの許可]チェックボックスにチェックをつけます。


RECONVERTSTRING構造体を宣言します。ポインタで宣言し、Marshal.AllocHGlobalメソッドを用いてメモリ領域を確保します。確保する領域はRECONVERTSTRING構造体のサイズと変換対象テキストのサイズに1(変換対象テキストのヌルターミネーター分)を加えたサイズになります。
  RECONVERTSTRING* reconv = (RECONVERTSTRING*)Marshal.AllocHGlobal(sizeof(RECONVERTSTRING) + Encoding.Unicode.GetByteCount(Text)+1);

RECONVERT構造体の直後のアドレスをchar型のポインタとして取得します。
  char* paragraph = (char*)((byte*)reconv + sizeof(RECONVERTSTRING));

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

RECONVERSIONSTRING構造体の直後に変換対象文を挿入します。
  for (int i = 0; i < Text.Length; i++) {
   paragraph[i] = Text[i];
  }

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

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

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

実行結果

len=0 での実行結果


len=10 での実行結果


再変換対象部分がきちんと抽出されていることが確認できます。

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2021-08-24
作成日: 2011-01-06
iPentec all rights reserverd.