DOBON.NET DOBON.NETプログラミング掲示板過去ログ

IMEの読みガナを取得したい

分類:[.NET]

初めて投稿させていただきます。
VB6で下記の方法でIMEの読みガナを取得していました。
VB.NET上でも同じ処理をしたいので、ご存知の方おられましたら教えて下さい。
IMEの読みガナを取得できさえすれば、どんな方法でも歓迎です。
'-------------------------------------------------------------------------
'テキストボックスのChangedイベントにて
Sub txtBox_Change()
Dim strRet As String
strRet = IMEの読みガナを返す(txtBox)'txtBoxはテキストボックス
...
...
...
End Sub
'関数の宣言
Option Explicit
' ウィンドウに関連付けされた入力コンテキストを
' 取得する関数の宣言
Declare Function ImmGetContext Lib "imm32.dll" _
(ByVal hWnd As Long) As Long
' IMEの変換文字列を取得する定数の宣言
Public Const GCL_CONVERSION = &H1
Public Const GCL_REVERSECONVERSION = &H2
Public Const GCS_RESULTREADSTR = &H200
Public Const BEGIN_OFFSET = 6
' 変換文字列に関する情報を取得する関数の宣言
Declare Function ImmGetCompositionString _
Lib "imm32.dll" _
Alias "ImmGetCompositionStringA" _
(ByVal hIMC As Long, _
ByVal dwIndex As Long, _
lpBuf As Any, _
ByVal dwBufLen As Long) As Long
' ウィンドウに関連付けされた入力コンテキストを
' 解放する関数の宣言
Declare Function ImmReleaseContext Lib "imm32.dll" _
(ByVal hWnd As Long, _
ByVal hIMC As Long) As Long
'呼び出される関数
Function IMEの読みガナを返す(txtBox As TextBox) As String
Dim lngImeContext As Long
Dim strBuffer As String * 256
Dim strFurigana As String
Dim lngResult As Long
' 入力コンテキストを取得
lngImeContext = ImmGetContext(txtBox.hWnd)
' 変換文字列に関する情報を取得
lngResult = _
ImmGetCompositionString( _
lngImeContext, _
GCS_RESULTREADSTR, _
ByVal strBuffer, _
Len(strBuffer))
' 入力コンテキストを解放
lngResult = _
ImmReleaseContext( _
Text.hWnd, _
lngImeContext)
' 取得した文字列からNull文字を削除
strFurigana = Replace(strBuffer, vbNullChar, "")
' 取得した文字列からスペースを削除
strFurigana = Replace(strFurigana, " ", "")
' 結果を返す
IMEの読みガナを返す = strFurigana
End Function
hWnd/hIMCなどのhで始まる引数でHANDLE型のものは.NETではIntPtr型が対応します。
dwIndexなどのdwで始まる引数はDWORD型のものは、
.NETではInteger型(正確にはUInt32ですが、たいていの場合はIntegerで問題ありません)が対応します。

また、ImmGetCompositionStringAの「lpBuf As Any, 」の部分は、
LPVOID型で関数からの結果の文字列を受け取るためのバッファですので、
StringBuilderクラスで型を指定してやればOKです。

あと、旧VBでのウィンドウハンドルのプロパティはhWndですが、
.NETではHandleプロパティになっています。

下記はC#での例ですが参考になるでしょう。
http://www.egroups.co.jp/message/dotnet-csharp/144
■No456に返信(よねKENさんの記事)
よねKENさんの早速のご指摘を元にコーディングをしたところ
、殆ど正常に動作しました。ありがとうございます。
しかし残念ながら、不具合が見つかってしまいました。
様々な入力をして試したところ、
下記の様な場合に発生するらしいことがわかりました。

濁音・破裂音・吃音を含む読み仮名を持つ文字列を入力すると、
その時あるいはそれ以降、返される文字列の右側が文字化け
することがある(時々)。
同じ文字列を入力しても、その時々で現象は違ったりもします。
私の未熟さからか、あるいはIMEがらみのOS依存部分が原因なのか
、取得したコンテキストの解放ができていないのかもしれません。
ソースコードの該当部分を下記に示します。
お気づきの点があったらご指摘ください。幸いです。
'◇TextBox側の処理-----------------------------------------------------
Private Sub TextBox1_TextChanged _
(ByVal sender As System.Object _
, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
Dim strRet As String
strRet = modIME.IMEの読みガナを返す(TextBox1)
...
...
...
End Sub
'◇標準モジュール側の処理----------------------------------------------
Module modIME
'------------------dllの宣言------------------
' ウィンドウに関連付けされた入力コンテキストを
' 取得する関数の宣言
Declare Function ImmGetContext Lib "imm32.dll" _
(ByVal Handle As IntPtr) As IntPtr
'-----IMEの変換文字列を取得する定数の宣言-----
Public Const GCL_CONVERSION = &H1
Public Const GCL_REVERSECONVERSION = &H2
Public Const GCS_RESULTREADSTR = &H200
Public Const BEGIN_OFFSET = 6
'--変換文字列に関する情報を取得する関数の宣言--
Declare Function ImmGetCompositionString Lib "imm32.dll" _
Alias "ImmGetCompositionStringA" _
(ByVal hIMC As IntPtr, ByVal dwIndex As Integer, _
ByVal strBuffer As System.Text.StringBuilder, _
ByVal dwBufLen As Integer) As Integer
' ウィンドウに関連付けされた入力コンテキストを
' 解放する関数の宣言
Declare Function ImmReleaseContext Lib "imm32.dll" _
(ByVal Handle As IntPtr, ByVal hIMC As IntPtr) As Long
'呼び出される関数
Function IMEの読みガナを返す(ByVal txtBox As TextBox) As String
Dim lngImeContext As IntPtr
Dim strBuffer As New System.Text.StringBuilder(256)
Dim strFurigana As String
Dim intResult As Integer

' 入力コンテキストを取得
lngImeContext = ImmGetContext(txtBox.Handle)
Try
'変換文字列に関する情報を取得
If 0 = txtBox.MaxLength Then 'TextBoxのMaxLength指定があるか?
'なければ、dwBufLenは64にする(この関数内でのデフォルト値)
intResult = ImmGetCompositionString(lngImeContext _
, GCS_RESULTREADSTR, strBuffer, 64)
Else 'あれば、それをdwBufLenに指定
intResult = ImmGetCompositionString(lngImeContext _
, GCS_RESULTREADSTR, strBuffer, txtBox.MaxLength)
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
' 入力コンテキストを解放
ImmReleaseContext(txtBox.Handle, lngImeContext)
If 0 <> intResult Then '読み仮名のバイト数が得られた
' 取得した文字列からNull文字を削除
strFurigana = Replace(strBuffer.ToString, vbNullChar, "")
' 取得した文字列からスペースを削除
strFurigana = Replace(strFurigana, " ", "")
' 左詰めにして結果を返す
Return strFurigana.PadLeft(intResult)
Else
Return ""
End If
End Function

End Module
’-----------------------------------------------
#問題の解決につながるかどうかは別にして、気づいた点を

> ' ウィンドウに関連付けされた入力コンテキストを
> ' 解放する関数の宣言
> Declare Function ImmReleaseContext Lib "imm32.dll" _
> (ByVal Handle As IntPtr, ByVal hIMC As IntPtr) As Long

戻り値はAs Long→As Integerとして下さい。これだと不正なメモリを参照してしまい、
まずいことになるかもしれません。

> '呼び出される関数
> Function IMEの読みガナを返す(ByVal txtBox As TextBox) As String
> Dim lngImeContext As IntPtr
> Dim strBuffer As New System.Text.StringBuilder(256)

ここでは宣言だけをしておいて、
Dim strBuffer As System.Text.StringBuilder

> Dim strFurigana As String
> Dim intResult As Integer
>
> ' 入力コンテキストを取得
> lngImeContext = ImmGetContext(txtBox.Handle)
> Try
> '変換文字列に関する情報を取得
> If 0 = txtBox.MaxLength Then 'TextBoxのMaxLength指定があるか?
> 'なければ、dwBufLenは64にする(この関数内でのデフォルト値)
> intResult = ImmGetCompositionString(lngImeContext _
> , GCS_RESULTREADSTR, strBuffer, 64)
> Else 'あれば、それをdwBufLenに指定
> intResult = ImmGetCompositionString(lngImeContext _
> , GCS_RESULTREADSTR, strBuffer, txtBox.MaxLength)
> End If

If 0 = txtBox.MaxLength Then 'TextBoxのMaxLength指定があるか?
'なければ、dwBufLenは64にする(この関数内でのデフォルト値)
strBuffer = New System.Text.StringBuilder(64)
Else
strBuffer = New System.Text.StringBuilder(txtBox.MaxLength)
End If

intResult = ImmGetCompositionString(lngImeContext _
, GCS_RESULTREADSTR, strBuffer, strBuffer.Capacity)

とするとよりすっきり安全に書けると思います。
#元のコードだとtxtBox.MaxLengthが256以上の場合にまずいです。
#そんなMaxLength設定はまずないでしょうけど。

> If 0 <> intResult Then '読み仮名のバイト数が得られた
> ' 取得した文字列からNull文字を削除
> strFurigana = Replace(strBuffer.ToString, vbNullChar, "")
> ' 取得した文字列からスペースを削除
> strFurigana = Replace(strFurigana, " ", "")
> ' 左詰めにして結果を返す
> Return strFurigana.PadLeft(intResult)

このIf 0 <> intResult Then 内のロジックですが、

Return strBuffer.ToString()

だけでいいはずです。
■No465に返信(よねKENさんの記事)
#よねKENさん、再度のご指摘ありがとうございます。
#ご指摘のとおり修正し
#呼び出される関数のソースは下記の通りとなりました。
'=========================================================================
Module modIME
'-------------------------dllの宣言-----------------------
'------ウィンドウに関連付けされた入力コンテキストを-------
' 取得する関数の宣言
Declare Function ImmGetContext Lib "imm32.dll" _
(ByVal Handle As IntPtr) As IntPtr
'-----------IMEの変換文字列を取得する定数の宣言-----------
Public Const GCL_CONVERSION = &H1
Public Const GCL_REVERSECONVERSION = &H2
Public Const GCS_RESULTREADSTR = &H200
Public Const BEGIN_OFFSET = 6
'-------変換文字列に関する情報を取得する関数の宣言--------
Declare Function ImmGetCompositionString Lib "imm32.dll" _
Alias "ImmGetCompositionStringA" _
(ByVal hIMC As IntPtr, ByVal dwIndex As Integer, _
ByVal strBuffer As System.Text.StringBuilder, _
ByVal dwBufLen As Integer) As Integer
'------ウィンドウに関連付けされた入力コンテキストを-------
' 解放する関数の宣言
Declare Function ImmReleaseContext Lib "imm32.dll" _
(ByVal Handle As IntPtr, ByVal hIMC As IntPtr) As Integer
'---------------------------------------------------------
'◇◇◇◇◇◇◇◇TextBoxでのIMEの読みガナを返す◇◇◇◇◇◇◇◇
Function IMEの読みガナを返す(ByVal txtBox As TextBox) As String
'txtBox:IMEによる変換が行われたTextBox
Dim lngImeContext As IntPtr '入力コンテキストの取得用
Dim strBuffer As New System.Text.StringBuilder '読み仮名格納用
Dim intResult As Integer '

' 入力コンテキストを取得
lngImeContext = ImmGetContext(txtBox.Handle)
'変換文字列に関する情報を取得
Try
If 0 = txtBox.MaxLength Then 'TextBoxのMaxLength指定があるか?
'なければ、dwBufLenは64にする(この関数内でのデフォルト値)
strBuffer = New System.Text.StringBuilder(64)
Else 'あれば、それをdwBufLenに指定
strBuffer = New System.Text.StringBuilder(txtBox.MaxLength)
End If
intResult = ImmGetCompositionString(lngImeContext _
, GCS_RESULTREADSTR, strBuffer, strBuffer.Capacity)
Catch ex As Exception
MsgBox(ex.ToString)
End Try
' 入力コンテキストを解放
ImmReleaseContext(txtBox.Handle, lngImeContext)
If 0 <> intResult Then '読み仮名のバイト数が得られたか?
' 結果を返す
Return strBuffer.ToString
Else
' 長さ0の文字列を返す
Return ""
End If
End Function

End Module
'=========================================================================
#残念ながら、問題は解決しませんでした。
#ソースコードが原因ではなさそうですね。
私の方でも提示のコードで実際に試してみました。
Win2000、VS.NET2003(.NET Framework1.1)

> 濁音・破裂音・吃音を含む読み仮名を持つ文字列を入力すると、
> その時あるいはそれ以降、返される文字列の右側が文字化け
> することがある(時々)。
> 同じ文字列を入力しても、その時々で現象は違ったりもします。

私の方でも似たような現象が起きるのを確認できました。
で、一応別の方法を取ってみたら、うまく動作している感じになったのですが、
「同じ文字列を入力しても、その時々で現象が違う」ので、
本当に問題ないのかどうかはちょっと怪しさが残りますが。

下記に変更したコードを掲載します。
変更点は、
・読みガナはByte配列で受ける
・Shift_JISのEncodingオブジェクトを取得し、Byte配列からStringに変換する。
その際、読みガナのバイト数にImmGetCompositionStringの戻り値
(読みガナのバイト数)を使うことで不要な部分をカットする。

'-------------------------dllの宣言-----------------------
'------ウィンドウに関連付けされた入力コンテキストを-------
' 取得する関数の宣言
Declare Function ImmGetContext Lib "imm32.dll" _
(ByVal Handle As IntPtr) As IntPtr
'-----------IMEの変換文字列を取得する定数の宣言-----------
Public Const GCL_CONVERSION = &H1
Public Const GCL_REVERSECONVERSION = &H2
Public Const GCS_RESULTREADSTR = &H200
Public Const BEGIN_OFFSET = 6
'-------変換文字列に関する情報を取得する関数の宣言--------
Declare Function ImmGetCompositionString Lib "imm32.dll" _
Alias "ImmGetCompositionStringA" _
(ByVal hIMC As IntPtr, ByVal dwIndex As Integer, _
ByVal strBuffer As Byte(), _
ByVal dwBufLen As Integer) As Integer
'------ウィンドウに関連付けされた入力コンテキストを-------
' 解放する関数の宣言
Declare Function ImmReleaseContext Lib "imm32.dll" _
(ByVal Handle As IntPtr, ByVal hIMC As IntPtr) As Integer
'---------------------------------------------------------
'◇◇◇◇◇◇◇◇TextBoxでのIMEの読みガナを返す◇◇◇◇◇◇◇◇
Function GetImePhonetic(ByVal txtBox As TextBox) As String
'txtBox:IMEによる変換が行われたTextBox
Dim lngImeContext As IntPtr '入力コンテキストの取得用
'Dim strBuffer As New System.Text.StringBuilder '読み仮名格納用
Dim strBuffer As Byte()
Dim intResult As Integer '

' 入力コンテキストを取得
lngImeContext = ImmGetContext(txtBox.Handle)
'変換文字列に関する情報を取得
Try
If 0 = txtBox.MaxLength Then 'TextBoxのMaxLength指定があるか?
'なければ、dwBufLenは64にする(この関数内でのデフォルト値)
'strBuffer = New System.Text.StringBuilder(64)
strBuffer = New Byte(64) {}
Else 'あれば、それをdwBufLenに指定
'strBuffer = New System.Text.StringBuilder(txtBox.MaxLength)
strBuffer = New Byte(txtBox.MaxLength) {}
End If
intResult = ImmGetCompositionString(lngImeContext _
, GCS_RESULTREADSTR, strBuffer, strBuffer.Length)
Catch ex As Exception
MsgBox(ex.ToString)
End Try
' 入力コンテキストを解放
ImmReleaseContext(txtBox.Handle, lngImeContext)
If 0 <> intResult Then '読み仮名のバイト数が得られたか?
' 結果を返す
Return System.Text.Encoding.GetEncoding("Shift_JIS").GetString(strBuffer, 0, intResult)
Else
' 長さ0の文字列を返す
Return ""
End If
End Function
よねKENさん、毎度の返信ありがとうございます。

> 変更点は、
> ・読みガナはByte配列で受ける
> ・Shift_JISのEncodingオブジェクトを取得し、Byte配列からStringに変換する。
> その際、読みガナのバイト数にImmGetCompositionStringの戻り値
> (読みガナのバイト数)を使うことで不要な部分をカットする。
>

ご提示のソースコードをそのままコピーして、少し試したところ
正常に動きました。これで問題ないと思いますので、
解決済み!とさせて頂きます。
以上
■No533に返信(h.hayashiさんの記事)
> よねKENさん、毎度の返信ありがとうございます。
>
>>変更点は、
>>・読みガナはByte配列で受ける
>>・Shift_JISのEncodingオブジェクトを取得し、Byte配列からStringに変換する。
>> その際、読みガナのバイト数にImmGetCompositionStringの戻り値
>> (読みガナのバイト数)を使うことで不要な部分をカットする。
>>
>
> ご提示のソースコードをそのままコピーして、少し試したところ
> 正常に動きました。これで問題ないと思いますので、
> 解決済み!とさせて頂きます。
> 以上
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板