So how would you do the same thing in Visual Basic .NET? The syntax is a little different, but the general idea is the same:
The old VB 6 Declare facility still exists, but it now maps to the new DllImport attribute. |
|
Imports System.Runtime.InteropServices
Imports System.Text
Module Module1
<DllImport("advapi32.dll")> _
Public Function GetUserName( _
ByVal lpBuffer As StringBuilder, _
ByRef nSize As Integer) As Boolean
End Function
Sub Main()
Dim b As StringBuilder = New StringBuilder(100)
Dim n As Integer = b.Capacity
Dim rc As Boolean = GetUserName(b, n)
Console.WriteLine(b)
End Sub
End Module
As you can see, the square brackets for the
DLLImport attribute in C# have become angle brackets in Visual Basic .NET, and the static extern declaration of the function in C# has gone away. Of course, the ref modifier in C# becomes ByRef in Visual Basic .NET, and bool becomes Boolean. The semicolons between statements are gone in Visual Basic .NET, at the expense of needing underscores for line continuations, and the curly brackets to delimit blocks have been replaced by explicit
End statements.
The old VB 6
Declare facility still exists, but it now maps to the new DllImport attribute. For example, you could declare the
ANSI form of
GetUserName as:
Declare Ansi Function GetUserNameA Lib _
"advapi32.dll" (ByVal lpBuffer As _
StringBuilder, ByRef nSize As Integer) _
As Boolean
The existence of the
Declare statement isn't as useful as you might think. The parameter passing mechanism has changed from VB 6 to Visual Basic .NET: parameters formerly defaulted to
ByRef, but they now default to ByVal. Also, some of the types have changed. So, you can't blindly use the VB 6 API viewer. For example, the declaration you'll find for
GetUserName in the VB 6 API viewer is:
Declare Function GetUserName _
Lib "advapi32.dll" Alias "GetUserNameA" _
(ByVal lpBuffer As String, nSize As Long) As Long
That won't work, principally for the same reasons that my original effort in C# didn't work: the first argument must be a StringBuilder, and the second argument needs to be changed to a reference.
How P/Invoke Works
P/Invoke, as you've already seen, relies on the
DllImport attribute from the
System.Runtime.InteropServices namespace. The DllImport attribute specifies that the target method is an export from an unmanaged shared library such as the Win32 API. In the simple case you've seen here, all you need to do is declare the name of the DLL from which you're importing the call and the parameters of the call itself.
The compiler emits the
DllImport information directly into the MSIL (Microsoft Intermediate Language) executable file format. The runtime recognizes this information and uses the
Marshal class of the System. Runtime.InteropServices namespace to get the parameter and return data across the boundary between managed and unmanaged code. The runtime is actually quite clever about doing the right thing when marshaling the data back and forth between managed and unmanaged types. It copies data when it needs to, pins managed data reference types in memory so that they can be used by unmanaged code, and when needed, converts strings from ANSI to Unicode or vice-versa.
Sometimes, merely declaring a DLL name and function prototype does not suffice to describe the necessary behavior for marshalling. For such cases, the
DllImport attribute has additional fields to specify the imported function's calling convention and character set, among others. You can also use the
MarshalAs attribute to control how specific parameters are passed, and the
StructLayout attribute to define structures for marshaling. I'll discuss those in Part 2 of this article series.
What happens, though, if the API you're calling does something really weird?