Previous | Index | Next 

[PRB] A call to a Windows API Declare returns a string filled with spaces

We have found an undocumented detail in the way VB6 and VB.NET deal with string arguments passed by-reference to methods, which becomes important when invoking Windows API methods. Consider the following VB6 code:

    Private Declare Function GetPrivateProfileString Lib "kernel32.dll" Alias _
        "GetPrivateProfileStringA" (ByVal lpApplicationName As String, _
        ByVal lpKeyName As String, ByVal lpDefault As String, _
        ByVal lpReturnedString As String, ByVal nSize As Long, _
        ByVal lpFileName As String) As Long

        ' ...
        Dim sRet As String
        sRet = String(255, Chr(0))
        sRet = Left(sRet, GetPrivateProfileString("mysection", ByVal "mykey", "", sRet, _
               Len(sRet), "myfile.ini"))

Notice that, even though the lpReturnedString parameter appears to be passed by value, it is actually passed by reference and is the buffer where the GetPrivateProfileString method stores its string result, whereas the 32-bit value returned by the call is the number of characters stored in the buffer. The neat effect is that, when this code terminates its execution, the sRet variable contains the value read from the INI file.

VB Migration Partner translates the above fragment to this VB.NET code, which seems to be equivalent to the original code:

        Dim sRet As String = String6(255, Chr6(0))
        ' UPGRADE_WARNING (#0384): Using the 'GetPrivateProfileString' Windows API method
        ' as an argument to the 'Left' function might result in an string filled with 
        ' spaces. Please split the next line in two statements.
        sRet = VB.Left(sRet, GetPrivateProfileString("mysection", "mykey", "", sRet, _
               Len6(sRet), "myfile.ini"))

Here’s the problem: VB.NET pushes the first argument for Left (the sRet string) on the stack and then calls GetPrivateProfileString. When the API method modifies the sRet string it creates a new instance of the string, whereas the address already on the stack still points to the old string filled with spaces. When the code completes its execution, sRet contains a number of spaces rather than the actual value. Please notice that this isn’t a VB.NET (or VB Migration Partner’s) bug, it’s just a consequence of the fact that .NET strings are immutable.

You can fix this problem by following the suggestion given by the warning the VB Migration Partner emits: modify the original VB6 code to explicitly use temporary variables.

    Dim sRet As String
    sRet = String(255, Chr(0))
    Dim length As Integer
    length = GetPrivateProfileString("mysection", ByVal "mykey", "", sRet, _
        Len(sRet), "myfile.ini")
    sRet = Left(sRet, length)

This fix doesn’t change the behavior of the VB6 code, but the converted VB.NET code behaves as intended.

 

Previous | Index | Next