Advanced Windows APIs conversion with refactoring

    Redundant API declarations will be converted to a single declaration, in an API declarations project.
    All uses of these APIs will be redirected to this single declaration
    The VBUC creates a wrapper for each unmanaged API declaration.
    Using this Wrapper files allows for easy replacement of unmanaged APIs
    This refactoring functionality will produce more readable and maintainable code

    For example if you had a VB6 project with a couple of forms like the following:

    When this project is migrated using the VBUC it will generate a Visual Studio Solution like the following:

    Notice that a new project is added. In the lastest version of the VBUC this project will usually be called with the same name as your VBUC solution + Support. So for example if your solution name is UpgradeSolution1 a folder called UpgradeSolution1Support will be created with a project with the same name.
    Two subfolder will be created:

    • PInvokeSafeMethods:
    • PInvokeUnSafeMethods

    Inside those folder a file will be created grouping PInvoke calls per DLL. If your API calls were for example to user32.dll then a file with that name will generated and all PInvoke calls to that DLL will be arranged inside that file.

    Below you can see an example of how the code is upgraded.

    VB6 Code

    Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

    Private Declare Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As Long) As Long

    Private Sub Command1_Click()
      Dim titleForm1 As String
      titleForm1 = String(GetWindowTextLength(Me.hwnd) + 1, Chr$(0))
       GetWindowText Me.hwnd, titleForm1, Len(titleForm1)
    End Sub

    .NET Code

    File: user32.cs (safe methods)
    public static class user32
    {
    public static int GetWindowText(int hwnd,ref string lpString, int cch)
      {
      return WinAPI.UnsafeNative.user32.GetWindowText(hwnd,ref lpString,
        cch);
      }
      public static int GetWindowTextLength( int hwnd)
      {
       return WinAPI.UnsafeNative.user32.GetWindowTextLength(hwnd);
      }
    }

    File: user32.cs (unsafe methods) [System.Security.SuppressUnmanagedCodeSecurity]
    public static class user32
    {

    [DllImport("user32.dll", EntryPoint = "GetWindowTextA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    extern public static int GetWindowText( int hwnd, [MarshalAs(UnmanagedType.VBByRefStr)] ref string lpString, int cch);

    [DllImport("user32.dll", EntryPoint = "GetWindowTextLengthA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    extern public static int GetWindowTextLength( int hwnd); }

    File: Form1.cs
    private void Command1_Click(Object eventSender, EventArgs eventArgs)
    {
      string titleForm1 = new string(Strings.Chr(0));
      GetWindowTextLength(this.Handle.ToInt32()) + 1);
      GetWindowText(this.Handle.ToInt32(), ref titleForm1,
      titleForm1.Length);
    }
    Important Resources

    Download VBUC Free Trial
    Download  Now

    It's time to eradicate VB6
    ROI of Eradicating VB6

    8 Proven Tips for
    Planning a Successful Migration

    Learn More

    Upgrading a simple external method


    The VB6 syntax of a Declare statement is:

    [Public|Private] Declare Function globalname Lib libname$ [Alias aliasname$] [(argument list)] [As type]

    [Public|Private] Declare Sub globalname Lib libname$ [Alias aliasname$] [(argument list)]

    The following is a simple example that beeps a sound using the external method named MessageBeep in the “user32.dll” windows library. Notice additionally an invocation to that external method.

    Declare Sub MessageBeep Lib "user32" (ByVal wType As Integer)

    MessageBeep 1

    This is the corresponding upgraded code in .NET:

    C#.NET

    //[PInvokeSafeNative\user32.cs]
    namespace WinAPI.SafeNative
    {
        public static class user32
        {
          public static void MessageBeep( short wType)
          {
             WinAPI.UnsafeNative.user32.MessageBeep(wType);
          }
        }
    }
    //[PInvokeUnsafeNative\user32.cs]
    namespace WinAPI.UnsafeNative
    {
        [System.Security.SuppressUnmanagedCodeSecurity]
        public static class user32
        {
        [DllImport("user32.dll",CharSet=CharSet.Ansi, SetLastError=true,ExactSpelling= true)]
          public extern static void MessageBeep( short wType);
        }
    }

    WinAPI.SafeNative.user32.MessageBeep(1);

    Two files are generated as result of the external method declaration. The first one (UnsafeNative) containing the corresponding PInvoke method declaration of the “MessageBeep” and the second one (SafeNative) with a simple invocation to the PInvoke method.

    The source invocation would be converted pointing to the SafeNative method. In this case there is neither marshaling nor additional code because this function doesn’t need it, but in this method is where this code would be generated.


    VB.NET

    '[PInvokeSafeNative\user32.vb]
    Namespace SafeNative
      Public Module user32
        Public Sub MessageBeep(ByVal wType As Short)
          UnsafeNative.user32.MessageBeep(wType)
        End Sub
      End Module
    End NameSpace

    '[PInvokeUnsafeNative\user32.vb]
    Namespace UnsafeNative
    <System.Security.SuppressUnmanagedCodeSecurity> _
    Public Module user32
    Declare Sub MessageBeep Lib "user32" (ByVal wType As Short)
    End Module
    End NameSpace

    '…
    SafeNative.user32.MessageBeep(1)

    As C# code, VB.NET code presents two files with similar information to C# ones, changing only in the way of PInvoke declaration is written in VB, very similar to VB6.

    Structure of new PInvoke generated files

    As you can note in previous section a declaration of a VB6 external method will generate two files.

    The quantity of files will depend on the number of libraries are being used in project(s). It means if there are two external methods, one using a user32 library method and the other referencing a gdi library method, the VBUC will generate two files (user32 and gdi) in a folder inside the solution named by default PInvokeSafeNative, it will contain any additional code related to castings, marshaling, error handling, freeing memory and so on, and it will have the manage code that will reference the unmanaged code.

    The second two generated files (named user32 and gdi too) will be generated in the folder named PInvokeUnsafeNative, where will be stored the PInvoke declaration code. Look previous example by reference.

    In summary the new structure of WinAPI code will be:


    /PInvokeSafeNative/
    Lib1
    Lib2

    LibN

    /PInvokeUnsafeNative/
    Lib1
    Lib2

    LibN

    Upgrading PInvoke methods in a single project solution


    In the case where only a standalone project is being upgraded in a VBUC solution, the structure of folders explained previous section will be added to the target project. None new project will be generated.

    The following is a VB.NET resultant project:

     

     Upgrading PInvoke methods in a multiple project solution


    Differently to a standalone project, when a group of project is being upgraded in a VBUC solution, a new project will be added to the resultant solution.

    This new project (named <SolutionName>PInvoke) will contain the structure of folders with the generated Safe and Unsafe files. These files will be exactly the same as the previous section, but the only difference (transparent to the user) is that VBUC will merge all the repeated method declaration trying to simplify to the maximum the quantity of method declarations generated in the PInvoke infrastructure.

    Moreover, all projects using any PInvoke method will have a new reference to this new generated project, to be able to use these methods.

    This is a C# example of how could be generated this project and its references:

     A PInvoke method with Any arguments


    Any is a VB6 data type only used for external method declarations that indicates that could receive any type.

    This is a type that is not available in Visual Studio .NET, so that it must be converted to pointer (System.IntPtr) that contains the beginning of the memory address for each data type used in that argument.

    The following example references an external method named LoadCursor. The second parameter is declared as Any.

    Declare Function LoadCursor Lib "user32" Alias "LoadCursorA" (ByVal hInstance As Long, ByVal lpCursorName As Any) As Long

    Public Sub LoadSomeCursor()
      ' ...
      Dim inst As Long, myCursorName As String
      ' ...
      LoadCursor inst, myCursorName
    End Sub

    These are the corresponding C# and VB .NET resultant codes. You can note the declaration of that Any argument as IntPtr in C# and Integer in VB for the Unsafe methods. But the Safe methods are declared depending on the uses or invocations to the respective method. Because of that, the safe method is declared as string in this case, and are needed some castings (marshaling) to be able to send this argument as a pointer and its respective setting back.

    C#.NET


    //[PInvokeSafeMethods]
    public static int LoadCursor( int hInstance, string lpCursorName)
    {
      int result = 0;
      IntPtr tmpPtr = Marshal.StringToHGlobalAnsi(lpCursorName);
      try
      {
        result = WinAPI.UnsafeNative.user32.LoadCursor(hInstance, tmpPtr);
        lpCursorName = Marshal.PtrToStringAnsi(tmpPtr);
      }
      finally
      {
        Marshal.FreeHGlobal(tmpPtr);
      }
      return result;
    }

    //[PInvokeUnsafeMethods]
    [DllImport("user32.dll",EntryPoint = "LoadCursorA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    public extern static int LoadCursor( int hInstance, System.IntPtr lpCursorName);
    //…
    public void LoadSomeCursor()
    {
      // ...
      int inst = 0;
      string myCursorName = String.Empty;
      // ...
      WinAPI.SafeNative.user32.LoadCursor(inst, myCursorName);
    }


    VB.NET


    '[PInvokeSafeMethods]
    Public Function LoadCursor(ByVal hInstance As Integer, ByRef lpCursorName As String) As Integer
      Dim result As Integer = 0
      Dim tmpPtr As IntPtr = Marshal.StringToHGlobalAnsi(lpCursorName)
      Try
        result = WinAPISolPInvoke.UnsafeNative.user32.LoadCursor(hInstance, tmpPtr)
        lpCursorName = Marshal.PtrToStringAnsi(tmpPtr)
      Finally
       Marshal.FreeHGlobal(tmpPtr)
      End Try
      Return result
    End Function

    '[PInvokeUnsafeMethods]
    Declare Function LoadCursor Lib "user32" Alias "LoadCursorA"(ByVal hInstance As Integer, ByVal lpCursorName As Integer) As Integer
      ' ...
    Public Sub LoadSomeCursor()
      ' ...
      Dim inst As Integer
      Dim myCursorName As String = ""
      ' ...
      WinAPISolPInvoke.SafeNative.user32.LoadCursor(inst, myCursorName)
    End Sub

     

    A PInvoke method with string arguments


    The string arguments in external methods are handled almost as general case, but the main difference is that if the argument was declared byref or byval, it always would be converted as byref in the target code. It implies handling of byref parameters being sent to that method and the declaration of that argument as VBByRefStr (in C#).

    For example:

    Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" _
    (ByVal lpBuffer As String, nSize As Long) As Long

    Public Sub GetCurUserName()
      Dim lpBuf As String * 200
      GetUserName lpBuf, 200
    End Sub


    C#.NET


    //[PInvokeSafeMethods]
    public static int GetUserName(ref string lpBuffer, ref int nSize)
    {
      return WinAPI.UnsafeNative.advapi32.GetUserName(ref lpBuffer, ref nSize);
    }

    //[PInvokeUnsafeMethods]
    [DllImport("advapi32.dll", EntryPoint = "GetUserNameA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    public extern static int GetUserName([MarshalAs(UnmanagedType.VBByRefStr)] ref string lpBuffer, ref int nSize);
    //…
    public void GetCurUserName()
    {
      FixedLengthString lpBuf = new FixedLengthString(200);
      string tempRefParam = lpBuf.Value;
      int tempRefParam2 = 200;
      WinAPI.SafeNative.advapi32.GetUserName(ref tempRefParam, ref tempRefParam2);
      lpBuf.Value = tempRefParam;
    }


    VB.NET


    '[PInvokeSafeMethods]
    Public Function GetUserName(ByRef lpBuffer As String, ByRef nSize As Integer) As Integer
      Return WinAPISolPInvoke.UnsafeNative.advapi32.GetUserName(lpBuffer, nSize)
    End Function

    '[PInvokeUnsafeMethods]
    Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA"(ByVal lpBuffer As String, ByRef nSize As Integer) As Integer
    ' ...
    Public Sub GetCurUserName()
      Dim lpBuf As New FixedLengthString(200)
      WinAPISolPInvoke.SafeNative.advapi32.GetUserName(lpBuf.Value, 200)
    End Sub

    A PInvoke method with structure arguments


    Handling and passing structures to an external method is other variant in the PInvoke solution. For this case a new file is generated in the PInvokeUnsafeMethods folder, which will have the structure being passed as argument in that external method.

    It implies, additionally, that any reference to that structure has to be changed to reference this new location of the structure.

    Moreover, in multiple project solution a merge and removing of repeated structures must be done, in order to discard many duplicated structures. Sometimes there are differences between some structures that seem very similar (same name but different fields or data type fields), in this case a renaming is added for following structures and it is possible some manual revision must be done.

    Type POINTAPI
      x As Long
      y As Long
    End Type

    Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Public Sub ObtainCursorPos()
      Dim pos As POINTAPI
      GetCursorPos pos
    End Sub


    C#.NET


    //[PInvokeSafeMethods]
    public static void GetCursorPos( ref WinAPI.UnsafeNative.Structures.Pointapi lpPoint)
    {
      WinAPI.UnsafeNative.user32.GetCursorPos(lpPoint);
    }

    // [PInvokeUnsafeMethods]
    [DllImport("user32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    extern public static int GetCursorPos(ref WinAPI.UnsafeNative.Structures.Pointapi lpPoint);

    //[PInvokeUnsafeMethods\Structures]
    public static class Structures
    {
      public struct Pointapi
      {
        public int X;
        public int Y;
      }
    }


    VB.NET


    '[PInvokeSafeMethods]
    Public Sub GetCursorPos(ByRef lpPoint As WinAPISolPInvoke.UnsafeNative.Structures.Pointapi)
      WinAPISolPInvoke.UnsafeNative.user32.GetCursorPos(lpPoint)
    End Sub

    '[PInvokeUnsafeMethods]
    Declare Function GetCursorPos Lib "user32" (ByRef lpPoint As WinAPISolPInvoke.UnsafeNative.Structures.Pointapi) As Integer

    '[PInvokeUnsafeMethods\Structures]
    <System.Security.SuppressUnmanagedCodeSecurity> _
    Public Module Structures
      Public Structure Pointapi
        Dim X As Integer
        Dim Y As Integer
      End Structure
    End Module

    There is a related feature named “Add Marshalling Attributes for Structs” that must be enabled when you have external methods that use structures. This feature adds some attributes and fixes some issues related to marshaling structures that could be necessary to pass structures via PInvoke methods. This following EWI is shown when this feature could be necessary to enable.

    //UPGRADE_TODO: (1050) Structure POINTAPI may require marshalling attributes to be passed as an argument in this Declare statement. More Information: http://www.vbtonet.com/ewis/ewi1050.aspx
    [DllImport("user32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    extern public static int GetCursorPos(ref WinAPI.UnsafeNative.Structures.Pointapi lpPoint);


     A PInvoke method with string arguments


    The string arguments in external methods are handled almost as general case, but the main difference is that if the argument was declared byref or byval, it always would be converted as byref in the target code. It implies handling of byref parameters being sent to that method and the declaration of that argument as VBByRefStr (in C#).

    For example:

    Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" _
    (ByVal lpBuffer As String, nSize As Long) As Long

    Public Sub GetCurUserName()
      Dim lpBuf As String * 200
      GetUserName lpBuf, 200
    End Sub


    C#.NET


    //[PInvokeSafeMethods]
    public static int GetUserName(ref string lpBuffer, ref int nSize)
    {
      return WinAPI.UnsafeNative.advapi32.GetUserName(ref lpBuffer, ref nSize);
    }

    //[PInvokeUnsafeMethods]
    [DllImport("advapi32.dll", EntryPoint = "GetUserNameA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    public extern static int GetUserName([MarshalAs(UnmanagedType.VBByRefStr)] ref string lpBuffer, ref int nSize);
    //…
    public void GetCurUserName()
    {
      FixedLengthString lpBuf = new FixedLengthString(200);
      string tempRefParam = lpBuf.Value;
      int tempRefParam2 = 200;
      WinAPI.SafeNative.advapi32.GetUserName(ref tempRefParam, ref tempRefParam2);
      lpBuf.Value = tempRefParam;
    }


    VB.NET


    '[PInvokeSafeMethods]
    Public Function GetUserName(ByRef lpBuffer As String, ByRef nSize As Integer) As Integer
      Return WinAPISolPInvoke.UnsafeNative.advapi32.GetUserName(lpBuffer, nSize)
    End Function

    '[PInvokeUnsafeMethods]
    Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA"(ByVal lpBuffer As String, ByRef nSize As Integer) As Integer
    ' ...
    Public Sub GetCurUserName()
    Dim lpBuf As New FixedLengthString(200)
       WinAPISolPInvoke.SafeNative.advapi32.GetUserName(lpBuf.Value, 200)
    End Sub

    A PInvoke method with structure arguments

    Handling and passing structures to an external method is other variant in the PInvoke solution. For this case a new file is generated in the PInvokeUnsafeMethods folder, which will have the structure being passed as argument in that external method.

    It implies, additionally, that any reference to that structure has to be changed to reference this new location of the structure.

    Moreover, in multiple project solution a merge and removing of repeated structures must be done, in order to discard many duplicated structures. Sometimes there are differences between some structures that seem very similar (same name but different fields or data type fields), in this case a renaming is added for following structures and it is possible some manual revision must be done.

    Type POINTAPI
      x As Long
      y As Long
    End Type

    Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
    Public Sub ObtainCursorPos()
      Dim pos As POINTAPI
      GetCursorPos pos
    End Sub


    C#.NET


    //[PInvokeSafeMethods]
    public static void GetCursorPos( ref WinAPI.UnsafeNative.Structures.Pointapi lpPoint)
    {
        WinAPI.UnsafeNative.user32.GetCursorPos(lpPoint);
    }

    //[PInvokeUnsafeMethods]
    [DllImport("user32.dll", CharSet = CharSet.Ansi,SetLastError = true,ExactSpelling =true)]
    extern public static int GetCursorPos(ref WinAPI.UnsafeNative.Structures.Pointapi lpPoint);


    //[PInvokeUnsafeMethods\Structures]
    public static class Structures
    {
       public struct Pointapi
       {
        public int X;
        public int Y;
       }
    }


    VB.NET


    '[PInvokeSafeMethods]
    Public Sub GetCursorPos(ByRef lpPoint As WinAPISolPInvoke.UnsafeNative.Structures.Pointapi)
       WinAPISolPInvoke.UnsafeNative.user32.GetCursorPos(lpPoint)
    End Sub

    '[PInvokeUnSafeMethods]
    Declare Function GetCursorPos Lib "user32" (ByRef lpPoint As WinAPISolPInvoke.UnsafeNative.Structures.Pointapi) As Integer

    '[PInvokeUnsafeMethods\Structures]
    <System.Security.SuppressUnmanagedCodeSecurity> _
    Public Module Structures
       Public Structure Pointapi
        Dim X As Integer
        Dim Y As Integer
      End Structure
    End Module

    There is a related feature named “Add Marshalling Attributes for Structs” that must be enabled when you have external methods that use structures. This feature adds some attributes and fixes some issues related to marshaling structures that could be necessary to pass structures via PInvoke methods. This following EWI is shown when this feature could be necessary to enable.

    //UPGRADE_TODO: (1050) Structure POINTAPI may require marshalling attributes to be passed as an argument in this Declare statement. More Information: http://www.vbtonet.com/ewis/ewi1050.aspx
    [DllImport("user32.dll", CharSet = CharSet.Ansi,SetLastError = true,ExactSpelling =true)]
    extern public static int GetCursorPos(ref WinAPI.UnsafeNative.Structures.Pointapi lpPoint);