Win-API to Pinvoke support

The Microsoft Windows application programming interface (Win API) allows developers to call Windows OS functions directly. All programs interact with the Windows API at some point, either directly or through some other API.

Many system resources can be managed using this API, and applications employ them to perform special functions not available from the programming language natively, such as file system access, working with the Windows registry, or interacting directly with the user interface. These functions are implemented in several DLLs that reside on the Windows\system32 folder, including kernel32.dll, advapi32.dll, user32.dll, gdi32.dll, and others.

Visual Basic 6.0 is not the exception, and it provides the “declare” keyword to call methods from external libraries. These methods are not necessarily written in VB6. This keyword is used to access functionality from dlls part of both the Win API and external components.


Download VBUC Free Trial
Download VBUC Now

It's time to eradicate VB6
ROI of eradicating VB6

8 Proven Tips for
Planning a Successful Migration

8 Tips for migration

A brief example of a declare keyword usage is:

Private Declare Sub SetWindowPos Lib "User32" (arguments…)

This statement declares a private subroutine with no return value called “SetWindowPos” in the VB6.0 class or module that belongs to the “User32” Win API library. As mentioned before, the “declare” keyword can be used to access external dlls other than those included in the Windows API.

In .NET, there are two concepts that need to be acknowledged to better understand external dll references. These are the concepts of managed and unmanaged code, which explain the main differences regarding the external dll use. The following paragraphs explain these differences:

  • Managed Code: The .Net Framework provides a way to build and execute applications. It is made up of two parts, the Common Language Runtime (CLR), which handles run-time services for applications, and a unified set of class libraries that provide standard, reusable functionality. Managed applications are compiled to a  common representation called Microsoft Intermediate Language (MSIL or IL). The compiled units of .NET code – both executables (.exe) and library (.dll) files - are called assemblies. When an IL assembly is executed, it is translated to machine code using a Just-in-Time (JIT) compiler. Since the execution of the code in all assemblies is managed by the CLR, it is called Managed Code.
  • Unmanaged (native) Code: Unmanaged code is compiled directly to a binary that can be executed directly into the CPU. It is a binary image with x86 code that can be loaded into memory and executed. Form a .NET perspective, unmanaged code is everything not compiled into MSIL that either calls or is called by code managed by the CLR. For example, VB6 ActiveX dlls and Windows API dlls are considered unmanaged code for these very reasons.

The .NET platform introduced the Platform Invocation Services also known as “Pinvoke” to interact with unmanaged code from a managed environment. As for the code itself,.NET declarations for calls to external dlls are not that different from VB6 declarations. There is, however, an important difference – the way the input and output parameters are passed between both types of code require and special transformations known as marshalling.

Marshalling transforms the data type of input and output parameters from unmanaged code to .NET’s Common Type System, so they can be used as managed data types. This allows the CLR to handle unmanaged parameters in memory as if they were managed.

The .NET Pinvoke functionality often use composite data types, known as structures or structs, to transmit parameters to and from unmanaged code. Struct types require marshalling to enforce memory-safe execution on the managed side.

Starting with version 3.0, the VBUC is able to automatically upgrade most Windows API calls from the “declare” keyword to the correct P/invoke counterpart. For every external *.dll call, the VBUC generates the required interoperability data-type marshalling to allow a smooth interaction between.NET and unmanaged code. The most changes in this feature are:

  • Adds specific interoperability attributes to the signatures of upgraded API calls
  • Adds error handling for the upgraded API calls.
  • Generates the correct data types for pointer-type parameters
  • Modifies structure generation so the marshalling of structs to Windows API calls and the copying of structures in migrated .NET applications is completely automated.

The following code sample demonstrates how the VBUC upgrades Windows API calls to their corresponding Pinvoke signatures:

Resulting VB.NET Code:

Private Declare Sub SetWindowPos Lib "User32" (ByVal hWnd As Integer, ByVal hWndInsertAfter As Integer, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer)
 
Private Declare Function GetWindowLong Lib "User32"  Alias "GetWindowLongA"(ByVal hWnd As Integer, ByVal nIndex As Integer) As Integer
 
Private Declare Function SetWindowLong Lib "User32"  Alias "SetWindowLongA"(ByVal hWnd As Integer, ByVal nIndex As Integer, ByVal dwNewLong As Integer) As Integer

Original VB6 Code:

Private Declare Sub SetWindowPos Lib "User32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long)
 
Private Declare Function GetWindowLong Lib "User32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
 
Private Declare Function SetWindowLong Lib "User32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Resulting C#.NET Code:

[DllImport("User32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
public extern static void  SetWindowPos( int hWnd,  int hWndInsertAfter,  int X,  int Y,  int cx,  int cy,  int wFlags);
 
[DllImport("User32.dll", EntryPoint = "GetWindowLongA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
 public extern static int GetWindowLong( int hWnd,  int nIndex);
 
[DllImport("User32.dll", EntryPoint = "SetWindowLongA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
public extern static int SetWindowLong( int hWnd,  int nIndex,  int dwNewLong);

Note that the C# code features marshalling data (between brackets) used to interpret the input and output data from the windows API calls. Also note that VB.NET code doesn’t have this information, since is implicit for this language.