Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For C#, is there a down-side to using 'string' instead of 'StringBuilder' when calling Win32 functions such as GetWindowText?

Tags:

c#

winapi

user32

Consider these two definitions for GetWindowText. One uses a string for the buffer, the other uses a StringBuilder instead:

[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);

Here's how you call them:

var windowTextLength = GetWindowTextLength(hWnd);

// You can use either of these as they both work
var buffer = new string('\0', 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}'");

They both seem to work correctly whether I pass in a string, or a StringBuilder. However, all the examples I've seen use the StringBuilder variant. Even PInvoke.net lists that one.

My guess is the thinking goes 'In C# strings are immutable, therefore use StringBuilder', but since we're poking down to the Win32 API and messing with the memory locations directly, and that memory buffer is for all intents and purposes (pre)allocated (i.e. reserved for, and being currently used by the string) by the nature of it being assigned a value at its definition, that restriction doesn't actually apply, hence string works just fine. But I'm wondering if that assumption is wrong.

I don't think so because if you test this by increasing the buffer by say 10, and change the character you're initializing it with to say 'A', then pass in that larger buffer size to GetWindowText, the string you get back is the actual title, right-padded with the ten extra 'A's that weren't overwritten, showing it did update that memory location of the earlier characters.

So provided you pre-initialize the strings, can't you do this? Could those strings ever 'move out from under you' while using them because the CLR is assuming they're immutable? That's what I'm trying to figure out.

like image 770
Mark A. Donohoe Avatar asked Aug 28 '18 00:08

Mark A. Donohoe


People also ask

What is the use of for in C?

The for statement lets you repeat a statement or compound statement a specified number of times. The body of a for statement is executed zero or more times until an optional condition becomes false.

What is the correct syntax of for loop in C?

Explanation: for(;;) loop need not contain any initialization, condition and incre/decrement sections. All are optional. BREAK breaks the FOR Loop.

What is while loop in C?

The do/while loop is a variant of the while loop. This loop will execute the code block once, before checking if the condition is true, then it will repeat the loop as long as the condition is true.


1 Answers

First off, pre-allocated is a misleading word in current context.The string is nothing different than just another .Net immutable string, and is as immutable as Hugh Jackman in real life. I believe OP knows this already.

In fact:

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

is exactly same as:

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

Why shouldn't we use String/string and instead use StringBuilder for passing callee modifiable arguments to Interop/Unmanaged code? Are there specific scenarios where it will fail?

Honestly, I found this an interesting question and tested a few scenarios, by writing a custom Native DLL that takes a string and StringBuilder, while I force garbage collection, by forcing GC in different thread and so on. My intention was to force-relocate the object while its address was passed to an external library though PInvoke. In all cases, the object's address remained same even when other objects relocated. On research I found this by Jeffrey himself: The Managed Heap and Garbage Collection in the CLR

When you use the CLR’s P/Invoke mechanism to call a method, the CLR pins the arguments for you automatically and unpins them when the native method returns.

So takeaway is we can use it because it seems to work. But should we? I believe No:

  1. Because its clearly mentioned in the docs, Fixed-Length String Buffers. So string works for now, may not work in future releases.
  2. Because StringBuilder is library-provided mutable type, and logically it makes more sense to allow a mutable type to be modified vs an immutable type (string).
  3. There's a subtle advantage. When using StringBuilder, we're pre-allocating capacity, and not string. What this does is, we get rid of additional steps to trim/sanitize the string, and also not bother about terminating null character.
like image 106
subdeveloper Avatar answered Oct 02 '22 00:10

subdeveloper