Для C# существует ли обратная сторона использования "string" вместо "StringBuilder" при вызове функций Win32, таких как GetWindowText?

рассмотрим эти два определения для GetWindowText. Один использует string для буфера другой использует StringBuilder вместо:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, string lpString, int nMaxCount);

вот как вы их называете:

var windowTextLength = GetWindowTextLength(hWnd);

// You can use either of these as they both work
var buffer = new string('', windowTextLength);
//var buffer = new StringBuilder(windowTextLength);

// Add 1 to windowTextLength for the trailing null character
var readSize = GetWindowText(hWnd, buffer, windowTextLength + 1);

Console.WriteLine($"The title is '{buffer}'");

они оба, кажется, работают правильно ли я проехать в string или StringBuilder. Тем не менее, все примеры, которые я видел, используют StringBuilder вариант. Даже PInvoke.net перечисляет это.

Я предполагаю, что мышление идет "в C# строки неизменяемы, поэтому используйте StringBuilder', но поскольку мы копаемся в Win32 API и возимся с местоположениями памяти напрямую, и этот буфер памяти для всех намерений и целей (предварительно)выделен (т. е. зарезервирован для и в настоящее время используется строкой) по характеру его присвоения значения при его определении, это ограничение фактически не применяется, следовательно string работает просто отлично. Но мне интересно, правильно ли это предположение.

Я так не думаю, потому что если вы проверите это, увеличив буфер, скажем, на 10, и измените символ, с которым вы его инициализируете, чтобы сказать "A", затем передайте этот больший размер буфера в GetWindowText, строка, которую вы получите,-это фактический заголовок, справа дополненный десятью дополнительными "A", которые не были перезаписаны, показывая, что он обновил это местоположение памяти предыдущих символов.

Итак, если вы предварительно инициализируете строки, не можете ли вы это сделать? Могут ли эти строки когда-либо "выйти из-под вас", используя их, потому что CLR предполагает, что они неизменяемы? Это я и пытаюсь выяснить.

2 ответов


если вы string для функции, использующей P / Invoke, среда CLR примет функцию will читать строку. Для эффективности строка закрепляется в памяти, и в функцию передается указатель на первый символ. Никакие символьные данные не должны копироваться таким образом.

конечно, функция может делать все, что захочет, с данными в строке, включая ее изменение.

Это означает, что функция перезапишет первые несколько символов без вопрос, но buffer.Length останется неизменным, и вы получите существующие данные в конце строки, все еще присутствующие в строке. строки .NET хранят свою длину в поле. Они также завершаются null, но null terminator используется только для удобства взаимодействия с кодом C и не влияет на управляемый код.

использование такой строки не было бы удобно, если бы вы заранее не определили размер строки, чтобы идеально соответствовать, где символ с нулевым завершением будет в конечном итоге записан, поле длины .NET будет не синхронизировано с базовыми данными.

кроме того, это лучше, так как изменение длины строки, безусловно, повредит кучу CLR (GC не сможет ходить по объектам). Строки и массивы-это единственные два типа объектов, которые не имеют фиксированного размера.

С другой стороны, если вы проходите StringBuilder через P / Invoke вы явно говорите маршалеру ожидается, что функция написать к экземпляру, и когда вы вызываете ToString() на нем тут обновите длину на основе символа null-termination, и все будет идеально синхронизировано.

лучше использовать правильный инструмент для работы. :)


во-первых, предварительно выделено это неверное слово в данном контексте.Строка ничем не отличается от другой неизменяемой строки .Net и является такой же неизменяемой, как Хью Джекман в реальной жизни. Я думаю, ОП это уже знает.

на самом деле:

// You can use either of these as they both work
var buffer = new string('', windowTextLength);

и ровно то же, что:

// assuming windowTextLength is 5
var buffer = ""; 

почему не должны ли мы использовать String/string и вместо того, чтобы использовать StringBuilder для передачи вызываемого изменяемого аргументы для взаимодействия / неуправляемого кода? Существуют ли конкретные сценарии, в которых он будет глючить?

честно говоря, я нашел этот интересный вопрос и протестировал несколько сценариев, написав пользовательскую собственную DLL, которая принимает строку и StringBuilder, в то время как я заставляю сбор мусора, заставляя GC в другом потоке и так далее. Мое намерение состояло в том, чтобы принудительно переместить объект, пока его адрес был передан во внешнюю библиотеку, хотя PInvoke. Во всех случаях адрес объекта оставался неизменным даже при перемещении других объектов. На исследовании я нашел это самим Джеффри:управляемая куча и сбор мусора в CLR

когда вы используете механизм P/Invoke среды CLR для вызова метода, среда CLR pins аргументы для вас автоматически и распаковывает их, когда возвращает собственный метод.

так что на вынос мы can используйте его, потому что он, кажется, работает. Но стоит ли? Я считаю нет:

  1. потому что его четко указано в документах,Строковые Буферы Фиксированной Длины. Так что string работает сейчас, может не работать в будущих версиях.
  2. , потому что StringBuilder является изменяемым типом библиотеки, и логически имеет смысл разрешить изменение изменяемого типа против неизменяемого типа (string).
  3. есть небольшое преимущество. При использовании StringBuilder, мы предварительно выделяем емкости, и не струна. Что это делает, мы избавляемся от дополнительных шагов для обрезки / очистки строки, а также не беспокоимся о завершении нулевого символа.