C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[6]: 変換後のGetPrivateProfileString


(過去ログ 110 を表示中)

[トピック内 8 記事 (1 - 8 表示)]  << 0 >>

■65076 / inTopicNo.1)  変換後のGetPrivateProfileString
  
□投稿者/ MassyPie (7回)-(2013/02/01(Fri) 14:52:01)

分類:[.NET 全般] 

度々、失礼致します。
今回もよろしくお願い致します。

2010のデバッガが使うことができ、そこで不思議な現象を見つけました。
VB6でも確認しましたがそのようにはならないので、.net 2010でもVB6と同じ動作を行うような方法 or 設定を教えて下さい。


iniDataGetという関数を作成しました。
iniDataGetでフルパスのファイル名を渡しています。(g_strIniFileName)
このファイル名は変わることがないので、処理の一番初めで以下のように設定をしています。


1回目のiniDataGetを呼ぶ時に、g_strIniFileNameをウオッチで確認すると、正常なファイル名が入っています。
しかし、iniDataGetに入り、GetPrivateProfileString 実行後に、値が変わります。

前:C:\..(長いので省略)..\aaaaaaaa.ini
後:C:\..(長いので省略)..\aaaaaaaa

サフィックスが消えてしまいます。


そして、心当たりがあるのが Public Declare Function GetPrivateProfileString の部分です。
VB6は、As Anyだったのですが、2010ではエラーとなったため、As Stringに変更しました。

いろいろ調べてみましたが、私の力だけではどうにも解決できそうもありません。

どうぞ、みなさま方のお力をお貸しくださいませ。

よろしくお願い致します。



<呼び元>
g_strIniFileName = My.Application.Info.DirectoryPath & "\" & My.Application.Info.AssemblyName & ".ini" 'フルパスでのファイル名設定

strWork = iniDataGet(g_strIniFileName, "LOG", "LOGKEEPDAY") '1回目



strWork = iniDataGet(g_strIniFileName, "DOC", "MAN") '2回目



<呼び先>
Public Function iniDataGet(ByRef FileName As String, ByRef Section As String, ByRef key As String) As String

Dim characters As Integer
Dim KeyValue As New VB6.FixedLengthString(128)
Dim strX As String
Dim retString As String

On Error Resume Next

characters = GetPrivateProfileString(Section, key, "", KeyValue.Value, 127, FileName) '<−−ここで実行後、変わる
If characters > 0 Then

.
.
.
.
End Function



<定義>
Public g_strIniFileName As String


Public Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, _
ByVal lpKeyName As String, _
ByVal lpDefault As String, _
ByVal lpReturnedString As String, _
ByVal nSize As Integer, _
ByVal lpFileName As String) As Integer
引用返信 編集キー/
■65077 / inTopicNo.2)  Re[1]: 変換後のGetPrivateProfileString
□投稿者/ 魔界の仮面弁士 (148回)-(2013/02/01(Fri) 15:08:00)
No65076 (MassyPie さん) に返信
> そして、心当たりがあるのが Public Declare Function GetPrivateProfileString の部分です。
> VB6は、As Anyだったのですが、2010ではエラーとなったため、As Stringに変更しました。

・GetPrivateProfileStringA ではなく、GetPrivateProfileStringW を呼び出すべきです。

・String 型で受けるべきではありません。StringBuilder を使いましょう。
 もしも String で受けるなら、MarshalAsAttribute の指定が必要です。


同 API の呼び出し方について、以下の資料を参照してみてください。

http://www5b.biglobe.ne.jp/~yone-ken/VBNET/special/sp06_GetPrivateProfileString.html
http://www5b.biglobe.ne.jp/~yone-ken/VBNET/Reference/ref2_GetPrivateProfileString.html


> g_strIniFileName = My.Application.Info.DirectoryPath & "\" & My.Application.Info.AssemblyName & ".ini"
パスの結合には、Path.Combine メソッドを使うようにしましょう。
このメソッドは、VB6 でいえば FileSystemObject の BuildPath メソッドに相当します。


> Public Function iniDataGet(ByRef FileName As String, ByRef Section As String, ByRef key As String) As String
引数で結果を返却するわけではないので、各引数は ByVal であるべきです。

これは VB6 でも同様であり、もしも元のコードに ByVal が付いていなかったのであれば、
そちらにも ByVal を付けておくべきです。
引用返信 編集キー/
■65091 / inTopicNo.3)  Re[2]: 変換後のGetPrivateProfileString
□投稿者/ MassyPie (8回)-(2013/02/04(Mon) 14:12:32)
ご回答ありがとうございます。
ご指摘された項目を全て修正しました。(修正したつもりです。)

GetPrivateProfileString 終了後のcharactersには値が入っているのですが(0ではない)、ウオッチで見ると KeyValue.ToString = "" なので、INIファイルをリードしていません。
何かの定義が、まだ足りないのでしょうか?

今の状態で、GetPrivateProfileStringW を、GetPrivateProfileStringA にしたところ、iniファイルの1つの項目をリードしたことは確認しました。
(WにしないでAのまま、起動していたことに気づき停止したため、最後までINIファイルをリードできたかは確認していません。)


以上、よろしくお願い致します。






<呼び先>

Public Function iniDataGet(ByVal FileName As String, ByVal Section As String, ByVal key As String) As String

Dim characters As Integer
Dim KeyValue As New System.Text.StringBuilder
Dim strX As String
Dim retString As String

On Error Resume Next

characters = GetPrivateProfileString(Section, key, "", KeyValue, 127, FileName) 'ここで変わる


If characters > 0 Then '<---- ここ
strX = KeyValue.ToString '2013/01/30
retString = Left(strX, characters) '2013/01/30

Else
retString = ""
End If
iniDataGet = retString
End Function


<定義>

Public Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringW" (ByVal pApplicationName As String, _
ByVal lpKeyName As String, _
ByVal lpDefault As String, _
ByVal lpReturnedString As System.Text.StringBuilder, _ <----型変更
ByVal nSize As Integer, _
ByVal lpFileName As String) As Integer
引用返信 編集キー/
■65092 / inTopicNo.4)  Re[3]: 変換後のGetPrivateProfileString
□投稿者/ shu (168回)-(2013/02/04(Mon) 17:06:00)
No65091 (MassyPie さん) に返信

Declare宣言のcharsetmodifierを省略するとAnsiになるのでWの場合Unicode指定しないと駄目です。
http://msdn.microsoft.com/ja-jp/library/4zey12w5(v=vs.80).aspx

以下の4つの宣言どれでも戻りは同じになります。
Wの方が良いのでDeclareかDllImportのUnicode指定の宣言を使用されるとよいと思います。


    Public Declare Unicode Function GetPrivateProfileString1 Lib "kernel32" Alias "GetPrivateProfileStringW" (ByVal pApplicationName As String, _
                            ByVal lpKeyName As String, _
                            ByVal lpDefault As String, _
                            ByVal lpReturnedString As System.Text.StringBuilder,
                            ByVal nSize As Integer, _
                            ByVal lpFileName As String) As Integer


    Public Declare Ansi Function GetPrivateProfileString2 Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal pApplicationName As String, _
                        ByVal lpKeyName As String, _
                        ByVal lpDefault As String, _
                        ByVal lpReturnedString As System.Text.StringBuilder,
                        ByVal nSize As Integer, _
                        ByVal lpFileName As String) As Integer


    <DllImport("kernel32.DLL", EntryPoint:="GetPrivateProfileStringW", SetLastError:=True, _
                CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function GetPrivateProfileString3(ByVal pApplicationName As String, _
                                                        ByVal lpKeyName As String, _
                                                        ByVal lpDefault As String, _
                                                        ByVal lpReturnedString As System.Text.StringBuilder,
                                                        ByVal nSize As Integer, _
                                                        ByVal lpFileName As String) As Integer
    End Function


    <DllImport("kernel32.DLL", EntryPoint:="GetPrivateProfileStringA", SetLastError:=True, _
            CharSet:=CharSet.Ansi, CallingConvention:=CallingConvention.StdCall)> _
    Public Shared Function GetPrivateProfileString4(ByVal pApplicationName As String, _
                                                        ByVal lpKeyName As String, _
                                                        ByVal lpDefault As String, _
                                                        ByVal lpReturnedString As System.Text.StringBuilder,
                                                        ByVal nSize As Integer, _
                                                        ByVal lpFileName As String) As Integer
    End Function

引用返信 編集キー/
■65093 / inTopicNo.5)  Re[3]: 変換後のGetPrivateProfileString
□投稿者/ 魔界の仮面弁士 (149回)-(2013/02/04(Mon) 17:20:41)
No65091 (MassyPie さん) に返信
> ご指摘された項目を全て修正しました。
紹介したサイトにあるサンプルをダウンロードして試してみましたか?
正しく動作するコードが含まれていたはずですよ。


>(修正したつもりです。)
元のサンプルでは、128 文字分のバッファを用意していたのに、
今度のコードでは、文字列バッファを用意していませんよね。これがまず問題。

受信用バッファを確保するために、
 Dim KeyValue As New System.Text.StringBuilder(バッファサイズ)
あるいは
 Dim KeyValue As New System.Text.StringBuilder()
 KeyValue.Append(vbNullChar, バッファサイズ)
などとしておいてください。

また、第5引数 nSize に渡す値は固定値とせず、keyValue.Capacity を用いて
求めるようにします。この部分の動作については、下記で解説されています。
[文字列に対する既定のマーシャリング]-[固定長の文字列バッファ]
http://msdn.microsoft.com/ja-jp/library/s9ts558h%28vs.80%29.aspx#cpcondefaultmarshalingforstringsanchor3


もう一つ。〜W 指定の方法が間違っています。

意図的に Wide 版 API を使うためには、「Unicode」句の指定が必要です。
宣言を「Public Declare Unicode Function 」に変更してみてください。
Ansi、指定省略、Unicode、Auto のそれぞれを用いた場合の動作が、
先のサイトにて実験・紹介されていますので、再度確認してみてください。


ちなみに、API から返された文字列には、コード 0 の文字、すなわち vbNullChar が含まれている
場合があります。たとえばこの API では、取得した文字列の末尾に vbNullChar が含まれます。
また、キー(第2引数)を Nothing にした場合には、各キーを vbNullChar で繋いで並べた
文字列が返されますし、第1引数も空なら、セクション名を vbNullChar で繋いで並べた
文字列が返されます。

そして、VB の開発環境は、vbNullChar を含む文字列をウォッチした場合に、
それ以降の文字が切り捨てて表示される事があります。

たとえば
  Dim sb As New System.Text.StringBuilder()
  sb.Append("ABC" & vbNullChar & "XYZ")
  Dim s As String
  s = sb.ToString()
という文字列 s をウォッチした場合、VB2005 においては
「"ABC」、VB2010では「"ABC XYZ"」と表示されますが、
実際には、いずれの環境でも s.Length は 7 を返します。



> 今の状態で、GetPrivateProfileStringW を、GetPrivateProfileStringA にしたところ、

それで問題が無ければ ANSI 版でも構わないと思います。多くの場合、A でも動くはずです。

それでも W をお奨めしたのは、〜A 系 が本来、16bit API や Win9x 向けのものであり、
NT 系 OS (NT, 2000, XP, Vista, 7, 8等)では、Unicode ベースで OS が組まれているためです。
(とはいえ、〜A で困ることが少ないせいか、W 系のサンプルはそう多く無いのですけれどね…)

W でないと困るのは、Unicode でないと扱えない文字があった場合です。
たとえば ini ファイルは、UTF-16で保存されたiniテキストや、レジストリ上の
 HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\IniFileMapping
から読み取られる可能性があり、その場合、データ中に Shift_JIS では表現できない文字が
含まれている場合があります。それを拾うためには Wide 版の利用が必要です。


なお、A 系を採用するとしたのであれば、
 retString = Left(strX, characters)
という記述だと、漢字等が混在していた場合に正しく切り出せません。
受信した文字列バッファ中の「vbNullChar」の位置を探って切り出す方が良いでしょう。

これは、Left 関数が切り出すのが「文字数」であるのに対し、
Wide バージョンでは、API の戻り値は「取得した文字数」ですが、
Ansi バージョンでは、API の戻り値は「取得したバイト数」だからです。

もし、Ansi 版を使いつつ、かつ、API の戻り値を利用して切り出したいというのであれば
System.Text.Encoding.Default を併用するなどの対処が必要です。


> 何かの定義が、まだ足りないのでしょうか?
On Error Resume Next を使っているのは、どのようなエラーに対処するためでしょうか?

characters が 0 以下だと "" を返すようにしておられますが、
どのような場合に 0 となり、どのような時に負数となるのでしょうか?
引用返信 編集キー/
■65094 / inTopicNo.6)  Re[4]: 変換後のGetPrivateProfileString
□投稿者/ MassyPie (9回)-(2013/02/04(Mon) 17:36:50)
ありがとうございます。
UNICODEで、できました。

しかし、1歩進むとまた問題が・・・


次のようなINIファイルを読み込んでいるのですが、

characters = GetPrivateProfileString(Section, key, "", KeyValue, 127, FileName)で


OUTFILE=C:\Program Files\OPCOPCOP\Data\TagData.csv を読み込み後、静かに死にます。
エラー等何も出しません。
あれっ?F8キーが効かない。と思っていたら、死んでいました。

スペースが悪いのかと思い、

OUTFILE=C:\TagData.csv にしたところと、正常動作しました。
しかし、スペースは FOLDER=C:\Program Files\OPCOPCOPC にも入っていますが、ここは正常に読み込んでいます。

何が問題なのでしょうか?


重ね重ねよろしくお願い致します。<(__)>




<iniファイル中身>

[OPC]
// 読み込み側OPCサーバ・ホスト名(ローカルの場合はブランク)
DCSOPCServer=STN01


// ログ設定
[LOG]
// ログ出力フォルダ
FOLDER=C:\Program Files\OPCOPCOPC   <−−− OK

// ログ保存期間(日)
LOGKEEPDAY=14



[FILE]
//
OUTFILE=C:\Program Files\OPCOPCOP\Data\TagData.csv    <−−− ここ!!!




引用返信 編集キー/
■65095 / inTopicNo.7)  Re[5]: 変換後のGetPrivateProfileString
□投稿者/ 魔界の仮面弁士 (150回)-(2013/02/04(Mon) 17:42:47)
2013/02/04(Mon) 17:43:49 編集(投稿者)

No65094 (MassyPie さん) に返信
> 次のようなINIファイルを読み込んでいるのですが、
> characters = GetPrivateProfileString(Section, key, "", KeyValue, 127, FileName)で
固定値「127」を指定しておられますが、取得用のバッファとなる
変数 KeyValue に、127 文字分のメモリを確保していないのだと予想しています。


> OUTFILE=C:\Program Files\OPCOPCOP\Data\TagData.csv を読み込み後、静かに死にます。
メモリ破損を起こすような不正な位置へのデータ書き込みが行われたため、
開発環境を巻き込んでクラッシュしてしまったものと思います。
入れ違いになってしまいましたが、 No65093 の回答を参照してみてください。


なお、GetPrivateProfileString 関数は、バッファサイズが不足した場合、そこで打ち切られた
文字列を返します。この場合、返却されたデータ長は nSize - 1 となります。
このような値が返された場合には、バッファ長を増やして、再度実行してみてください。
http://msdn.microsoft.com/ja-jp/library/cc429779.aspx

セクションやキー名に Nothing を指定したとき(列挙モード)には、
バッファ不足のときに nSize - 2 が返されます。
引用返信 編集キー/
■65105 / inTopicNo.8)  Re[6]: 変換後のGetPrivateProfileString
□投稿者/ MassyPie (10回)-(2013/02/05(Tue) 10:24:00)
本当にありがとうございます。

INIファイルは、全て読み込むことができました。
内容を変更しても、死んだりしないので問題ないと思われます。


教えて頂いたURLを読んでいても根本的なことが分かっていないので、こういうことかな?と思って進めて行くと、とんでもないことになっていました。
URLの内容の理解度、まずは50%を目標に頑張って行こうと思います。

本当に本当にありがとうございます。



P.S
このコンバージョン・プログラムが正常に動作するまで、まだまだ質問が続きそうな気配です。
その際は、またよろしくお願い致します。
解決済み
引用返信 編集キー/


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

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -