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.|
Public Function GetUserName( _
ByVal lpBuffer As StringBuilder, _
ByRef nSize As Integer) As Boolean
Dim b As StringBuilder = New StringBuilder(100)
Dim n As Integer = b.Capacity
Dim rc As Boolean = GetUserName(b, n)
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
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
Declare Ansi Function GetUserNameA Lib _
"advapi32.dll" (ByVal lpBuffer As _
StringBuilder, ByRef nSize As Integer) _
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
, 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?