|
Strings pose a problem when you're calling unmanaged methods . Most managed types map onto one unmanaged equivalent-for example, a Boolean maps onto a C++ bool- but a managed string can map onto several unmanaged types.
When you're calling Windows API functions, a managed string can map onto four unmanaged string types:
ANSI strings, represented by the LPSTR type
Unicode strings, represented by LPWSTR
System-dependent strings, represented by LPTSTR
COM strings, represented by BSTR
Note | The system-dependent type, LPTSTR , is provided so that the same API function can be used on both Unicode and ANSI systems. LPTSTR will be represented by LPSTR or LPWSTR , depending on whether the ANSI or Unicode version, respectively, of the Windows API function is being called. |
The default conversion for managed string arguments depends on the information supplied in the Platform Invoke prototype. As I explained in Chapter 12, the CharSet parameter can be used with the DllImport attribute to specify which character encoding to use. Table 13-2 summarizes the values that CharSet can take and the default values assumed by the .NET programming languages.
CharSet Value | Description |
---|---|
Ansi | Marshals strings as ANSI characters . This is the default value for Visual Basic .NET and Microsoft Visual C#. |
Auto | Chooses ANSI or Unicode, depending on the target system. The default is ANSI on Windows 98 and Windows ME, and Unicode on Windows NT, Windows 2000, Windows XP, and Windows 2003 Server. |
None | This value is obsolete and is equivalent to CharSet.Ansi. This is the default value for managed C++. |
Unicode | Marshals strings as Unicode characters. |
String and StringBuilder
When marshaling strings, remember that .NET System.String objects are immutable: once created, their content cannot be changed. This means that System.String objects can be used only as [in] parameters and return values; you should use the System.Text.StringBuilder type to represent string arguments that will be marshaled back to the managed caller.
To use a C-style string as a return type from an unmanaged function, you will normally use a managed string, as shown in the following C# code fragment:
//Unmanagedfunction char*ReturnsAString(); //PlatformInvokeprototype [DllImport("mydll.dll")] publicexternstringReturnsAString();
When a return value is marshaled in this way, the interop marshaler will assume that it has to free the unmanaged memory returned by the function once it has created the managed string and initialized it. Occasionally this is not the case: for instance, the GetCommandLine API returns a string buffer that is owned by the system and which must not be freed by the caller. In this case, you need to specify an IntPtr as the return type. An IntPtr is an integer that is large enough to hold a pointer, but the interop marshaler will not automatically treat this as a pointer and will not free the memory it references.
Here is a Visual C# example showing how to return an IntPtr and reference the string through the IntPtr :
//TheGetCommandLineAPIreturnsapointertoabuffer //thatisownedbythesystem,andwhichmustnotbe //freedbythecaller [DllImport("kernel32.dll",CharSet=CharSet.Auto)] publicstaticexternIntPtrGetCommandLine(); //CallGetCommandLine IntPtrip=GetCommandLine(); stringcmdLine=Marshal.PtrToStringAuto(ip);
Note how the Marshal.PtrToStringAuto method is used to marshal the data referenced by the IntPtr back into a string.
When a string argument to an unmanaged function needs to be marshaled back to the caller, you need to use a StringBuilder . In the following example, the FormatMessage API builds a string and passes it back in the fifth argument:
//ThePlatformInvokeprototypeforFormatMessage [DllImport("kernel32.dll",CharSet=CharSet.Auto)] publicstaticexternintFormatMessage(intflags, IntPtrsource,intmessageId, intlangId,StringBuilderbuff, intsize,IntPtrargs); //CallingthefunctionfromC#code StringBuilderbuff=newStringBuilder(256); FormatMessage(TestPI.FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero,errCode,0, buff,buff.Capacity,IntPtr.Zero); Console.WriteLine("Errormessage:{0}",buff);
A StringBuilder object needs to be constructed so that it can be passed to the FormatMessage function, and you need to ensure that the object has enough capacity to hold the returned string.
|