GetPrivateProfileString - Длина буфера

Функции Windows GetPrivateProfileXXX (используемые для работы с файлами INI) имеют некоторые странные правила работы с длиной буфера.

В документации GetPrivateProfileString говорится:

Если [..] предоставленный целевой буфер слишком мал для хранения запрошенной строки, строка усекается и сопровождается нулевым символом, а возвращаемое значение равно nSize минус один.

Я прочитал это и понял, что такое поведение делает невозможным различение двух сценариев в коде:

  • Когда длина строки значений в точности равна nSize - 1.
  • Когда значение nSize (т.е. буфер) слишком мало.

Думал поэкспериментировать:

У меня есть это в файле INI:

[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, и вызывать функцию с большим буфером, но в этом не было бы необходимости в случаях, когда буфер имеет точно правильный размер.

Нет лучшего способа?


person Dai    schedule 08.05.2012    source источник


Ответы (6)


Пока я работал над тем, чтобы перенести в будущее часть моего античного кода, я обнаружил этот вопрос, касающийся буферизации и API частного профиля. После моих собственных экспериментов и исследований я могу подтвердить исходное утверждение автора вопроса о невозможности определить разницу между тем, когда строка имеет размер ровно nSize - 1, или когда буфер слишком мал.

Есть ли способ лучше? В принятом ответе Майка говорится, что это не соответствует документации, и вам следует просто попытаться убедиться, что буфер достаточно велик. Марк говорит увеличить буфер. Роман говорит коды ошибок проверки. Какой-то случайный пользователь говорит, что вам нужно предоставить достаточно большой буфер, и, в отличие от Марка, продолжает показывать код, расширяющий его буфер.

Есть ли способ лучше? Давайте узнаем факты!

Из-за возраста ProfileString API, поскольку ни один из тегов в этом вопросе не касается какого-либо конкретного языка и для удобства чтения, я решил показать свои примеры с использованием VB6. Не стесняйтесь переводить их для своих целей.


Документация GetPrivateProfileString

Согласно документации GetPrivateProfileString, эти функции частного профиля предоставляются только для совместимости с 16-разрядными приложениями на базе Windows. Это отличная информация, потому что она позволяет нам понять ограничения того, что могут делать эти функции API.

16-битовое целое число со знаком имеет диапазон от -32 768 до 32 767, а 16-битное целое число без знака имеет диапазон от 0 до 65 535. Если эти функции действительно созданы для использования в 16-битной среде, весьма вероятно, что любые числа, с которыми мы столкнемся, будут ограничены одним из этих двух пределов.

В документации указано, что каждая возвращаемая строка будет заканчиваться нулевым символом, а также сказано, что строка, которая не помещается в предоставленный буфер, будет усечена и завершена нулевым символом. Следовательно, если строка действительно помещается в буфер, второй последний символ будет иметь значение NULL, а также последний символ. Если только последний символ имеет значение NULL, то длина извлеченной строки точно такая же, как у предоставленного буфера - 1 или размер буфера недостаточен для хранения строки.

В любой ситуации, когда второй последний символ не равен нулю, извлеченная строка имеет точную длину или слишком велика для буфера, 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 символов, буфер будет содержать строку из файла, а также заканчиваться одним нулевым символом.

Это дает нам первое, хотя и не идеальное решение. Если вы хотите, чтобы ваш первый буфер всегда был достаточно большим, сделайте этот буфер длиной 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 символов, возможно, нет причин для создания такого большого буфера.

В разделе примечаний документации говорится, что раздел в файле инициализации должен иметь следующую форму:

[раздел]
ключ = строка

Можно считать, что каждый раздел содержит две квадратные скобки и знак равенства. После небольшого теста я смог убедиться, что 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 килобайта - это не много для современных компьютеров, а современные компьютеры довольно быстры, поэтому вы можете найти любой из этих примеров, который подходит для ваших целей в любом случае.

Я довольно много искал API GetPrivateProfileString и обнаружил, что обычно, когда кто-то, не обладающий обширными знаниями об API, пытается создать достаточно большой буфер для своих нужд, он выбирает длину буфера 255. Это позволило бы вы можете прочитать строку из файла длиной до 254 символов. Я не уверен, почему кто-то начал использовать это, но я предполагаю, что кто-то где-то представил этот API, используя строку, в которой длина буфера ограничена 8-битным числом без знака. Возможно это было ограничением WIN16.

Я собираюсь начать свой буфер с низкого уровня, 64 байта, если максимальная длина буфера не меньше, и учетверить это число либо до максимальной длины буфера, либо до 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 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 раздел не может содержать закрывающую скобку (]).

Быстрый тест API GetPrivateProfileString подтвердил, что это правда лишь частично. У меня не было проблем с использованием точки с запятой в имени ключа, если точка с запятой не была в самом начале. Они не упоминают никаких других ограничений в документации или в Википедии, хотя их может быть больше.

Еще один быстрый тест для определения максимальной длины раздела или имени ключа, принимаемого GetPrivateProfileString, дал мне ограничение в 65 535 символов. Эффект от использования строки, превышающей 65 535 символов, был таким же, как я испытал при тестировании максимальной длины буфера. Другой тест показал, что этот API принимает пустую строку в качестве имени раздела или ключа. По функциональности API это приемлемый файл инициализации:

[]
= Привет, мир!

Согласно Википедии, интерпретация пробелов различается. После еще одного теста 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, и мы будем читать только один раз, используя наш новый предел буфера.

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, ограничьте количество строк до 65 535 или менее символов задолго до использования WritePrivateProfileString. Также неплохо включить те же проверки.

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
person Brogan    schedule 03.02.2019

Нет лучшего способа. Просто убедитесь, что размер первого буфера достаточно велик. Любой метод, решающий эту проблему, должен использовать что-то, не описанное в документации, и, следовательно, не будет гарантировать работы.

person Mike Kwan    schedule 08.05.2012

Нет, к сожалению, лучшего пути нет. Вы должны предоставить буфер достаточно большого размера. Если этого недостаточно, перераспределите буфер. Я взял фрагмент кода здесь и адаптирован к вашему случаю:

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 всегда подойдет.

person Community    schedule 08.05.2012

Может быть, позвонить GetLastError сразу после GetPrivateProfileString - лучший вариант. Если буфер достаточно велик и других ошибок нет, GetLastError возвращает 0. Если буфер слишком мал, GetLastError возвращает 234 (0xEA) ERROR_MORE_DATA.

person Roman Sizov    schedule 10.11.2015
comment
К сожалению, функции INI всегда возвращают ERROR_MORE_DATA, когда они полностью заполняют буфер (даже без усечения данных). - person efotinis; 14.01.2016

Я знаю, что уже немного поздно, но я нашел отличное решение. Если не осталось места в буфере (возвращаемая длина + 1 = длина буфера), увеличьте размер буфера и снова получите значение. Повторяйте этот процесс, пока не останется буферное пространство.

person Marc Johnston    schedule 13.05.2016

Лучшее решение, безусловно, - это решение Брогана, но проверять размер файла как верхний предел размера буфера просто неправильно. Особенно при работе с INI-файлами, расположенными в Windows или системной папке, многие ключи, которые отображаются, для чтения и / или записи в реестр. Структуру отображения можно найти в:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\IniFileMapping

Полное объяснение того, как это работает, можно найти в Примечания в GetPrivateProfileString документации.

Таким образом, у вас может быть много строк, переназначенных в реестр, которые растут достаточно долго, но небольшой INI-файл на диске. В этом случае решение не читается.

У этого решения также есть еще одна небольшая проблема, когда не используется абсолютный путь к желаемому файлу или когда он не находится в текущем рабочем каталоге программы, поскольку GetPrivateProfileStrings ищет файл инициализации в каталоге Windows. Этой операции нет в функции FileLen, и решение не проверяет это.

Закончил выделение 64 КБ памяти и принял это как ограничение.

person Christian Cristofori    schedule 07.02.2020