Winapi GetPrivateProfileString-缓冲区长度
Windows的GetPrivateProfileXXX函数(用于处理INI文件)在处理缓冲区长度方面有一些奇怪的规则 GetPrivateProfileString的文档状态: 如果提供的目标缓冲区[…]太小,无法容纳请求的字符串,则该字符串将被截断并后跟一个空字符,返回值等于nSize减1 我读了这篇文章,意识到这种行为使得无法在代码中区分两种场景:Winapi GetPrivateProfileString-缓冲区长度,winapi,ini,Winapi,Ini,Windows的GetPrivateProfileXXX函数(用于处理INI文件)在处理缓冲区长度方面有一些奇怪的规则 GetPrivateProfileString的文档状态: 如果提供的目标缓冲区[…]太小,无法容纳请求的字符串,则该字符串将被截断并后跟一个空字符,返回值等于nSize减1 我读了这篇文章,意识到这种行为使得无法在代码中区分两种场景: 当值字符串的长度正好等于nSize-1时 nSize值(即缓冲区)太小时 我想我应该做个实验: 我在INI文件中有这个: [Bar] fo
- 当值字符串的长度正好等于nSize-1时
- nSize值(即缓冲区)太小时
[Bar]
foo=123456
我用这些参数调用GetPrivateProfileString作为测试:
// Test 1. The buffer is big enough for the string (16 character buffer).
BYTE* buffer1 = (BYTE*)calloc(16, 2); // using 2-byte characters ("Unicode")
DWORD result1 = GetPrivateProfileString(L"Bar", L"foo", NULL, buffer, 16, fileName);
// result1 is 6
// buffer1 is { 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 0, 0, 0, 0, ... , 0, 0 }
// Test 2. The buffer is exactly sufficient to hold the value and the trailing null (7 characters).
BYTE* buffer2 = (BYTE*)calloc(7, 2);
DWORD result2 = GetPrivateProfileString(L"Bar", L"foo", NULL, buffer, 7, fileName);
// result2 is 6. This is equal to 7-1.
// buffer2 is { 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 0, 0 }
// Test 3. The buffer is insufficient to hold the value and the trailing null (6 characters).
BYTE* buffer3 = (BYTE*)calloc(6, 2);
DWORD result3 = GetPrivateProfileString(L"Bar", L"foo", NULL, buffer, 6, fileName);
// result3 is 5. This is equal to 6-1.
// buffer3 is { 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 0, 0 }
调用此代码的程序无法确定实际键值的长度是否为5个字符,甚至6个字符,因为在最后两种情况下,结果等于nSize-1
唯一的解决方案是在result==nSize-1时进行检查,并使用更大的缓冲区调用函数,但在缓冲区大小正好合适的情况下,这是不必要的
没有更好的办法了吗?没有更好的办法了。请确保第一个缓冲区足够大。任何解决此问题的方法都必须使用文档中未描述的内容,因此无法保证有效。不,不幸的是,没有更好的方法。您必须提供足够大的缓冲区。如果不够,请重新分配缓冲区。我从您的案例中提取了一段代码片段:
int nBufferSize = 1000;
int nRetVal;
int nCnt = 0;
BYTE* buffer = (BYTE*)calloc(1, 2);
do
{
nCnt++;
buffer = (BYTE*) realloc (buffer , nBufferSize * 2 * nCnt);
DWORD nRetVal = GetPrivateProfileString(L"Bar", L"foo", NULL,
buffer, nBufferSize*nCnt, filename);
} while( (nRetVal == ((nBufferSize*nCnt) - 1)) ||
(nRetVal == ((nBufferSize*nCnt) - 2)) );
但是,在您的特定情况下,文件名的长度不能大于MAX_PATH,因此
(MAX_PATH+1)*2
将始终适用 也许,在GetPrivateProfileString
之后调用GetLastError
是一种方法。如果缓冲区足够大并且没有其他错误,GetLastError
返回0。如果缓冲区太小,GetLastError
返回234(0xEA)ERROR\u MORE\u DATA
我知道有点晚了,但我想出了一个很棒的解决方案。如果没有剩余的缓冲区空间(返回长度+1=缓冲区长度),则增加缓冲区并再次获取值。重复这个过程,直到有剩余的缓冲区空间。当我致力于将我的一些古老代码带入未来时,我发现了这个关于缓冲和私有配置文件API的问题。在我自己的实验和研究之后,我可以确认询问者关于无法确定字符串是nSize-1还是缓冲区太小之间的差异的原始陈述
有更好的办法吗?Mike接受的回答是,根据文档,没有缓冲区,您应该确保缓冲区足够大。马克说要增加缓冲区。罗曼说检查错误代码。一些随机用户说您需要提供足够大的缓冲区,并且不像Marc,继续显示一些扩展缓冲区的代码
有更好的办法吗?让我们了解事实吧
由于ProfileString API已经过时,因为这个问题的标记都不涉及任何特定的语言,并且为了便于阅读,我决定使用VB6展示我的示例。为了你自己的目的,请随意翻译
GetPrivateProfileString文档 根据,这些私有配置文件功能仅用于与基于Windows的16位应用程序兼容。这是一个很好的信息,因为它允许我们理解这些API函数所能做的限制 16位有符号整数的范围为−32768到32767,无符号16位整数的范围为0到65535。如果这些函数真正用于16位环境,那么我们遇到的任何数字都很可能被限制在这两个限制之一 文档说明返回的每个字符串都将以空字符结尾,并且还说明不适合提供的缓冲区的字符串将被截断并以空字符结尾。因此,如果字符串确实适合缓冲区,则最后一个字符和最后一个字符都将为空。如果只有最后一个字符为null,则提取的字符串与提供的缓冲区-1的长度完全相同,或者缓冲区不够大,无法容纳字符串 在最后一个字符不为null、提取的字符串为精确长度或对于缓冲区来说太大的任何情况下,GetLastError都将返回错误号,使我们无法区分它们
GetPrivateProfileString接受的最大缓冲区大小是多少? 虽然文档没有说明最大缓冲区大小,但我们已经知道该API是为16位环境设计的。经过一点实验,我得出结论,最大缓冲区大小是65536。如果文件中的字符串长度大于65535个字符,我们在尝试读取字符串时会看到一些奇怪的行为。如果文件中的字符串长度为65536个字符,则检索到的字符串长度将为0个字符。如果文件中的字符串长度为65546个字符,则检索到的字符串长度将为10个字符,以空字符结尾,并从文件中包含的字符串的最开始处截断。API将写入大于65535个字符的字符串,但无法读取大于65535个字符的任何内容。如果缓冲区长度为65536,且文件中的字符串长度为65535个字符,则缓冲区将包含文件中的字符串,并以单个空字符结尾 这为我们提供了第一个,尽管不是完美的解决方案。如果您希望始终确保第一个缓冲区很大
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
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
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
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
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
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
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\IniFileMapping