Richard Moss
Richard Moss

Reputation: 1620

Automatic casting for string DllImport arguments vs Marshal.StringToCoTaskMemUni

Consider the WM_SETTEXT message that you can use to set the text of another window via old school Win32 API. There are probably a multitude of ways of doing this in .NET, here's two that I know:

[DllImport("USER32", EntryPoint = "SendMessage", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, string lParam);

SendMessage(handle, WM_SETTEXT, IntPtr.Zero, "Magic String");

 

[DllImport("USER32", EntryPoint = "SendMessage", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

IntPtr textPointer = Marshal.StringToCoTaskMemUni("Magic String");
SendMessage(handle, WM_SETTEXT, IntPtr.Zero, textPointer);
Marshal.FreeCoTaskMem(textPointer);

The first one uses a string in the declaration of the dll import and lets .NET handle it. This second uses an IntPtr and explicitly creates a pointer using Marshal.StringToCoTaskMemUni and then freed using Marshal.FreeCoTaskMem. Both of these approaches work happily as far as I can tell.

Note I normally do the former (which in fact I used to do many years ago as a VB6 programmer), but I came across the latter on a comment on another StackOverflow post and was curious if the former was another bad habit that needed squashing.

I have also seen a variant of the first example using a char[] array in the declaration and passing in a StringBuilder

My question is simple - which (if any) is the correct way of doing it? For example, the SendMessage Win32 call only accepts pointers anyway, so "something" must be creating pointers behind the scenes for the first version, and if so does it do the cleaning up, or is it better to be verbose and use the explicit allocation and deallocation of pointers.

Upvotes: 0

Views: 1032

Answers (1)

David Heffernan
David Heffernan

Reputation: 613311

When you declare a parameter of string, the p/invoke marshaller will, behind the scenes, marshal that to a pointer to a null-terminated array of characters. The marshaller will handle all memory allocation and deallocation. You can do it yourself with an IntPtr parameter, but it comes to the same in the end.

Which you choose comes down to whether or not you plan to use the SendMessage declaration for any other messages that you send.

  • If you only ever call SendMessage for WM_SETTEXT, then you declare it to receive a string parameter.
  • If you also use SendMessage to send other messages, then you can declare the parameter to be IntPtr and perform the marshalling manually.
  • And yet another option is to declare multiple overloaded variants so that you can make the code that calls SendMessage as convenient as possible for the programmer.

Now, let's pick up that last option. Suppose you needed to send WM_GETTEXT as well as WM_SETTEXT. For WM_SETTEXT you need to pass string data from managed to native, so you'd prefer string. For WM_GETTEXT, you need to supply a buffer so that the native code can fill out the window text. And that means you want to use StringBuilder. So, declare two overloads, one receiving string and one receiving StringBuilder. Use the former for WM_SETTEXT and the latter for WM_GETTEXT.

Upvotes: 2

Related Questions