Chapter 13: Working with the Windows API
Chapter 13 demonstrates how you can upgrade Visual Basic 6.0 Windows API to .NET, using two different methods. The first method involves the continued use of the Windows API from within Visual Basic .NET code. When choosing this approach, you must take care to appropriately upgrade VB6 data types and VB6 fixed length strings to .NET, to ensure the proper operation of API calls. You can also upgrade the VB6 As Any variable type to .NET by declaring the same API function multiple times. The second method is to replace Windows API calls with equivalent Visual Basic.NET function calls. This approach requires more effort during the upgrade process, but it reduces an application’s dependence on an older, unmanaged API.
What is the difference between Windows API in Visual Basic 6.0 and Visual Basic .NET?
The Windows API is the foundation that every Windows application is built on. Visual Basic, through the language and forms package, abstracts the Windows API to a set of easy-to-use statements, components, and controls. VB supports directly calling Windows API functions, enabling you to add capabilities to your application that cannot be implemented by the Visual Basic runtime.
Visual Basic .NET moves forward with this capability by enabling you to declare and call Windows API functions in the same way as before. Furthermore, access to many of these APIs has been exposed through the more comprehensive .NET Framework class library.
How are VB 6.0 API function calls upgraded to .NET?
You have two options for upgrading Windows API function calls: to continue using the Windows API and use interoperability techniques to access it, or to replace these calls with Visual Basic .NET alternatives, resulting in fully managed code that is not dependent on an unmanaged library.
What are the main changes in data types between Visual Basic 6.0 and VB .NET and how is this handled by the Upgrade Wizard?
The storage sizes for the Integer and Long data types have changed between VB 6.0 and Visual Basic .NET to maintain consistency with the .NET Framework.
These changes affect almost every Windows API function declaration that involves the numeric types Integer and Long. In Visual Basic 6.0, an Integer is 16 bits and a Long is 32 bits. In Visual Basic .NET, an Integer is 32 bits and a Long is 64 bits. Visual Basic .NET adds a new type named Short, which, in terms of size, is the replacement for the Visual Basic 6.0 Integer type. In Visual Basic .NET, when you create a new Declare statement for a Windows API function, you need to take into account this difference. Any parameter type or user-defined type member that was formerly a Long needs to be declared as Integer; any member formerly declared as Integer needs to be declared as Short.
The Visual Basic Upgrade Wizard will automatically change all the variable declarations in your code to use the correct size. You need to consider the size of a type only when you are creating new Declare statements or you are modifying existing statements.
How are Visual Basic 6.0 fixed-length strings upgraded to VB .NET?
Visual Basic 6.0 has a fixed-length string data type that is not supported in Visual
Basic .NET. Any fixed-length string declarations in your code must be upgraded to a fixed-length string wrapper class that is provided in Visual Basic .NET.
The Upgrade Wizard produces code that has a dependency on the Visual Basic 6.0 compatibility runtime. To avoid this, you must prepare your code prior to the upgrade or change the resulting Visual Basic .NET code.
Is the variable type “As Any” still supported in Visual Basic .NET?
Visual Basic 6.0 allows you to declare parameter types using the As Any variable type. This declaration allows you to pass an argument of any type; Visual Basic 6.0 passes the correct information when the call is made. This gives you greater flexibility, but no type checking is performed on the argument when you call the API function. Thus, if you pass an incompatible argument type, the application may generate a run-time exception.
Visual Basic .NET does not support declaring Windows API parameters with the As Any variable type. However, a similar effect can be achieved by declaring the same API function multiple times, using different types for the same parameter in each declaration.
The Windows API function SendMessage is an example of an instance where you would use the As Any type in the API function declaration in Visual Basic 6.0 code. Depending on the Windows message you are sending, the parameter types required for the message will vary. For example, the WM_SETTEXT message requires you to pass a string as the last parameter. By contrast, the WM_GETTEXTLENGTH message requires you to pass 0, a numeric value, for the last parameter. To handle both of these messages, you create the Visual Basic 6.0 Declare statement for SendMessage. To implement the equivalent functionality in Visual Basic .NET, you create multiple Declare statements for the SendMessage function.
How does passing of user-defined types to API functions differ in .NET?
When you pass a Visual Basic 6.0 user-defined type to an API function, Visual Basic passes a pointer to the memory that contains the user-defined type. The API function sees the members of the user-defined type in the same order that they were declared in Visual Basic. However, this is not the case for Visual Basic .NET. If you declare a user-defined type, the order of the members is not guaranteed to stay the same in the code. The common language runtime (CLR) may reorganize the members of a user-defined type in a way that is most efficient for the user-defined type to be passed to a function. To guarantee that the members are passed exactly as declared in the code, you need to use marshaling attributes.
For example, if your code is calling an API function named MyFunction (written in
C or C++), that takes a 2-byte Boolean type (VARIANT_BOOL) parameter, you can use the MarshalAs attribute to specify the parameter type that the API function expects. Usually, a Boolean parameter is passed using 4 bytes, but if you include UnmanagedType.VariantBool as a parameter to the MarshalAs attribute, the parameter is marshaled — or, passed — as a 2-byte VARIANT_BOOL argument.
Please note that the MarshalAs attribute is contained in the System.Run-time.InteropServices namespace. To call MarshalAs without qualification, you need to add an Imports statement to the top of the module for System.Runtime.InteropServices.
For any structure passed to API functions, the structure should be declared using the StructLayout attribute to ensure compatibility with the Windows API. The StructLayout attribute takes a number of parameters, but the two most significant attributes for ensuring compatibility are LayoutKind and CharSet. For example, to specify that the structure members should be passed in the same order that they are declared, set the LayoutKind attribute to LayoutKind.Sequential. To ensure that string parameters are marshaled as ANSI strings, set the CharSet attribute to CharSet.Ansi.
It is important to know that the .NET Framework uses Unicode strings when it communicates with external APIs. It may be necessary to use different string marshaling options, such as ANSI encoding, or an encoding that is determined by the underlying operating system.
In general, whenever possible, use Unicode versions of the API functions and use <MarshalAs(UnmanagedType.LPWStr)> instead of <MarshalAs(UnmanagedType.LPStr)> for string arguments. Apply the same reasoning to structure fields that are necessary for API interaction. Whenever possible, use Unicode versions of structures and use <StructLayout(LayoutKind.Sequential, CharSet:= CharSet.Unicode)> instead of <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> as the marshaling attributes for structures and string fields.
What are the main changes in the “AddressOf” functionality between Visual Basic 6.0 and VB .NET?
Certain Windows API functions, such as EnumFontsFamilies, require a pointer to a callback function. When you call the API function, Windows invokes the callback function that you provided. In the case of EnumFontsFamilies, Windows will call the function for each available font.
Visual Basic 6.0 allows you to declare Windows API functions that take callback function pointers by declaring the function pointer parameter as Long, to represent a 32-bit pointer. Your code calls the API function and by qualifying the subroutine name with the AddressOf keyword, it passes the subroutine to serve as the callback function.
Visual Basic .NET still supports the AddressOf keyword, but instead of returning a 32-bit integer, it returns a delegate. A delegate is a new type in Visual Basic .NET that allows you to declare pointers to functions or to class members. This means that you can still create Declare statements for Windows API functions that take a callback function pointer as a parameter. The difference is that instead of declaring the parameter type as a 32-bit integer, you need to declare the function parameter as a delegate type.
Are the ObjPtr, StrPtr, and VarPtr functions still supported in Visual Basic .NET?
In Visual Basic 6.0, it is possible to obtain the 32-bit address of an object, a string, or a variable or user-defined value through the undocumented helper functions ObjPtr, StrPtr, and VarPtr, respectively. Visual Basic .NET does not support these functions, nor does it allow you to obtain the memory address for any type. The benefits of this restriction are significant. Leaving memory management to the CLR frees you from having to worry about allocating and freeing memory resources; this lets you concentrate on application and business logic. Also, this increases application reliability and security. Managed application memory spaces are isolated from one another. No longer can an application accidentally (or maliciously) use pointers to access another application’s memory space.
In cases where you must have control over underlying memory, the .NET Framework provides a pointer type named System.IntPtr. System.IntPtr is a platformspecific type that is used to represent a pointer or handle. This type can be used in conjunction with the System.Runtime.InteropServices.Marshal class, which contains methods for unmanaged memory operations, to obtain a pointer for a given type. For example, the functions AllocHGlobal, GetComInterfaceForObject, and OffSetOf all return pointers to memory. You can also use the GCHandle structure to obtain a handle to an element that is contained in garbage collector – managed memory. In addition, you can obtain a pointer to the memory by having the garbage collector pin the object to a single memory location to prevent it from being moved.
What are other alternatives for moving API Calls to Visual Basic .NET?
Another option when upgrading API calls from Visual Basic 6.0 to Visual Basic .NET is to use Visual Basic .NET alternatives instead of the Declare statement. An advantage of this approach is that all the Visual Basic .NET alternatives result in fully managed code even when the APIs are unmanaged. Many of the APIs have equivalents in Visual Basic .NET or the .NET Framework.
The replacement of API functions by Visual Basic .NET alternatives should be done on a case-by-case basis.
Finally, after the Visual Basic 6.0 code is converted by the upgrade wizard, you should perform several steps to eliminate the dependencies on the API calls and to replace them with Visual Basic .NET equivalents.