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

[ 最新記事及び返信フォームをトピックトップへ ]

■34090 / inTopicNo.1)  参照dllへの文字列引数をutf8で渡したい。
  
□投稿者/ ukots 一般人(1回)-(2019/01/12(Sat) 11:01:07)
  • アイコン環境/言語:[VB.NET on Windows7] 
    分類:[.NET] 

    vbnetに第三者のdllを参照しようとしているが、渡すべき引数の一つがUTF8の文字列変数(String)という仕様となっています。vbnet上では文字列変数はすべてUNICODEに限定されているため、どうしてもUNICODEの文字列を渡すことになり、正常に作動しません(文字化けなど)。なにか良い解決策があればご意見ください。
マルチポストを報告
違反を報告
引用返信 削除キー/
■34091 / inTopicNo.2)  Re[1]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ Hongliang 大御所(549回)-(2019/01/12(Sat) 11:05:22)
  • アイコン確認ですが、そのDLLというのは、Visual Studioの「参照の追加」で参照するものでしょうか。
    それとも、Declare(あるいはDllImport)で呼び出す関数を定義するものでしょうか。
違反を報告
引用返信 削除キー/
■34092 / inTopicNo.3)  Re[2]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ ukots 一般人(2回)-(2019/01/12(Sat) 13:25:04)
  • アイコンNo34091に返信(Hongliangさんの記事)
    確認ですが、そのDLLというのは、Visual Studioの「参照の追加」で参照するものでしょうか。
    それとも、Declare(あるいはDllImport)で呼び出す関数を定義するものでしょうか。
    返事ーーー後者です。Declare.........(ByVAl abc As Strings, .........) As Stringのように。

違反を報告
引用返信 削除キー/
■34093 / inTopicNo.4)  Re[3]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ Hongliang 大御所(550回)-(2019/01/12(Sat) 17:48:36)
  • アイコン> 返事ーーー後者です。Declare.........(ByVAl abc As Strings, .........) As Stringのように。

    であれば、abc As Stringではなくabc As Byte()と宣言して、バイト配列で渡すのが良さそうです。
    渡すバイト配列は以下のように作成します。

    ' str1と言う変数に渡すべき文字列が入ってるとする。
    Dim bytes As Byte() = Encoding.UTF8.GetBytes(str1 & ChrW(0))

    // 返値がStringなのはメモリリークしそうで怖いなぁ。
違反を報告
引用返信 削除キー/
■34095 / inTopicNo.5)  Re[4]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ ukots 一般人(5回)-(2019/01/13(Sun) 10:29:14)
  • アイコンNo34093に返信(Hongliangさんの記事)
    // 返値がStringなのはメモリリークしそうで怖いなぁ。

    解決策が見えたかな:
    DllImportを使用し
    文字コードをCharSet.Unicodeとし、
    関数の引数をXYZ(ByVAl abc() As Byte, .........) As Stringとし、
    Dim abc() As Byte = System.Text.Encoding.UTF8.GetBytes(目的の文字列 + ChrW(0))として懸案の第三者Dllを動かし、
    返値XYZを一旦dim temp As String =System.Text.Encoding.Unicode.GetBytes(XYZ)とし、
    それを更にDim result As String System.Text.Encoding.UTF8.GetString(temp)としたら関数の返値(utf8)がresult中にUicodeとして文字化けせずに見えました。

    これで問題ないか更に検証してみます。


違反を報告
引用返信 削除キー/
■34097 / inTopicNo.6)  Re[5]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ ukots 一般人(6回)-(2019/01/15(Tue) 15:40:08)
  • アイコンNo34095に返信(ukotsさんの記事)
    > ■No34093に返信(Hongliangさんの記事)
    > // 返値がStringなのはメモリリークしそうで怖いなぁ。
    >
    > これで問題ないか更に検証してみます。

    前記のように、Encoding繰り返し偶然にも文字列が化けずに読めたのですが、文字数に矛盾があり、想定していた返値の文字列以外にゴミのようなものが数或は数十バイト付いてきました。したがって、完璧とは行かない結果となりました。


違反を報告
引用返信 削除キー/
■34099 / inTopicNo.7)  Re[1]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ 魔界の仮面弁士 大御所(1189回)-(2019/01/15(Tue) 16:48:04)
  • アイコンNo34095に返信(ukotsさんの記事)
    > 関数の引数をXYZ(ByVAl abc() As Byte, .........) As Stringとし、

    「第三者のdll」が戻り値として返却する文字列のメモリは
    DLL 側でどのように確保されているのでしょうか。
    また、戻り値の文字列エンコードは何になっているのでしょうか。


    戻り値が ANSI あるいは UTF-16 の場合には、戻り値の As String に
    対して MarshalAs 属性を付与することで受けられる可能性が
    あるのですが、その時、どのような宣言になるべきかは
    DLL 側の実装に依存しますので、DLL 提供元に問い合わせてみてください。

    たとえば DLL 側が SysAllocString している場合は
    UnmanagedType.BStr あるいは UnmanagedType.AnsiBStr を、
    CoTaskMemAlloc している場合は
    UnmanagedType.LPWStr あるいは UnmanagedType.LPStr を、
    MarshalAs することで対処できる可能性があります。

    あるいは戻り値として、As IntPtr が求められるケースもあるでしょう。
    仮に、戻り値のエンコードが UTF-8 な BSTR だとしたら、
    As String では正しく受けられないはずですので、
    As IntPtr で受け取った上で VB 側でデコードし、
    使用後に Marshal.FreeBSTR メソッドで解放するなどの
    追加手順が必要になるかもしれません。(こちらでは検証できませんが)
違反を報告
引用返信 削除キー/
■34101 / inTopicNo.8)  Re[2]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ ukots 一般人(7回)-(2019/01/16(Wed) 11:25:02)
  • アイコンNo34099に返信(魔界の仮面弁士さんの記事)

    魔界の仮面弁士さん、返信有り難うございます。marshal関係のことは私には難し過ぎてgive-upです。今回の問題のdllの仕様は下記のpdfに載っています(NLPIR_ParagraphProcess)。

    https://github.com/NLPIR-team/NLPIR/blob/master/NLPIR%20SDK/NLPIR-ICTCLAS/doc/NLPIR-ICTCLAS%E5%88%86%E8%AF%8D%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C2017%E7%89%88%20.pdf

    Declare Function NLPIR_ParagraphProcess Lib "NLPIR.dll" (ByVal sParagraph As String, ByVal bPOStagged As Integer) As Stringに関しては返値文字列の必要な部分のみを取り出す処理を追加し、一応使用できる状態になりましたので、自分的にはこれで良しと考えています。

    別件で、この関数のシリーズ中に、NLPIR_FileProcessがあります。これも
    Declare Function NLPIR_FileProcess Lib "NLPIR.dll" (ByVal sSourceFileName As String, ByVal sResultFileName As String, ByVal bPOStagged As Integer) As Doubleという記述でVBNETで使用しようとしています。ただ、ファイルパスはUTF8文字列であり、漢字交じりのパス名を渡すとエラーになります。よって、半角英数のファイルパスの元で動かすようにして使用できるようにしました。

    皆さんいろいろお世話いただき有り難うございます。そろそろこの件をcloseしようかと思います。

違反を報告
引用返信 削除キー/
■34102 / inTopicNo.9)  Re[3]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ 魔界の仮面弁士 大御所(1190回)-(2019/01/16(Wed) 17:05:20)
  • アイコン2019/01/16(Wed) 17:08:10 編集(投稿者)

    No34101に返信(ukotsさんの記事)
    > 今回の問題のdllの仕様は下記のpdfに載っています

    簡体は読み解けないので、コード部と英語部分だけを
    流し読みしてみただけですが…エンコーディングについては
    最初の NLPIR_Init API で指定するのでしょうか。

    簡体字、繁体字、UTF-8 の 3 種があるようですが(既定では簡体字)、
    残念ながら .NET Framework では Marshal.PtrToStringXXX メソッドの
    UTF-8 版が使えないという罠。
    https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.marshal.ptrtostringutf8


    となるとやはり、System.Text.Encoding を使って
    自前で処理する必要があると思います。


    > NLPIR_ParagraphProcess

    この API ですね。

    const char * NLPIR_ParagraphProcess(const char *sParagraph, int bPOStagged = 1);

    だとすれば、こんな手順でどうでしょう。


    (1) NLPIR_ParagraphProcess API の戻り値を As IntPtr で宣言しなおす

    (2) 戻り値のバッファ長を得るため、kernel32.dll の lstrlenA API を呼び出す
     Private Declare Ansi Function lstrlenA Lib "kernel32" (ByVal p As IntPtr) As Integer

    (3) 受信バッファとして、その長さの Byte 型 1 次元配列を確保する

    (4) 確保したバッファに、Marshal.Copy( IntPtr, Byte(), Integer, Integer) メソッドで複写

    (5) System.Text.Encoding.UTF8.GetString メソッドで文字列に変換する



    なお、受信した文字列バッファの解放処理をどうすべきかは、
    読み解けませんでした。

    NLPIR_Exit を呼び出すとワーキングバッファも解放されるようですが、
    戻り値で返される文字列のメモリ解放もこれですかね…?
違反を報告
引用返信 削除キー/
■34103 / inTopicNo.10)  Re[4]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ ukots 一般人(8回)-(2019/01/17(Thu) 10:16:14)
  • アイコンNo34102に返信(魔界の仮面弁士さんの記事)

    NLPIR_ParagraphProcessの件、ご指示通り行いました。うまく行きそうです。
    UTF8のバッファーをUnicodeにEncodingして、末尾の0hを消去して実用に供する予定です。

    なお、「戻り値で返される文字列のメモリ解放」については特段手立てを設けずともいまのところエラーが出ません。

    魔界の仮面弁士さん、天才だね。有り難うございました。

    PS: NLPIR_FileProcess(FileName,FileName,1) As Doubleについては、NLPIR_FileProcess自体がUTF8でWindows側のファイルにアクセスしようとすることは、そのままではどうにも対処できるものではないのであきらめですね。
    ではこれにて解決済みとします。


違反を報告
引用返信 削除キー/
■34104 / inTopicNo.11)  Re[5]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ 魔界の仮面弁士 大御所(1191回)-(2019/01/17(Thu) 12:42:37)
  • アイコンNo34103に返信(ukotsさんの記事)
    > UTF8のバッファーをUnicodeにEncodingして、末尾の0hを消去して実用に供する予定です。

    あれ? lstrlenA は「0h の直前までのバイト数」を返すので、
    取得したバッファー内に 0h が含まれることは無いはずなのですが…。

    たとえば UTF-8 文字列のバイナリ表現が
     { &HE5, &HBC, &H81, &HE5, &HA3, &HAB,  &H0  , &HCE, &HB2 }
    だったとしたら、戻り値は 7 では なく 6 ですし、
     { &H0  , &HE5, &HBC, &H81, &HE5, &HA3, &HAB, HCE, &HB2 }
    だった場合、戻り値は 1 ではなく 0 となります。


    もしかして、
    > (3) 受信バッファとして、その長さの Byte 型 1 次元配列を確保する
    > (4) 確保したバッファに、Marshal.Copy( IntPtr, Byte(), Integer, Integer) メソッドで複写
    の手順で、サイズ指定をミスっていたりはしませんか?


    > PS: NLPIR_FileProcess(FileName,FileName,1) As Doubleについては、

    この API ですよね。第 3 引数は Optional ByVal bPOStagged As Integer = 1 かな。

    Double NLPIR_FileProcess(
     const char *sSourceFilename,
     const char *sResultFilename,
     int bPOStagged = 1);


    この場合の入力引数も、先の戻り値と同様、「Encoding.UTF8 を使って自前で変換する」だけです。

    現状の引数宣言が、
     <MarshalAs(UnmanagedType.LPStr)> ByVal sSourceFilename As String
    になっているのだとしたら、それを
     ByVal sSourceFilename As Byte()
    もしくは
     ByVal sSourceFilename As IntPtr
    にしてみてください。


    As Byte() で渡す場合は、 No34093 で Hongliang さんが書かれたコードそのままです。
     Dim bytes As Byte() = Encoding.UTF8.GetBytes("Source.txt" & vbNullChar)


    As IntPtr で渡す場合は、
     Dim ptrSrc As IntPtr = Marshal.StringToHGlobalUTF8( "Source.txt" & vbNullChar )
     Dim ptrDst As IntPtr = Marshal.StringToHGlobalUTF8( "Result.txt" & vbNullChar )
     Dim ret As Double = NLPIR_FileProcess( ptrSrc, ptrDst, 1 )
     Marshal.FreeHGlobal( ptrDst )
     Marshal.FreeHGlobal( ptrSrc )
    としたいところですが……残念ながら StringToHGlobalUTF8 なんていうメソッドは無いので、こんな感じで。
     Dim bytes As Byte() = Encoding.UTF8.GetBytes("Source.txt" & vbNullChar)
     Dim ptrSrc As IntPtr = Marshal.AllocHGlobal( bytes.Length )
     Marshal.Copy(bytes, 0, ptrSrc, bytes.Length)
     Dim ptrDst As IntPtr
     '中略:ptrDst も ptrSrc と同様の手順で、AllocHGlobal + Copy で UTF-8 バッファを作成
     Dim ret As Double = NLPIR_FileProcess( ptrSrc, ptrDst, 1 )
     Marshal.FreeHGlobal( ptrDst )
     Marshal.FreeHGlobal( ptrSrc )
違反を報告
引用返信 削除キー/
■34105 / inTopicNo.12)  Re[6]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ ukots 一般人(9回)-(2019/01/17(Thu) 17:03:14)
  • アイコンNo34104に返信(魔界の仮面弁士さんの記事)

    Dim intptr As Integer = NLPIR_ParagraphProcess(ybyt, 1)
    Dim plen As Integer = lstrlenA(intptr)
    Dim bf(plen - 1) As Byte
    Marshal.Copy(intptr, bf, 0, bf.Length)
    Dim pstr As String = System.Text.Encoding.UTF8.GetString(bf)
    これで0hは付加されませんでした。

    Double NLPIR_FileProcess(
     const char *sSourceFilename,
     const char *sResultFilename,
     int bPOStagged = 1);については、

    Dim srcStr As String = "d:\data\漢字\temp.txt"
    Dim dstStr As String = "d:\data\漢字\result.txt"

    Dim bytSrc As Byte() = Encoding.UTF8.GetBytes(srcStr & vbNullChar)
    Dim ptrSrc As IntPtr = Marshal.AllocHGlobal(bytSrc.Length)
    Marshal.Copy(bytSrc, 0, ptrSrc, bytSrc.Length)

    Dim bytDst As Byte() = Encoding.UTF8.GetBytes(dstStr & vbNullChar)
    Dim ptrDst As IntPtr = Marshal.AllocHGlobal(bytDst.Length)
    Marshal.Copy(bytDst, 0, ptrDst, bytDst.Length)

    Dim ret As Double = NLPIR_FileProcess(ptrSrc, ptrDst, 1)
    では機能しません。

    上記でディレクトリの漢字を削除したPathでは問題なくresult.txt作成され、内容も期待したものが入っていました。つまり、
    Dim srcStr As String = "d:\data\temp.txt"
    Dim dstStr As String = "d:\data\result.txt"

    NLPIR_FileProcessにutf8のファイルパスを渡せても、NLPIR_FileProcessからutf8のままでWindowsのファイルシステムにアクセスしてもファイルを探せないのだろうと素人的に推測しています。

    以上。


違反を報告
引用返信 削除キー/
■34106 / inTopicNo.13)  Re[7]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ 魔界の仮面弁士 大御所(1192回)-(2019/01/17(Thu) 19:41:47)
  • アイコン2019/01/17(Thu) 20:51:04 編集(投稿者)

    No34105に返信(ukotsさんの記事)
    > Dim bf(plen - 1) As Byte

    そうですね。それで OK だと思います。

    丸括弧内は「配列の要素数」ではなく「最大インデックス番号」ですので
    「バッファの長さ - 1」を指定することになります。

    たとえば 4 バイトのバッファなら、
    Dim bf(0 To 3) As Byte な範囲になる換算ですね。


    > Dim srcStr As String = "d:\data\temp.txt"
    > Dim dstStr As String = "d:\data\result.txt"

    結果報告ありがとうございます。

    ASCII なパスを UTF-8 変換したバイナリとして渡すのは OK だったということは
    それは DLL 側の不具合(もしくは制限)である可能性が考えられます。


    おそらくは、ファイル アクセスに用いられている Windows API が
    Wide バージョン(たとえば CreateFileW 関数など)ではなく、
    Ansi バージョン(たとえば CreateFileA 関数など)になっているのでしょう。


    すなわちこれは、ファイルパスに対して
     日本語版 Windows で実行した場合は CP932 (Shift_JIS 相当) の文字列
     繁体字版 Windows で実行した場合は CP950 (Big5 相当) の文字列
     簡体字版 Windows で実行した場合は CP936 (GBK/GB2312 相当) の文字列
    しか渡せないということを意味します。


    つまり、パス引数に対して使うのは Encoding.UTF8 や Encoding.Ascii や Encoding.Unicode ではなく、
     Encoding.Default.GetBytes(srcStr & vbNullChar)
     Encoding.GetEncoding(932).GetBytes(srcStr & vbNullChar)
     Encoding.GetEncoding(936).GetBytes(srcStr & vbNullChar)
     Encoding.GetEncoding(950).GetBytes(srcStr & vbNullChar)
    のいずれかであったものと推察します。
    ※ただし strSrc の中身が OS 既定のコードページで扱える文字のみであることが条件


    そしてもし、Encoding.Default で呼び出せる文字列である場合、
    ANSI バージョンであることを明示するために、API の引数宣言を

    Declare Ansi Function NLPIR_FileProcess Lib "NLPIR" ( _
     <MarshalAs(UnmanagedType.LPStr)> ByVal sSourceFilename As String, _
     <MarshalAs(UnmanagedType.LPStr)> ByVal sResultFileName As String, _
     Optional ByVal bPOStagged As Integer = 1) As Double

    もしくは

    <DllImport("NLPIR.DLL", CharSet:=CharSet.Ansi)> _
    Function NLPIR_FileProcess(
     <MarshalAs(UnmanagedType.LPStr)> ByVal sSourceFilename As String, _
     <MarshalAs(UnmanagedType.LPStr)> ByVal sResultFileName As String, _
     Optional ByVal bPOStagged As Integer = 1) As Double
    End Function

    の形式に修正することでも、ファイルパスを渡すことが出来るのでは無いでしょうか。


    > Dim srcStr As String = "d:\data\漢字\temp.txt"

    上記のように、API 宣言で Ansi 版であることを明示した場合、
    As String な引数に "漢字" という文字列が渡されたときには
      日本語 Windows では 8A, BF, 8E, 9A
      繁体字 Windows では BA, 7E, A6, 72
      簡体字 Windows では 9D, 68, D7, D6
    というバイナリに自動変換されてから DLL に渡される見込みです。
違反を報告
引用返信 削除キー/
■34107 / inTopicNo.14)  Re[8]: 参照dllへの文字列引数をutf8で渡したい。
□投稿者/ ukots 一般人(10回)-(2019/01/18(Fri) 09:21:45)
  • アイコンNo34106に返信(魔界の仮面弁士さんの記事)

    皆様、おかげさまで懸案のdllを使えるようになりました。種々のアドバイス、大変勉強になりました。また、別件あるいは今回の延長線で何かあれば寄稿させていただきます。
    御礼と記事を閉じることの報告まで。

解決み!
違反を報告
引用返信 削除キー/



トピック内ページ移動 / << 0 >>

このトピックに書きこむ

Mode/  Pass/


- Child Tree -