Lesson 2: Using Unmanaged Code

Lesson 2: Using Unmanaged Code

Microsoft ASP.NET Web applications run under the control of the common language runtime (CLR). The CLR controls how the application s assembly executes, allocates, and recovers memory; therefore, ASP.NET applications are said to use managed code. In contrast, most other Windows executables use unmanaged code because the executable itself determines how memory is used.

Examples of unmanaged code include the Microsoft Win32 API, legacy DLLs and EXEs created for Windows applications prior to the Microsoft .NET Framework, and COM objects. In this lesson, you ll learn how to declare and call unmanaged code at the procedure level from a .NET assembly. For information about calling COM objects from .NET, see Lesson 3.

After this lesson, you will be able to

  • Declare an unmanaged procedure for use in a .NET assembly

  • Call an unmanaged procedure with scalar parameter types

  • Convert structure and object parameters between.NET and unmanaged types

  • Catch and handle errors from unmanaged code within a .NET assembly

  • Understand the limitations of using unmanaged code within a .NET assembly

Estimated lesson time: 30 minutes

Using Platform Invoke

The process of executing native code from within a .NET assembly is called platform invoke, or pinvoke for short. You use platform invoke to call the Win32 API directly, to access existing (legacy) DLLs your company uses, or to access procedures compiled to native code for performance reasons. To use platform invoke, follow these steps:

  1. Import the System.Runtime.InteropServices namespace.

  2. Declare the unmanaged procedure using the DllImport attribute or the Declare statement.

  3. Map the data types of the procedures parameters to the equivalent .NET types.

  4. Call the unmanaged procedure and test its return value for success.

  5. If the procedure did not succeed, retrieve and handle the exception code using the Marshal object s GetLastWin32Error method.

For example, the following code declares the Win32 API s GetSystemInfo procedure and then calls the unmanaged procedure to display information about the server s processor on a Web form:

Visual Basic .NET

' (1) Import InteropServices namespace. Imports System.Runtime.InteropServices Public Class Win32API Inherits System.Web.UI.Page ' (2) Use a Declare statement to identify an unmanaged procedure to call. Declare Auto Sub GetSystemInfo Lib "kernel32.dll" (ByRef Info As SYSTEM_INFO) ' (3) Define Structs or other types for unmanaged procedure parameters. Structure SYSTEM_INFO Dim ProcessorArchitecture As Int16 Dim Reserved As Int16 Dim PageSize As Int32 Dim MinAppAddress As Int32 Dim MaxAppAddress As Int32 Dim ActiveProcMask As Int32 Dim NumberOfProcessors As Int32 Dim ProcessorType As Int32 Dim AllocGranularity As Int32 Dim ProcessorLevel As Int32 Dim ProcessorRevision As Int32 End Structure Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Declare the parameter to pass in. Dim CurrentSystem As SYSTEM_INFO ' (4) Call the unmanaged procedure. GetSystemInfo(CurrentSystem) ' Display results. litSystemInfo.Text = "Number of processors: " + _ CurrentSystem.NumberOfProcessors.ToString() + "<br>" litSystemInfo.Text += "Type of processor: " + _ CurrentSystem.ProcessorType.ToString() + "<br>" litSystemInfo.Text += "Page size: " + _ CurrentSystem.PageSize.ToString() + "<br>" End Sub End Class

Visual C#

// (1) Use InteropServices namespace. using System.Runtime.InteropServices; public class Win32API : System.Web.UI.Page { protected System.Web.UI.WebControls.Literal litSystemInfo; // (2) Identify an unmanaged procedure to call. [DllImport("KERNEL32.DLL", EntryPoint="GetSystemInfo", SetLastError=true,CharSet=CharSet.Unicode, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern void GetSystemInfo(ref SYSTEM_INFO Info); // (3) Define types for unmanaged procedure parameters. [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_INFO { public Int16 ProcessorArchitecture ; public Int16 Reserved ; public Int32 PageSize ; public Int32 MinAppAddress; public Int32 MaxAppAddress ; public Int32 ActiveProcMask ; public Int32 NumberOfProcessors ; public Int32 ProcessorType ; public Int32 AllocGranularity ; public Int32 ProcessorLevel ; public Int32 ProcessorRevision ; } private void Page_Load(object sender, System.EventArgs e) { // Declare the parameter to pass in. SYSTEM_INFO CurrentSystem; // (4) Call unmanaged procedure. GetSystemInfo(ref CurrentSystem); litSystemInfo.Text = "Number of processors: " + CurrentSystem.NumberOfProcessors.ToString() + "<br>"; litSystemInfo.Text += "Type of processor: " + CurrentSystem.ProcessorType.ToString() + "<br>"; litSystemInfo.Text += "Page size: " + CurrentSystem.PageSize.ToString() + "<br>"; } }

In the preceding code, the Visual Basic .NET sample uses the Declare statement instead of the DllImport attribute. The Declare statement provides a simplified syntax for declaring unmanaged procedures, and it is converted to the following equivalent DllImport attribute code when compiled:

Visual Basic .NET

<DllImport("KERNEL32.DLL", EntryPoint:="GetSystemInfo", SetLastError:=True, _ CharSet:=CharSet.Unicode, ExactSpelling:=True, _ CallingConvention:=CallingConvention.StdCall)> _ Public Shared Sub GetSystemInfo(ByRef Info As SYSTEM_INFO) ' Leave procedure body empty End Sub

You can use either form in your Visual Basic .NET code. Microsoft Visual C# supports only the DllImport form.

Converting Data Types

The .NET Framework uses a unified type system that is different from the types defined in the Win32 API. When you call an unmanaged procedure from a .NET assembly, the CLR collects the parameters and converts their types in a process called marshaling.

Scalar numeric types map between the two type systems based on their size in memory. So, for instance, a dword maps to an Int32, a word maps to an Int16, and so on. String data types in .NET are the equivalent of BSTRS in Win32. Strings are automatically converted between Unicode and ANSI formats if the DllImport attribute s CharSet field is set to CharSet.Ansi.

Structures in .NET are defined in much the same way that they are for the Win32 API. By default, .NET structures are arranged sequentially in memory in the order in which they are defined within the structure declaration. You can also explicitly define how structures are ordered in memory using the StructLayout attribute. The following code shows two equivalent declarations of the SYSTEM_INFO structure using sequential and explicit layouts:

Visual Basic .NET

' Sequential (default) layout <StructLayout(LayoutKind.Sequential)> Structure SYSTEM_INFO Dim ProcessorArchitecture As Int16 Dim Reserved As Int16 Dim PageSize As Int32 Dim MinAppAddress As Int32 Dim MaxAppAddress As Int32 Dim ActiveProcMask As Int32 Dim NumberOfProcessors As Int32 Dim ProcessorType As Int32 Dim AllocGranularity As Int32 Dim ProcessorLevel As Int32 Dim ProcessorRevision As Int32 End Structure ' Equivalent explicit layout <StructLayout(LayoutKind.Explicit)> Structure SYSTEM_INFO <FieldOffset(0)> Dim ProcessorArchitecture As Int16 <FieldOffset(2)> Dim Reserved As Int16 <FieldOffset(4)> Dim PageSize As Int32 <FieldOffset(8)> Dim MinAppAddress As Int32 <FieldOffset(12)> Dim MaxAppAddress As Int32 <FieldOffset(16)> Dim ActiveProcMask As Int32 <FieldOffset(20)> Dim NumberOfProcessors As Int32 <FieldOffset(24)> Dim ProcessorType As Int32 <FieldOffset(28)> Dim AllocGranularity As Int32 <FieldOffset(32)> Dim ProcessorLevel As Int32 <FieldOffset(36)> Dim ProcessorRevision As Int32 End Structure

Visual C#

// Sequential (default) layout [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_INFO { public Int16 ProcessorArchitecture ; public Int16 Reserved ; public Int32 PageSize ; public Int32 MinAppAddress; public Int32 MaxAppAddress ; public Int32 ActiveProcMask ; public Int32 NumberOfProcessors ; public Int32 ProcessorType ; public Int32 AllocGranularity ; public Int32 ProcessorLevel ; public Int32 ProcessorRevision ; } // Equivalent explicit layout [StructLayout(LayoutKind.Explicit)] public struct SYSTEM_INFO { [FieldOffset(0)]public Int16 ProcessorArchitecture ; [FieldOffset(2)]public Int16 Reserved ; [FieldOffset(4)]public Int32 PageSize ; [FieldOffset(8)]public Int32 MinAppAddress; [FieldOffset(12)]public Int32 MaxAppAddress ; [FieldOffset(16)]public Int32 ActiveProcMask ; [FieldOffset(20)]public Int32 NumberOfProcessors ; [FieldOffset(24)]public Int32 ProcessorType ; [FieldOffset(28)]public Int32 AllocGranularity ; [FieldOffset(32)]public Int32 ProcessorLevel ; [FieldOffset(36)]public Int32 ProcessorRevision ; }

Declaring a layout is required when you re passing objects to unmanaged code, because objects might be moved around in memory after they are created. The preceding code passed the SYSTEM_INFO structure by reference. Passing a structure by reference is equivalent to passing an object by value. The following code performs the same task, but passes an object by value rather than a structure by reference:

Visual Basic .NET

Public Class Win32API Inherits System.Web.UI.Page ' Pass an object by value. Declare Auto Sub GetSystemInfo Lib "kernel32.dll" _ (ByVal Info As SYSTEM_INFO) Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Create the object parameter to pass in. Dim CurrentSystem As New SYSTEM_INFO ' Call the unmanaged procedure with a parameter by value. GetSystemInfo(CurrentSystem) ' Display results. litSystemInfo.Text = "Number of processors: " + _ CurrentSystem.NumberOfProcessors.ToString() + "<br>" litSystemInfo.Text += "Type of processor: " + _ CurrentSystem.ProcessorType.ToString() + "<br>" litSystemInfo.Text += "Page size: " + _ CurrentSystem.PageSize.ToString() + "<br>" End Sub End Class ' Define a class with a sequential layout. <StructLayout(LayoutKind.Sequential)> Public Class SYSTEM_INFO Dim ProcessorArchitecture As Int16 Dim Reserved As Int16 Public PageSize As Int32 Dim MinAppAddress As Int32 Dim MaxAppAddress As Int32 Dim ActiveProcMask As Int32 Public NumberOfProcessors As Int32 Public ProcessorType As Int32 Dim AllocGranularity As Int32 Dim ProcessorLevel As Int32 Dim ProcessorRevision As Int32 End Class

Visual C#

public class Win32API : System.Web.UI.Page { protected System.Web.UI.WebControls.Literal litSystemInfo; // Pass an object by value. [DllImport("KERNEL32.DLL", EntryPoint="GetSystemInfo", SetLastError=true,CharSet=CharSet.Unicode, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern void GetSystemInfo(SYSTEM_INFO Info); private void Page_Load(object sender, System.EventArgs e) { // Create the object to pass in. SYSTEM_INFO CurrentSystem = new SYSTEM_INFO(); // Call an unmanaged procedure with a parameter by value. GetSystemInfo(CurrentSystem); litSystemInfo.Text = "Number of processors: " + CurrentSystem.NumberOfProcessors.ToString() + "<br>"; litSystemInfo.Text += "Type of processor: " + CurrentSystem.ProcessorType.ToString() + "<br>"; litSystemInfo.Text += "Page size: " + CurrentSystem.PageSize.ToString() + "<br>"; } } // Define a class with a sequential layout. [StructLayout(LayoutKind.Sequential)] public class SYSTEM_INFO { public Int16 ProcessorArchitecture ; public Int16 Reserved ; public Int32 PageSize ; public Int32 MinAppAddress; public Int32 MaxAppAddress ; public Int32 ActiveProcMask ; public Int32 NumberOfProcessors ; public Int32 ProcessorType ; public Int32 AllocGranularity ; public Int32 ProcessorLevel ; public Int32 ProcessorRevision ; }

This section covered passing only the most common types of parameters to unmanaged procedures. An exhaustive list of the different possible types along with samples of how to pass them can be found in the Visual Studio .NET Help topic Marshaling Data with Platform Invoke.

Handling Exceptions from Unmanaged Procedures

Unmanaged procedures typically return a value that indicates whether an exception occurred during their execution. Nonzero return values usually indicate success, and a zero return value usually indicates that an exception occurred. To handle exceptions from unmanaged code, follow these steps:

  1. Declare the unmanaged procedure with the SetLastError field set to True/true. This is the default used by the Visual Basic .NET Declare statement.

  2. Check the returned value from the unmanaged procedure.

  3. If the procedure returned 0, get the unmanaged exception code using the Marshal object s GetLastWin32Error method.

  4. Compare the exception code to a list of possible values.

For example, the following code uses the Win32 API MoveFile method and displays an appropriate error message for some of the possible exceptions:

Visual Basic .NET

' Declare an unmanaged procedure. Declare Auto Function MoveFile Lib "kernel32.dll" (ByVal src As String, _ ByVal dst As String) As Boolean Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Call MoveFile on a read-only file to cause an error. If MoveFile(Server.MapPath(".\") + "protected.txt", _  "newfile.txt") = True Then litStatus.Text = "File moved." Else ' MoveTo returns False if there is an error. Dim msg As String ' Set a message depending on error code returned. Select Case Marshal.GetLastWin32Error Case 2 msg = "File not found." Case 3 msg = "Path not found." Case 5 msg = "Access denied." Case 15 msg = "Drive not found." Case Else msg = "Unlisted error." End Select ' Display error. litStatus.Text = "The following error occurred: <br>" + msg End If End Sub

Visual C#

[DllImport("KERNEL32.DLL", EntryPoint="MoveFile", SetLastError=true,CharSet=CharSet.Unicode, ExactSpelling=false, CallingConvention=CallingConvention.StdCall)] public static extern bool MoveFile(string src, string dest); private void Page_Load(object sender, System.EventArgs e) { // Call MoveFile on a read-only file to cause an error. if (MoveFile(Server.MapPath(".\\") + "protected.txt", "newfile.txt")) { litStatus.Text = "File moved."; } else { // MoveTo returns False if there is an error. string msg ; // Set a message depending on error code returned. switch (Marshal.GetLastWin32Error()) { case 2: msg = "File not found."; break; case 3: msg = "Path not found."; break; case 5: msg = "Access denied."; break; case 15: msg = "Drive not found."; break; default: msg = "Unlisted error."; break; } // Display error. litStatus.Text = "The following error occurred: <br>" + msg; } }

Limitations of Unmanaged Code

The .NET Framework adds many features that are either not available or implemented differently in unmanaged procedures. You should be aware of the following limitations whenever you start using unmanaged code from within a .NET assembly:

  • Performance

    Although native-code DLLs can perform some operations more quickly than equivalent code managed by the CLR, these benefits might be offset by the time it takes to marshal the data to pass between the unmanaged procedure and the .NET assembly.

  • Type safety

    Unlike .NET assemblies, unmanaged procedures might not be type-safe. This can affect the reliability of your .NET application. In general, reliability is a paramount concern with ASP.NET Web applications.

  • Code security

    Unmanaged procedures do not use the .NET Framework s model for code security.

  • Versioning

    Unmanaged code does not support .NET versioning; therefore, assemblies that call unmanaged procedures might lose the benefit of being able to coexist with other versions of the same assembly.



MCAD(s)MCSD Self-Paced Training Kit(c) Developing Web Applications With Microsoft Visual Basic. Net and Microsoft V[.  .. ]0-315
MCAD(s)MCSD Self-Paced Training Kit(c) Developing Web Applications With Microsoft Visual Basic. Net and Microsoft V[. .. ]0-315
ISBN: N/A
EAN: N/A
Year: 2003
Pages: 118

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net