Logo
programming4us
programming4us
programming4us
programming4us
Home
programming4us
XP
programming4us
Windows Vista
programming4us
Windows 7
programming4us
Windows Azure
programming4us
Windows Server
programming4us
Windows Phone
 
programming4us
Windows 7

Visual Basic 2010 : Platform Invokes and Interoperability with the COM Architecture - P/Invokes and Unmanaged Code

- Free product key for windows 10
- Free Product Key for Microsoft office 365
- Malwarebytes Premium 3.7.1 Serial Keys (LifeTime) 2019
8/2/2011 3:43:21 PM
One of the biggest benefits of the .NET Framework is that the technology is a bridge between you and the Windows operating system and is responsible for managing a lot of system features (such as memory management) highly reducing the risk of bad system resources management that could lead the system to unwanted crashes or problems. This is the reason why .NET programming is also known as managed. The .NET Framework base class library exposes managed wrappers for most of the Windows API system so that you do not need to manually handle system resources, and you can take all advantages from the CLR. By the way, there are situations in which you still need to access the Windows API (for example when there is not a .NET counterpart of an API function), and thus you need to work with unmanaged code. Basically unmanaged code is all code not controlled by the .NET Framework and that requires you to manually handle system resources. When you work with unmanaged code, you commonly invoke Windows API functions; such invocations are also known as Platform Invokes or, simpler, P/Invokes. In this section I cover both situations, starting with P/Invokes.

Note on Unmanaged Code

You should always avoid unmanaged code. The .NET Framework 4.0 offers an infinite number of managed objects and methods for performing almost everything, and if something from the Windows API has not been wrapped yet, you can find lots of open-source or free third-party libraries to help you solve your problems without P/Invokes. Using unmanaged code means working directly against the operating system and its resources, and if your code does not perfectly handle resources, it can lead to hard problems. Moreover, when performing unmanaged calls you need to be certain that they work or exist on all versions of the Windows operating system you plan to support for your application. In a few words, always search through the Base Class Library to ensure that a .NET counterpart for the Windows API already exists. It probably does.


Understanding P/Invokes

Calls to Windows API functions are known as Platform Invokes or P/Invokes. The Visual Basic programming language offers two ways for performing platform invokes:

  • Declare keyword

  • System.Runtime.InteropServices.DllImport attribute

The Declare keyword has a behavior similar to what happened in Visual Basic 6, and it has been kept for compatibility, but you should always prefer the DllImportPathIsUrl function, from the Shlwapi.dll system library, which checks if the specified is an URL and returns a value according to the result. This is with the Declare attribute because this is the one way recognized by the Common Language Specification. Now we can see how to declare a P/Invoke. The next example considers the keyword:

Declare Function PathIsUrl Lib "shlwapi.dll" Alias _
"PathIsURLA" (ByVal path As String) As Integer

Matching Numeric Types

Keep in mind the difference in numeric types between the Windows API system and the .NET common types system, because generally Windows APIs return Long; however when you perform P/Invokes you must use the .NET counterpart that is Integer. The same is for Integer in the Windows API, which is mapped by Short in .NET. Similarly, remember to use the IntPtr structure for declarations that require a handle (or a pointer) of type Integer.


As you can see, the API declaration looks similar to what you used to write in VB 6. The following is instead how you declare the API function via the DllImport attribute:

'Requires an
'Imports System.Runtime.InteropServices directive
<DllImport("shlwapi.dll", entrypoint:="PathIsURLA")>
Shared Function PathIsURL(ByVal path As String) As System.Int32
End Function

Among its number of options, the most important in DllImport are the library name and the entrypoint parameter that simply indicates the function name. It is important to remember that P/Invokes must be declared as Shared, because they cannot be exposed as instance methods; the only exception to this rule is when you declare a function within a module. When declared, you can consume P/Invokes like any other method (always remembering that you are not passing through the CLR) as demonstrated here:

Dim testUrl As String = "http://www.visual-basic.it"
Dim result As Integer = PathIsURL(testUrl)

Both Declare and DllImport lead to the same result, but from now we use only DllImport.

Encapsulating P/Invokes

Encapsulating P/Invokes in classes is a programming best practice and makes your code clearer and more meaningful. Continuing the previous example, you could create a new class and declare inside the class the PathIsUrl function, marking it as Shared so that it can be consumed by other objects. By the way, there is another consideration to make. If you plan to wrap Windows API functions in reusable class libraries, the best approach is to provide CLS-compliant libraries and API calls. For this reason we now discuss how you can encapsulate P/Invokes following the rules of the Common Language Specification. The first rule is to create a class that stores only P/Invokes declarations. Such a class must be visible only within the assembly, must implement a private empty constructor, and will expose only shared members. The following is an example related to the PathIsUri function:

Friend Class NativeMethods
<DllImport("shlwapi.dll", entrypoint:="PathIsURLA")>
Shared Function PathIsURL(ByVal path As String) As System.Int32
End Function

Private Sub New()

End Sub
End Class

The class is marked with Friend to make it visible only within the assembly. Notice that a CLS-compliant class for exposing P/Invokes declarations can have only one of the following names:

  • NativeMethods, which is used on the development machine and indicates that the class has no particular security and permissions requirements

  • SafeNativeMethods, which is used outside the development machine and indicates that the class and methods have no particular security and permissions requirements

  • UnsafeNativeMethods, which is used to explain to other developers that the caller needs to demand permissions to execute the code (demanding permissions for one of the classes exposed by the System.Security.Permissions namespace)

To expose P/Invokes to the external call, you need a wrapper class. The following class demonstrates how you can expose the NativeMethods.PathIsUrl function in a programmatically correct approach:

Public Class UsefulMethods

Public Shared Function CheckIfPathIsUrl(ByVal path As String) _
As Integer
Return NativeMethods.PathIsURL(path)
End Function

End Class

Finally, you can consume the preceding code as follows (for example adding a reference to the class library):

Dim testUrl As String = "http://www.visual-basic.it"
Dim result As Integer = UsefulMethods.CheckIfPathIsUrl(testUrl)

Working with unmanaged code is not only performing P/Invokes. There are some other important concepts about error handling and type marshaling, as explained in next sections.

Converting Types to Unmanaged

When you work with P/Invokes, you might have the need to pass custom types as function arguments. If such types are .NET types, the most important thing is converting primitives into types that are acceptable by the COM/Win32 architecture. The System.Runtime.InteropServices namespace exposes the MarshalAs attribute that can be applied to fields and method arguments to convert the object into the most appropriate COM counterpart. The following sample implementation of the Person class demonstrates how to apply MarshalAs:

Imports System.Runtime.InteropServices


Public Class Person

<MarshalAs(UnmanagedType.LPStr)>
Private _firstName As String
<MarshalAs(UnmanagedType.SysInt)>
Private _age As Integer

Public Property FirstName As String
Get
Return _firstName
End Get
Set(ByVal value As String)
_firstName = value
End Set
End Property

Public Property Age As Integer
Get
Return _age
End Get
Set(ByVal value As Integer)
_age = value
End Set
End Property
Sub ConvertParameter(<MarshalAs(UnmanagedType.LPStr)> _
ByVal name As String)
End Sub
End Class


The attribute receives a value from the UnmanagedType enumeration; IntelliSense offers great help about members in this enumeration, showing the full members list and explaining what each member is bound to convert. You can check this out as an exercise.

The StructLayout Attribute

An important aspect of unmanaged programming is how you handle types, especially when such types are passed as P/Invoke arguments. Differently from P/Invokes, types representing counterparts from the Windows API pass through the Common Language Runtime and, as a general rule, you should provide the CLR the best way for handling them to keep performance high. Basically when you write a class or a structure, you give members a particular order that should have a meaning for you. In other words, if the Person class exposes FirstName and Age as properties, keeping this order should have a reason, which generally is dictated only by some kind of logic. With the System.Runtime.InteropServices.StructLayout attribute, you can tell the CLR how it can handle type members; it enables deciding if it has to respect a particular order or if it can handle type members the best way it can according to performances. The StructLayout attribute’s constructor offers three alternatives:

  • StructLayout.Auto: The CLR handles type members in its preferred order.

  • StructLayout.Sequential: The CLR handles type members preserving the order provided by the developer in the type implementation.

  • StructLayout.Explicit: The CLR handles type members according to the order established by the developer, using memory offsets.

By default, if StructLayout is not specified, the CLR assumes Auto for reference types and Sequential for structures. For example, consider the COMRECT structure from the Windows API, which represents four points. This is how you write it in Visual Basic, making it available to unmanaged code:

<StructLayout(LayoutKind.Sequential)>
Public Structure COMRECT

Public Left As Integer
Public Top As Integer
Public Right As Integer
Public Bottom As Integer

Shared Sub New()

End Sub
Public Sub New(ByVal left As Integer,
ByVal top As Integer,
ByVal right As Integer,
ByVal bottom As Integer)
Me.Left = left
Me.Top = top
Me.Right = right
Me.Bottom = bottom
End Sub
End Structure

Tips on Default Options

StructLayout must be applied explicitly if your assembly needs to be CLS-compliant. This happens because you have two choices, Sequential and Explicit. Instead, for classes this is not necessary, because they are always considered as Auto. Because of this, in this section we describe only structures.


This is how instead you can apply StructLayout.Explicit, providing memory offsets:

<StructLayout(LayoutKind.Explicit)>
Public Structure COMRECT

<FieldOffset(0)> Public Left As Integer
<FieldOffset(4)> Public Top As Integer
<FieldOffset(8)> Public Right As Integer
<FieldOffset(12)> Public Bottom As Integer

Shared Sub New()

End Sub

Public Sub New(ByVal left As Integer,
ByVal top As Integer,
ByVal right As Integer,
ByVal bottom As Integer)

Me.Left = left
Me.Top = top
Me.Right = right
Me.Bottom = bottom
End Sub
End Structure

The FieldOffset attribute specifies the memory offset for each field. In this case the structure provides fields of type Integer, so each offset is four bytes.

The VBFixedString attribute

The VBFixedString attribute can be applied to structure members of type String, in order to delimit the string length, since by default string length is variable. Such delimitation is established in bytes instead of characters. This attribute is required in some API calls. The following is an example:

Public Structure Contact
'Both fields are limited to 10 bytes size
<VBFixedString(10)> Public LastName As String
<VBFixedString(10)> Public Email As String
End Structure

Notice that the VBFixedString can be applied to fields but is not valid for properties.

Handling Exceptions

Functions from Windows API generally return a numeric value as their result (called HRESULT), for communicating with the caller if the function succeeded or failed. Prior to .NET 2.0, getting information on functions failures was a difficult task. Starting from .NET 2.0 you can handle exceptions coming from the P/Invokes world with a classic Try..Catch block. The real improvement is that the .NET Framework can wrap unmanaged errors that have a .NET counterpart into managed exceptions. For example, if a Windows API invocation causes an out-of-memory error, the .NET Framework maps such error as an OutOfMemoryException that you can embrace within a normal Try..Catch block. By the way, it is reasonable that not all unmanaged errors can have a managed counterpart, due to differences in COM and .NET architectures. To solve this, .NET provides the System.Runtime.InteropServices.SEHException, in which SEH stands for Structured Exception Handling and that maps all unmanaged exceptions that .NET cannot map. The exception is useful because it exposes an ErrorCode property that stores the HRESULT sent from P/Invokes. You use it like this:

Try
'Add your P/Invoke here..
Catch ex As SEHException
Console.WriteLine(ex.ErrorCode.ToString)
Catch ex As Exception

End Try

Tip

The SEHException does not provide a good number of exception details, differently from managed exceptions, but it is the most appropriate exception for error handling in a Try..Catch block within unmanaged code.


There is also an alternative, which requires some explanation. P/Invokes raise Win32 errors calling themselves the SetLastError native method that is different from how exceptions are thrown in the Common Language Runtime. In earlier days you could call the GetLastError method to retrieve the error code, but this is not the best choice because it can refer to managed exceptions, other than Win32 exceptions. A better, although not the ultimate, approach can be provided by invoking the System.Runtime.InteropServices.Marshal.GetLastWin32Error method, which can intercept the last error coming from a Win32 call. To make this work, first you need to set the SetLastError property in the DllImport attribute as True; then you can invoke the method. The following code shows an example on the Beep function, which returns a numeric value as the result:

<DllImport("kernel32.dll", entrypoint:="Beep", SetLastError:=True)>
Public Shared Function Beep(ByVal frequency As UInteger,
ByVal duration As UInteger) As Integer
End Function

Dim beepResult = NativeMethods.Beep(100, 100)
If beepResult = 0 Then
Console.WriteLine(Marshal.GetLastWin32Error())
End If

Here you need to know first what values can return a particular function. Beep returns zero if it does not succeed. So after a check on the result value, the Marshal.GetLastWin32Error method is invoked to understand the error code.

Other -----------------
- Microsoft Visio 2010 : Changing Shape Data Attributes
- Microsoft Visio 2010 : Viewing Shape Data Attributes
- Microsoft Excel 2010 : Building More Powerful Worksheets - Summarizing Data Using Subtotals & Summarizing Data Using Functions
- Microsoft Excel 2010 : Building More Powerful Worksheets - Using Lookup and Reference Functions & Using Text Functions
- Microsoft Excel 2010 : Building More Powerful Worksheets - Using Solver
- Microsoft Visio 2010 : Editing Shape Data
- Microsoft Visio 2010 : Viewing Shape Data
- Managing Disks : Understanding RAID & Using Disk Maintenance Tools
- Managing Disks : Managing Data Encryption with EFS
- Microsoft Excel 2010 : Building More Powerful Worksheets - Creating Scenarios
 
 
Top 10
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
 
programming4us
Windows Vista
programming4us
Windows 7
programming4us
Windows Azure
programming4us
Windows Server