|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
This article has been provided courtesy of MSDN. SummaryLearn how to use the Platform Invoke (P/Invoke) feature of the .NET Compact Framework. ContentsIntroductionIn order to realize the vision of access to information anywhere, any time, and on any device, developers need robust and feature-rich tools. With the release of the Microsoft .NET Compact Framework and Smart Device Projects in Visual Studio .NET 2003, Microsoft has made it easier for developers to extend their applications to smart devices by leveraging their existing knowledge of the Microsoft .NET Framework. The .NET Compact Framework targets Pocket PC 2000, 2002, and embedded Microsoft® Windows® CE .NET 4.1 devices, and has been developed as a subset of the desktop Framework. The Compact Framework, coupled with the Smart Device Project support in Visual Studio .NET 2003, allows developers to write managed code in Visual Basic or C# that is executed in a common language runtime analogous to that in the full .NET Framework. However, as a subset of the .NET Framework, the .NET Compact Framework supports approximately 25% of the types across the entire range of namespaces, in addition to several .NET Compact Framework-specific types for dealing with user input, messaging, and Microsoft® SQL™ Server 2000 Windows CE Edition 2.0. For developers, this means that there will be some functionality that you can only access by dropping down to the operating system (Windows CE) APIs. For example, the notification services included in the Pocket PC 2002 platform have no managed equivalents in the .NET Compact Framework. In addition, there are a variety of third-party and custom DLLs that you may require access to: for example, the APIs to make phone calls and retrieve the call log using a Pocket PC 2002 Phone Edition device. Luckily for you, the .NET Compact Framework (like its desktop cousin) does support the Platform Invoke (P/Invoke) service. This service allows managed code to invoke unmanaged functions residing in DLLs. Although the .NET Compact Framework supports P/Invoke, it does so a little differently than the full .NET Framework. In this whitepaper and the one to follow, we'll explore using P/Invoke in the Compact Framework, with a focus on the similarities and differences you'll encounter. This paper assumes that you're familiar at a basic level with the full .NET Framework. Using P/InvokeJust as in the .NET Framework, using the P/Invoke service in the .NET Compact Framework includes three primary steps: declaration, invocation, and error handling. After describing these steps, we'll take a look at the differences between the P/Invoke service in the .NET Compact Framework and the full .NET Framework. DeclarationTo begin, you must, at design time, tell the .NET Compact Framework which unmanaged function you intend to call. You'll need to include the DLL name (also called the module), the function name (also called the entry point) and the calling convention to use. For example, in order to call the [VB]Private Declare Function SHGetSpecialFolderPath Lib "coredll.dll" ( _
ByVal hwndOwner As Integer, _
ByVal lpszPath As String, _
ByVal nFolder As ceFolders, _
ByVal fCreate As Boolean) As Boolean
[C#][DllImport("coredll.dll", SetLastError=true)]
private static extern bool SHGetSpecialFolderPath(
int hwndOwner,
string lpszPath,
ceFolders nFolder,
bool fCreate);
In both cases, you'll notice that the declaration includes the name of the DLL in which the function resides (coredll.dll, in which many of the Windows CE APIs reside, analogous to kernel32.dll and user32.dll in the Win32 API) and the name of the function ( Note: As with full .NET Framework-based applications, it is a best practice to group unmanaged API declarations in a class within an appropriate namespace, such as
In the C# declaration, the You'll also notice that the function requires an argument of type [VB]Private Enum ceFolders As Integer
PROGRAMS = 2 ' \Windows\Start Menu\Programs
PERSONAL = 5 ' \My Documents
STARTUP = 7 ' \Windows\StartUp
STARTMENU = &HB ' \Windows\Start Menu
FONTS = &H14 ' \Windows\Fonts
FAVORITES = &H16 ' \Windows\Favorites
End Enum
Just as with the .NET Framework, the [DllImport("coredll.dll", EntryPoint="SHGetSpecialFolderPath")]
static extern bool GetFolderPath( //the rest of the declaration
Note: The Finally, you'll notice that the InvocationOnce you've correctly declared the function to call (typically in a utility class), you can wrap the function call in a method of that class. The normal technique used is to declare the wrapper function as a Namespace Quilogy.CeApi
Public Class FileSystem
Private Sub New()
' Prevents creation of the class
End Sub
Public Shared Function GetSpecialFolderPath( _
ByVal folder As ceFolders) As String
Dim sPath As String = New String(" "c, MAX_PATH)
Dim i As Integer
Dim ret As Boolean
Try
ret = SHGetSpecialFolderPath(0, sPath, folder, False)
Catch ex As Exception
HandleCeError(ex, "GetSpecialFolderPath")
Return Nothing
End Try
If Not ret Then
' API Error so retrieve the error number
Dim errorNum As Integer = Marshal.GetLastWin32Error()
HandleCeError(New WinCeException( _
"SHGetSpecialFolderPath returned False, " & _
"likely an invalid constant", errorNum), _
"GetSpecialFolderPath")
End If
Return sPath
End Function
' Other methods
End Class
End Namespace
In this case, the call to The method can then be invoked by a caller like so: Imports Quilogy
...
Dim docs As String
docs = CeApi.FileSystem.GetSpecialFolderPath(ceApi.ceFolders.PERSONAL)
When this code is just-in-time (JIT) compiled at runtime, the P/Invoke service of the common language runtime will extract the Handling ErrorsAlthough developers never expect their code to generate runtime errors, it is important to remember that functions invoked on DLLs using P/Invoke can generate two different kinds of errors. The first is an exception generated by the PInvoke service itself. This occurs if the arguments passed to the method contain invalid data, or if the function itself is declared with improper arguments. In this case, a Private Shared Sub HandleCeError(ByVal ex As Exception, _
ByVal method As String)
' Do any logging here
' Swallow the exception if asked
If Not ExceptionsEnabled Then
Return
End If
If TypeOf ex Is NotSupportedException Then
' Bad arguments or incorrectly declared
Throw New WinCeException( _
"Bad arguments or incorrect declaration in " & method, 0, ex)
End If
If TypeOf ex Is MissingMethodException Then
' Entry point not found
Throw New WinCeException( _
"Entry point not found in " & method, 0, ex)
End If
If TypeOf ex Is WinCeException Then
Throw ex
End If
' All other exceptions
Throw New WinCeException( _
"Miscellaneous exception in " & method, 0, ex)
End Sub
Public Class WinCeException : Inherits ApplicationException
Public Sub New()
End Sub
Public Sub New(ByVal message As String)
MyBase.New(message)
End Sub
Public Sub New(ByVal message As String, ByVal apiError As Integer)
MyBase.New(message)
Me.APIErrorNumber = apiError
End Sub
Public Sub New(ByVal message As String, _
ByVal apiError As Integer, ByVal innerexception As Exception)
MyBase.New(message, innerexception)
Me.APIErrorNumber = apiError
End Sub
Public APIErrorNumber As Integer = 0
End Class
You'll notice that the Note: The error strings shown in the previous code listing can alternatively be placed in a resource file, packaged in a satellite assembly, and retrieved dynamically using
The second type of error that might be produced is an error returned from the DLL function itself. In the case of the .NET Compact Framework DifferencesAlthough the declarations shown above are the same in the .NET Compact Framework as in the full .NET Framework (with the exception of the module name), there are several subtle differences.
Marshaling DataDuring the invocation process, the P/Invoke service is responsible for marshaling the parameter values passed to the DLL function. This component of P/Invoke is often referred to as the marshaler. In this section, we'll discuss the work of the marshaler in dealing with common types, strings, structures, non-integral types and a few other issues. Blittable TypesFortunately, many of the types you'll use to call DLL functions will have a common representation in both the .NET Compact Framework and unmanaged code. These types are referred to as blittable types, and can be seen in the following table.
In other words, the marshaler doesn't need to perform any special handling of parameters defined with these types in order to convert them between managed and unmanaged code. In fact, by extension, the marshaler needn't convert one-dimensional arrays of these types, or even structures and classes that contain only these types. While this behavior is the same on the full .NET Framework, the .NET Compact Framework also includes Warning: Although
Obviously, sticking to these types in your .NET Compact Framework applications leads to simpler coding on your part. Another simple example of using blittable types is calling the Public Enum ceEvents
NOTIFICATION_EVENT_NONE = 0
NOTIFICATION_EVENT_TIME_CHANGE = 1
NOTIFICATION_EVENT_SYNC_END = 2
NOTIFICATION_EVENT_DEVICE_CHANGE = 7
NOTIFICATION_EVENT_RS232_DETECTED = 9
NOTIFICATION_EVENT_RESTORE_END = 10
NOTIFICATION_EVENT_WAKEUP = 11
NOTIFICATION_EVENT_TZ_CHANGE = 12
End Enum
Public Class Environment
Private Sub New()
End Sub
<DllImport("coredll.dll", SetLastError:=True)> _
Private Shared Function CeRunAppAtEvent(ByVal appName As String, _
ByVal whichEvent As ceEvents) As Boolean
End Function
Public Shared Function ActivateAfterSync() As Boolean
Dim ret As Boolean
Try
Dim app As String
app = _
System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase
ret = CeRunAppAtEvent(app, ceEvents.NOTIFICATION_EVENT_SYNC_END)
If Not ret Then
Dim errorNum As Integer = Marshal.GetLastWin32Error()
HandleCeError(New WinCeException( _
"CeRunAppAtEvent returned false", errorNum), _
"ActivateAfterSync")
End If
Return ret
Catch ex As Exception
HandleCeError(ex, "ActivateAfterSync")
Return False
End Try
End Function
Public Shared Function DeactivateAfterSync() As Boolean
Dim ret As Boolean = False
Try
Dim app As String
app = _
System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase
ret = CeRunAppAtEvent(app, ceEvents.NOTIFICATION_EVENT_NONE)
If Not ret Then
Dim errorNum As Integer = Marshal.GetLastWin32Error()
HandleCeError(New WinCeException( _
"CeRunAppAtEvent returned false", errorNum), _
"DeactivateAfterSync")
End If
Return ret
Catch ex As Exception
HandleCeError(ex, "DeactivateAfterSync")
Return False
End Try
End Function
' other stuff
End Class
Note: After the
In this example, as in Passing StringsAs just mentioned, strings in the .NET Compact Framework are blittable types, and are represented to unmanaged functions as null-terminated arrays of Unicode characters. At invocation time, since Note: The .NET Compact Framework always passes a pointer to a reference type and does not support passing reference types by reference ( As you have no doubt noticed, however, Dim sPath As String = New String(" "c, MAX_PATH)
Since a pointer to the string is passed to the unmanaged function, the unmanaged code sees the string as This behavior is quite different from the full .NET Framework, where the marshaler must take the character set into consideration. As a result, the full .NET Framework does not support passing strings by value or by reference into unmanaged functions and allowing the unmanaged function to modify the contents of the buffer. To solve this problem (since many of the Win32 APIs expect string buffers) in the full .NET Framework, you can, instead, pass a It turns out that Private Declare Function SHGetSpecialFolderPath Lib "coredll.dll" ( _
ByVal hwndOwner As Integer, _
ByVal lpszPath As StringBuilder, _
ByVal nFolder As ceFolders, _
ByVal fCreate As Boolean) As Boolean
And the calling syntax to: Dim sPath As New StringBuilder(MAX_PATH)
ret = SHGetSpecialFolderPath(0, sPath, folder, False)
In fact, in this specific case, it is more efficient to change to a In any case, it is recommended that you use Passing StructuresAs mentioned previously, you can pass structures to unmanaged functions without worrying, as long as the structure contains blittable types. For example, the Private Structure MEMORY_STATUS
Public dwLength As UInt32
Public dwMemoryLoad As UInt32
Public dwTotalPhys As UInt32
Public dwAvailPhys As Integer
Public dwTotalPageFile As UInt32
Public dwAvailPageFile As UInt32
Public dwTotalVirtual As UInt32
Public dwAvailVirtual As UInt32
End Structure
<DllImport("coredll.dll", SetLastError:=True)> _
Private Shared Sub GlobalMemoryStatus(ByRef ms As MEMORY_STATUS)
End Sub
Public Shared Function GetAvailablePhysicalMemory() As String
Dim ms As New MEMORY_STATUS
Try
GlobalMemoryStatus(ms)
Dim avail As Double = CType(ms.dwAvailPhys, Double) / 1048.576
Dim sAvail As String = String.Format("{0:###,##}", avail)
Return sAvail
Catch ex As Exception
HandleCeError(ex, "GetAvailablePhysicalMemory")
Return Nothing
End Try
End Function
You'll notice that, in this case, the Alternatively, this function could have been called by declaring It is also important to note that reference types are always marshaled in the order in which they appear in managed code. This means that the fields will be laid out in memory, as expected by the unmanaged function. As a result, you don't need to decorate the structure with the While you can pass structures (and classes) to unmanaged functions, the .NET Compact Framework marshaler does not support marshaling a pointer to a structure returned from an unmanaged function. In these cases, you will need to marshal the structure manually, using the Finally, one of the major differences between the marshaler in the .NET Compact Framework and that in the full .NET Framework is that the .NET Compact Framework marshaler cannot marshal complex objects (reference types) within structures. This means that if any fields in a structure has types other than those listed in the table shown previously (including strings or arrays of strings), the structure cannot be fully marshaled. This is due to the fact that the .NET Compact Framework does not support the Passing Non-Integral TypesYou'll notice in the table of blittable types that no mention is made of floating point variables. These types are non-integral (not representing a whole number), and cannot be marshaled by value by the .NET Compact Framework. However, they can be marshaled by reference, and passed as pointers to an unmanaged function that acts as a wrapper or shim. Note: This is also the case with 64 bit integers ( For example, if the unmanaged function accepts two parameters of type double DoSomeWork(double a, double b) { } then, in order to call the function from the .NET Compact Framework, you would first need to create an unmanaged function, using eMbedded Visual C, that accepts both arguments by reference (as pointers), and returns the result as an output parameter by calling the original function, like so: void DoSomeWorkShim(double *pa, double *pb, double *pret) { *pret = DoSomeWork(pa, pb); } You would then declare the <DllImport("ShimFunction.dll")> _
Private Shared Sub DoSomeWorkShim(ByRef a As Double, _
ByRef b As Double, ByRef ret As Double)
End Sub
Finally, you could wrap the call to Public Shared Function DoSomeWork(ByVal a As Double, _
ByVal b As Double) As Double
Dim ret As Double
DoSomeWorkShim(a, b, ret)
Return ret
End Function
Other IssuesOne of the issues that developers worry about when thinking about P/Invoke is its interoperation with the garbage collector (GC) in the common language runtime. Since the garbage collector in both the .NET Compact Framework and the full .NET Framework can rearrange objects on the managed heap when a collection occurs, this could be a problem for an unmanaged function attempting to manipulate memory using a pointer passed to it. Fortunately, the .NET Compact Framework marshaler automatically pins (locks the object in its current memory location) any reference type passed to an unmanaged function for the duration of the call. A second issue that developers often encounter is the use of the Note: In addition to creating unverifiable code, on the full .NET Framework, unsafe code can only be executed in a trusted environment, although, in version 1.0 of the .NET Compact Framework, code access security (CAS) was not included, and so this is not presently an issue. The ConclusionAs you can see, the Platform Invoke feature of the .NET Compact Framework provides a solid subset of the features available on the full .NET Framework. With it, you should be able to fairly easily make the majority of the calls to unmanaged DLLs that your applications require. For situations that are more complex, such as passing strings embedded in structures, you may need to resort to pointers and memory allocation, the topic of a later whitepaper. Links
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||