アンティークコードの一部を将来に持ち込む作業をしているときに、バッファリングとプライベートプロファイルAPIに関するこの質問を見つけました。私自身の実験と調査の結果、文字列が正確にnSize -1の場合と、バッファが小さすぎる場合の違いを判断できないという、質問者の元のステートメントを確認できます。
もっと良い方法はありますか?マイクからの受け入れられた答えは、ドキュメントによるとありません、そしてあなたはただバッファが十分に大きいことを確認することを試みるべきであると言います。マークはバッファを増やすように言います。ローマンはチェックエラーコードを言います。一部のランダムなユーザーは、十分な大きさのバッファーを提供する必要があると言い、Marcとは異なり、自分のバッファーを拡張するコードを表示します。
もっと良い方法はありますか?事実を知りましょう!
ProfileString APIが古くなったため、この質問のタグは特定の言語に関係せず、読みやすくするために、VB6を使用して例を示すことにしました。あなた自身の目的のためにそれらを自由に翻訳してください。
GetPrivateProfileStringドキュメント
GetPrivateProfileStringのドキュメントによると、これらのプライベートプロファイル関数は、16ビットのWindowsベースのアプリケーションとの互換性のためにのみ提供されています。これらのAPI関数で実行できることの制限を理解できるため、これはすばらしい情報です。
16ビットの符号付き整数の範囲は-32,768〜32,767で、符号なしの16ビット整数の範囲は0〜65,535です。これらの関数が本当に16ビット環境で使用するために作成されている場合、遭遇する数値はこれら2つの制限のいずれかに制限される可能性が高くなります。
ドキュメントには、返されるすべての文字列はヌル文字で終わると記載されており、提供されたバッファに収まらない文字列は切り捨てられ、ヌル文字で終了するとも記載されています。したがって、文字列がバッファに収まる場合、最後から2番目の文字は最後の文字と同様にnullになります。最後の文字だけがnullの場合、抽出された文字列は指定されたバッファとまったく同じ長さ-1であるか、バッファが文字列を保持するのに十分な大きさではありませんでした。
最後から2番目の文字がnullでない場合、抽出された文字列が正確な長さであるか、バッファに対して大きすぎる場合、GetLastErrorはエラー番号234 ERROR_MORE_DATA(0xEA)を返し、それらを区別する方法がありません。
GetPrivateProfileStringで受け入れられる最大バッファーサイズはいくつですか?
ドキュメントには最大バッファサイズが記載されていませんが、このAPIが16ビット環境用に設計されていることはすでにわかっています。少し実験した結果、最大バッファサイズは65,536であると結論付けることができました。。ファイル内の文字列の長さが65,535文字を超える場合、文字列を読み取ろうとしているときに奇妙な動作が見られ始めます。ファイル内の文字列の長さが65,536文字の場合、取得される文字列の長さは0文字になります。ファイル内の文字列の長さが65,546文字の場合、取得される文字列は10文字の長さで、ヌル文字で終わり、ファイルに含まれる文字列の先頭から切り捨てられます。APIは65,535文字を超える文字列を書き込みますが、65,535文字を超える文字列を読み取ることはできません。バッファの長さが65,536で、ファイル内の文字列の長さが65,535文字の場合、バッファにはファイルの文字列が含まれ、1つのヌル文字で終わります。
これは、完璧な解決策ではありませんが、最初の解決策を提供します。最初のバッファが十分に大きいことを常に確認したい場合は、そのバッファを65,536文字の長さにします。
Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Public Function iniRead(ByVal Pathname As String, ByVal Section As String, ByVal Key As String, Optional ByVal Default As String) As String
On Error GoTo iniReadError
Dim Buffer As String
Dim Result As Long
Buffer = String$(65536, vbNullChar)
Result = GetPrivateProfileString(Section, Key, vbNullString, Buffer, 65536, Pathname)
If Result <> 0 Then
iniRead = Left$(Buffer, Result)
Else
iniRead = Default
End If
iniReadError:
End Function
最大バッファサイズがわかったので、ファイルのサイズを使用してファイルを修正できます。ファイルのサイズが65,535文字未満の場合、それほど大きなバッファを作成する理由はないかもしれません。
ドキュメントの備考セクションでは、初期化ファイルのセクションは次の形式である必要があると記載されています。
[セクション]
key= string
各セクションには2つの角括弧と等号が含まれていると想定できます。簡単なテストの後、APIがセクションとキーの間のあらゆる種類の改行(vbLf、vbCr、またはvbCrLf / vbNewLine)を受け入れることを確認できました。これらの詳細とセクションおよびキー名の長さにより、最大バッファー長を狭めることができ、ファイルを読み取ろうとする前に、ファイルサイズが文字列を含むのに十分な大きさになるようにすることもできます。
Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Public Function iniRead(ByVal Pathname As String, ByVal Section As String, ByVal Key As String, Optional ByVal Default As String) As String
On Error Resume Next
Dim Buffer_Size As Long
Err.Clear
Buffer_Size = FileLen(Pathname)
On Error GoTo iniReadError
If Err.Number = 0 Then
If Buffer_Size > 4 + Len(Section) + Len(Key) Then
Dim Buffer As String
Dim Result As Long
Buffer_Size = Buffer_Size - Len(Section) - Len(Key) - 4
If Buffer_Size > 65535 Then
Buffer_Size = 65536
Else
Buffer_Size = Buffer_Size + 1
End If
Buffer = String$(Buffer_Size, vbNullChar)
Result = GetPrivateProfileString(Section, Key, vbNullString, Buffer, Buffer_Size, Pathname)
If Result <> 0 Then
iniRead = Left$(Buffer, Result)
Exit Function
End If
End If
End If
iniRead = Default
iniReadError:
End Function
バッファの成長
最初のバッファが十分に大きく、最大バッファサイズが変更されていることを確認するために一生懸命努力したので、小さいバッファから始めて、バッファのサイズを徐々に増やして作成する方が理にかなっているかもしれません。ファイルから文字列全体を抽出できる十分な大きさのバッファ。ドキュメントによると、APIは234エラーを作成して、利用可能なデータがさらにあることを通知します。彼らがこのエラーコードを使用して、より大きなバッファで再試行するように指示することは非常に理にかなっています。何度も再試行することの欠点は、コストがかかることです。ファイル内の文字列が長いほど、それを読み取るために必要な試行回数が多くなり、時間がかかります。今日のコンピューターでは64キロバイトはそれほど多くなく、今日のコンピューターはかなり高速です。
GetPrivateProfileString APIをかなり検索しましたが、通常、APIの知識が豊富でない人が、必要に応じて十分な大きさのバッファーを作成しようとすると、255のバッファー長を選択することがわかりました。最大254文字の長さの文字列をファイルから読み取ることができます。なぜ誰かがこれを使い始めたのかはわかりませんが、バッファ長が8ビットの符号なしの数値に制限されている文字列を使用してこのAPIを想像した人がいると思います。おそらくこれはWIN16の制限でした。
最大バッファー長が短い場合を除いて、バッファーを64バイトの下位から開始し、最大バッファー長または65,536までの数を4倍にします。数値を2倍にすることもできます。乗算を大きくすると、ファイルを読み取って大きな文字列を探す回数が減りますが、比較的言えば、中程度の長さの文字列には余分なパディングが含まれる場合があります。
Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Public Function iniRead(ByVal Pathname As String, ByVal Section As String, ByVal Key As String, Optional ByVal Default As String) As String
On Error Resume Next
Dim Buffer_Max As Long
Err.Clear
Buffer_Max = FileLen(Pathname)
On Error GoTo iniReadError
If Err.Number = 0 Then
If Buffer_Max > 4 + Len(Section) + Len(Key) Then
Dim Buffer As String
Dim Result As Long
Dim Buffer_Size As Long
Buffer_Max = Buffer_Max - Len(Section) - Len(Key) - 4
If Buffer_Max > 65535 Then
Buffer_Max = 65536
Else
Buffer_Max = Buffer_Max + 1
End If
If Buffer_Max < 64 Then
Buffer_Size = Buffer_Max
Else
Buffer_Size = 64
End If
Buffer = String$(Buffer_Size, vbNullChar)
Result = GetPrivateProfileString(Section, Key, vbNullString, Buffer, Buffer_Size, Pathname)
If Result <> 0 Then
If Buffer_Max > 64 Then
Do While Result = Buffer_Size - 1 And Buffer_Size < Buffer_Max
Buffer_Size = Buffer_Size * 4
If Buffer_Size > Buffer_Max Then
Buffer_Size = Buffer_Max
End If
Buffer = String$(Buffer_Size, vbNullChar)
Result = GetPrivateProfileString(Section, Key, vbNullString, Buffer, Buffer_Size, Pathname)
Loop
End If
iniRead = Left$(Buffer, Result)
Exit Function
End If
End If
End If
iniRead = Default
iniReadError:
End Function
検証の改善
実装によっては、パス名、セクション、およびキー名の検証を改善すると、バッファーを準備する必要がなくなる場合があります。
ウィキペディアのINIファイルページによると、彼らは次のように述べています。
Windowsの実装では、キーに等号(=)またはセミコロン(;)の文字を含めることはできません。これらは予約文字であるためです。値には任意の文字を含めることができます。
と
Windowsの実装では、セクションに文字を閉じる角かっこ(])を含めることはできません。
GetPrivateProfileString APIの簡単なテストにより、これが部分的にのみ正しいことが証明されました。セミコロンが最初にない限り、キー名内でセミコロンを使用しても問題はありませんでした。ドキュメントやウィキペディアには他にも制限があるかもしれませんが、他の制限については触れられていません。
GetPrivateProfileStringで受け入れられるセクションまたはキー名の最大長を見つけるための別の簡単なテストでは、65,535文字の制限がありました。65,535文字を超える文字列を使用した場合の影響は、最大バッファ長をテストしたときに経験したものと同じでした。別のテストでは、このAPIがセクション名またはキー名のいずれかに空白の文字列を受け入れることが証明されました。APIの機能によると、これは許容可能な初期化ファイルです。
[]
= Hello world!
ウィキペディアによると、空白の解釈はさまざまです。さらに別のテストの後、Profile String APIはセクション名とキー名から空白を確実に削除しているので、それを実行してもおそらく問題ありません。
Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Public Function iniRead(ByVal Pathname As String, ByVal Section As String, ByVal Key As String, Optional ByVal Default As String) As String
On Error Resume Next
If Len(Pathname) <> 0 Then
Key = Trim$(Key)
If InStr(1, Key, ";") <> 1 Then
Section = Trim$(Section)
If Len(Section) > 65535 Then
Section = RTrim$(Left$(Section, 65535))
End If
If InStr(1, Section, "]") = 0 Then
If Len(Key) > 65535 Then
Key = RTrim$(Left$(Key, 65535))
End If
If InStr(1, Key, "=") = 0 Then
Dim Buffer_Max As Long
Err.Clear
Buffer_Max = FileLen(Pathname)
On Error GoTo iniReadError
If Err.Number = 0 Then
If Buffer_Max > 4 + Len(Section) + Len(Key) Then
Dim Buffer As String
Dim Result As Long
Dim Buffer_Size As Long
Buffer_Max = Buffer_Max - Len(Section) - Len(Key) - 4
If Buffer_Max > 65535 Then
Buffer_Max = 65536
Else
Buffer_Max = Buffer_Max + 1
End If
If Buffer_Max < 64 Then
Buffer_Size = Buffer_Max
Else
Buffer_Size = 64
End If
Buffer = String$(Buffer_Size, vbNullChar)
Result = GetPrivateProfileString(Section, Key, vbNullString, Buffer, Buffer_Size, Pathname)
If Result <> 0 Then
If Buffer_Max > 64 Then
Do While Result = Buffer_Size - 1 And Buffer_Size < Buffer_Max
Buffer_Size = Buffer_Size * 4
If Buffer_Size > Buffer_Max Then
Buffer_Size = Buffer_Max
End If
Buffer = String$(Buffer_Size, vbNullChar)
Result = GetPrivateProfileString(Section, Key, vbNullString, Buffer, Buffer_Size, Pathname)
Loop
End If
iniRead = Left$(Buffer, Result)
Exit Function
End If
End If
End If
iniRead = Default
End If
End If
End If
End If
iniReadError:
End Function
静的長バッファ
最大長または静的長の変数を格納する必要がある場合があります。ユーザー名、電話番号、カラーコード、またはIPアドレスは、最大バッファー長を制限したい文字列の例です。必要に応じてそうすることで、時間とエネルギーを節約できます。
以下のコード例では、Buffer_MaxはBuffer_Limit + 1に制限されます。制限が64より大きい場合は、64から始めて、前と同じようにバッファーを拡張します。64未満であり、新しいバッファ制限を使用して1回だけ読み取ります。
Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Public Function iniRead(ByVal Pathname As String, ByVal Section As String, ByVal Key As String, Optional ByVal Default As String, Optional Buffer_Limit As Long = 65535) As String
On Error Resume Next
If Len(Pathname) <> 0 Then
Key = Trim$(Key)
If InStr(1, Key, ";") <> 1 Then
Section = Trim$(Section)
If Len(Section) > 65535 Then
Section = RTrim$(Left$(Section, 65535))
End If
If InStr(1, Section, "]") = 0 Then
If Len(Key) > 65535 Then
Key = RTrim$(Left$(Key, 65535))
End If
If InStr(1, Key, "=") = 0 Then
Dim Buffer_Max As Long
Err.Clear
Buffer_Max = FileLen(Pathname)
On Error GoTo iniReadError
If Err.Number = 0 Then
If Buffer_Max > 4 + Len(Section) + Len(Key) Then
Dim Buffer As String
Dim Result As Long
Dim Buffer_Size As Long
Buffer_Max = Buffer_Max - Len(Section) - Len(Key) - 4
If Buffer_Limit > 65535 Then
Buffer_Limit = 65535
End If
If Buffer_Max > Buffer_Limit Then
Buffer_Max = Buffer_Limit + 1
Else
Buffer_Max = Buffer_Max + 1
End If
If Buffer_Max < 64 Then
Buffer_Size = Buffer_Max
Else
Buffer_Size = 64
End If
Buffer = String$(Buffer_Size, vbNullChar)
Result = GetPrivateProfileString(Section, Key, vbNullString, Buffer, Buffer_Size, Pathname)
If Result <> 0 Then
If Buffer_Max > 64 Then
Do While Result = Buffer_Size - 1 And Buffer_Size < Buffer_Max
Buffer_Size = Buffer_Size * 4
If Buffer_Size > Buffer_Max Then
Buffer_Size = Buffer_Max
End If
Buffer = String$(Buffer_Size, vbNullChar)
Result = GetPrivateProfileString(Section, Key, vbNullString, Buffer, Buffer_Size, Pathname)
Loop
End If
iniRead = Left$(Buffer, Result)
Exit Function
End If
End If
End If
iniRead = Default
End If
End If
End If
End If
iniReadError:
End Function
WritePrivateProfileStringを使用する
GetPrivateProfileStringを使用して文字列を読み取る際に問題が発生しないようにするには、WritePrivateProfileStringを使用するずっと前に、文字列を65,535文字以下に制限してください。同じ検証を含めることもお勧めします。
Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Private Declare Function WritePrivateProfileString Lib "kernel32" Alias "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As Long
Public Function iniRead(ByVal Pathname As String, ByVal Section As String, ByVal Key As String, Optional ByVal Default As String, Optional Buffer_Limit As Long = 65535) As String
On Error Resume Next
If Len(Pathname) <> 0 Then
Key = Trim$(Key)
If InStr(1, Key, ";") <> 1 Then
Section = Trim$(Section)
If Len(Section) > 65535 Then
Section = RTrim$(Left$(Section, 65535))
End If
If InStr(1, Section, "]") = 0 Then
If Len(Key) > 65535 Then
Key = RTrim$(Left$(Key, 65535))
End If
If InStr(1, Key, "=") = 0 Then
Dim Buffer_Max As Long
Err.Clear
Buffer_Max = FileLen(Pathname)
On Error GoTo iniReadError
If Err.Number = 0 Then
If Buffer_Max > 4 + Len(Section) + Len(Key) Then
Dim Buffer As String
Dim Result As Long
Dim Buffer_Size As Long
Buffer_Max = Buffer_Max - Len(Section) - Len(Key) - 4
If Buffer_Limit > 65535 Then
Buffer_Limit = 65535
End If
If Buffer_Max > Buffer_Limit Then
Buffer_Max = Buffer_Limit + 1
Else
Buffer_Max = Buffer_Max + 1
End If
If Buffer_Max < 64 Then
Buffer_Size = Buffer_Max
Else
Buffer_Size = 64
End If
Buffer = String$(Buffer_Size, vbNullChar)
Result = GetPrivateProfileString(Section, Key, vbNullString, Buffer, Buffer_Size, Pathname)
If Result <> 0 Then
If Buffer_Max > 64 Then
Do While Result = Buffer_Size - 1 And Buffer_Size < Buffer_Max
Buffer_Size = Buffer_Size * 4
If Buffer_Size > Buffer_Max Then
Buffer_Size = Buffer_Max
End If
Buffer = String$(Buffer_Size, vbNullChar)
Result = GetPrivateProfileString(Section, Key, vbNullString, Buffer, Buffer_Size, Pathname)
Loop
End If
iniRead = Left$(Buffer, Result)
Exit Function
End If
End If
End If
iniWrite Pathname, Section, Key, Default
iniRead = Default
End If
End If
End If
End If
iniReadError:
End Function
Public Function iniWrite(ByVal Pathname As String, ByVal Section As String, ByVal Key As String, ByVal Value As String) As Boolean
On Error GoTo iniWriteError
If Len(Pathname) <> 0 Then
Key = Trim$(Key)
If InStr(1, Key, ";") <> 1 Then
Section = Trim$(Section)
If Len(Section) > 65535 Then
Section = RTrim$(Left$(Section, 65535))
End If
If InStr(1, Section, "]") = 0 Then
If Len(Key) > 65535 Then
Key = RTrim$(Left$(Key, 65535))
End If
If InStr(1, Key, "=") = 0 Then
If Len(Value) > 65535 Then Value = Left$(Value, 65535)
iniWrite = WritePrivateProfileString(Section, Key, Value, Pathname) <> 0
End If
End If
End If
End If
iniWriteError:
End Function