|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
This article has been provided courtesy of MSDN. SummaryExplore advanced interoperability on the .NET Compact Framework. Contents
IntroductionIn our previous paper, An Introduction to P/Invoke and Marshaling on the Microsoft .NET Compact Framework, we discussed how the Platform Invoke service of both the Microsoft .NET Compact Framework and the Microsoft .NET Framework allow managed code to invoke functions residing in unmanaged DLLs, allowing both custom and operating system (Windows CE) APIs to be accessible to applications written for either framework. Although many of the features of the service are identical between the two frameworks, the .NET Compact Framework is a subset of the full .NET Framework, and so there are several differences, some of which we explored in the previous paper. In this whitepaper, we'll focus on two particular issues that arise when marshaling structures, and how they can be handled in the .NET Compact Framework. Marshaling Complex TypesAs we mentioned in the previous paper, one of the major differences between the marshaler in the .NET Compact Framework and that in the full .NET Framework is that the more lightweight .NET Compact Framework marshaler cannot marshal complex objects (reference types) within structures or classes. This means that if any of the fields in a structure or class are defined with types that do not have a common representation between the .NET Compact Framework and unmanaged code (referred to as blittable types, and enumerated in our previous paper), the structure or class cannot be fully marshaled. For practical purposes, this means that structures or classes that contain either string pointers or fixed-length character buffers will not be properly marshaled. As an example, consider the user notification API available on Windows CE. Using this API, an application can display a notify dialog or cause an application to execute at a specific time, or in response to an event, such as synchronization, or when a PC Card is changed. Since the .NET Compact Framework doesn't include a managed class that performs this functionality, developers who need it will need to P/Invoke to make the correct operating system calls. To use the Windows CE notification API ( Private Structure CE_NOTIFICATION_TRIGGER
Dim dwSize As Integer
Dim dwType As NotificationTypes
Dim dwEvent As EventTypes
Dim lpszApplication As String
Dim lpszArguments As String
Dim startTime As SYSTEMTIME
Dim endTime As SYSTEMTIME
End Structure
Unfortunately, the two string values used to specify the application to execute, and its command-line arguments, are defined in unmanaged code as pointers to null-terminated Unicode strings ( Note: As mentioned in our previous paper, In the full .NET Framework, the marshaler can handle this situation because it includes the Private Structure CE_NOTIFICATION_TRIGGER
Dim dwSize As Integer
Dim dwType As NotificationTypes
Dim dwEvent As EventTypes
<MarshalAs(UnmanagedType.LPWStr)> Dim lpszApplication As String
<MarshalAs(UnmanagedType.LPWStr)> Dim lpszArguments As String
Dim startTime As SYSTEMTIME
Dim endTime As SYSTEMTIME
End Structure
As a result, on the full .NET Framework, the strings referenced in the structure would be marshaled as null-terminated Unicode character strings, as the unmanaged function expects. Because the .NET Compact Framework does not include this behavior, you'll need to work around this issue. Marshaling Strings in StructuresTo allow string pointers in structures and classes to be marshaled correctly by the .NET Compact Framework, there are three primary solutions: invoking a thunking layer, using an Using a Thunking LayerThe term thunking has historically applied to the code that translates arguments and return values from 16-bit to 32-bit representations, and vice versa. However, in its more generic usage, the term simply refers to creating some intermediate code that handles the translation of data. In reference to the .NET Compact Framework and P/Invoke, a thunking layer is an unmanaged function that accepts the arguments that make up the structure, creates the unmanaged structure, and calls the appropriate function. This is the technique for passing complex objects in structures presented in the Visual Studio .NET help, and on MSDN. To use this technique in the notification example above, you could create an unmanaged DLL in eMbedded Visual C++ that exports a function that accepts all of the arguments of the HANDLE RunApplicationThunk (WCHAR *lpszApplication,
WCHAR *lpszArguments, SYSTEMTIME startTime)
{
HANDLE returnCode;
CE_NOTIFICATION_TRIGGER trigger;
// populate the structure
trigger.dwSize = sizeof(trigger);
trigger.dwType = 2; //CNT_TIME
trigger.dwEvent = 0; //NONE
trigger.lpszApplication = lpszApplication;
trigger.lpszArguments = lpszArguments;
trigger.startTime = startTime;
trigger.endTime = 0; //empty, not used
// call the native Windows CE API function
returnCode = CeSetUserNotificationEx(0,&trigger, 0);
return returnCode;
}
You'll note in the above listing that the unmanaged function defaults the From managed code, this function is declared like so in C#, using the [DLLImport("MyNotification.DLL", SetLastError=true"]
private static extern IntPtr RunApplicationThunk(
string lpszApplication, string lpszArguments,
SYSTEMTIME startTime);
Your unmanaged thunking function, shown here in C#, should then be encapsulated in a managed class, as discussed in our previous paper, and exposed through a public static void RunApplication(string application, string arguments,
DateTime startTime)
{
// translate the DateTime values into managed SYSTEMTIME structures
SYSTEMTIME startStruct = DateTimeToSystemTime( start );
try
{
// call the thunking layer
IntPtr hNotify = RunApplicationThunk(application, arguments,
startStruct);
// handle errors
if(hNotify == IntPtr.Zero)
{
int errorNum = Marshal.GetLastWin32Error();
HandleCeError(New WinCeException("Could not set notification ",
errorNum), "RunApplication");
}
}
catch (Exception ex)
{
HandleCeError(ex, "RunApplication");
}
}
Note that, in this case, you needn't even declare a Note: You'll notice that the code used to translate the The obvious downside to this option is that developers must install and use eMbedded Visual C++, a daunting task to many Visual Basic and .NET Framework developers. For that reason, we'll also discuss two other ways of solving this same problem using only managed code in the .NET Compact Framework. Using Unsafe String PointersThe second option for passing strings in structures is to use the Note: As mentioned in our previous paper, in addition to creating unverifiable code, unsafe code in the full .NET Framework can only be executed in a trusted environment. However, in version 1.0 of the .NET Compact Framework, code access security (CAS) is not included, so this is not (yet) an issue. The Besides the downside of losing code verification, of course, this technique cannot be used directly from VB. You can, however, create an assembly in C# that uses unsafe code, and then call that assembly from VB. To use the [DllImport("coredll.dll",SetLastError=true)]
private static extern IntPtr CeSetUserNotificationEx(
IntPtr h,
ref CE_NOTIFICATION_TRIGGER nt,
IntPtr un
);
private unsafe struct CE_NOTIFICATION_TRIGGER
{
public uint dwSize;
public CNT_TYPE NotificationTypes; //enumeration
public NOTIFICATION_EVENT EventTypes; //enumeration
public char *lpszApplication;
public char *lpszArguments;
public SYSTEMTIME startTime;
public SYSTEMTIME endTime;
}
public static unsafe void RunApplication( string application,
string arguments, DateTime start )
{
CE_NOTIFICATION_TRIGGER nt = new CE_NOTIFICATION_TRIGGER();
nt.dwSize = (uint)Marshal.SizeOf( typeof(CE_NOTIFICATION_TRIGGER) );
nt.dwType = NotificationTypes.Time;
nt.dwEvent = EventTypes.None;
nt.startTime = DateTimeToSystemTime( start );
nt.endTime = new SYSTEMTIME(); //skip this member
try
{
if ((application == null) || (application.Length == 0))
{
throw new ArgumentNullException();
}
fixed (char *pApp = application.ToCharArray( ))
{
if ((arguments == null) || (arguments.Length == 0) )
{
arguments = " ";
}
fixed (char *pArgs = arguments.ToCharArray())
{
nt.lpszApplication = pApp;
nt.lpszArguments = pArgs;
// call the native function
IntPtr hNotify = CeSetUserNotificationEx(IntPtr.Zero,
ref nt, IntPtr.Zero);
if( hNotify == IntPtr.Zero )
{
int errorNum = Marshal.GetLastWin32Error();
HandleCeError(New WinCeException(
"Could not set notification ", errorNum),
"RunApplication");
}
}
}
}
catch (Exception ex)
{
HandleCeError(ex, " RunApplication");
}
}
In this example, you'll notice that the Next, the Note: Keep in mind that, unlike the full .NET Framework, you don't need to decorate your structures with the Finally, the To populate the string pointers embedded in the structure, the method first checks to ensure the application name is not null or an empty string. If either is the case, an Note: In this example, the declaration of the If an error occurs (the handle returned by the function is empty), the custom Using a Managed String PointerThe final technique that you can use to marshal strings inside of structure in the .NET Compact Framework is to create your own managed string pointer class. The benefits to this approach are that it can be used in both C# and VB, and can be leveraged in a variety of situations once the string pointer class has been created. It does, however, require a little interaction with the memory allocation APIs of the Windows CE operating system. To begin with this technique, you can create a managed class that declares and calls the necessary memory management APIs. This class can expose shared methods to allocate a block of unmanaged memory, free that memory, resize a block of unmanaged memory, and copy a managed string to unmanaged memory. The VB version of the Public Class Memory
<DllImport("coredll.dll", SetLastError:=True)> _
Private Shared Function LocalAlloc(ByVal uFlags As Integer, _
ByVal uBytes As Integer) As IntPtr
End Function
<DllImport("coredll.dll", SetLastError:=True)> _
Private Shared Function LocalFree(ByVal hMem As IntPtr) As IntPtr
End Function
<DllImport("coredll.dll", SetLastError:=True)> _
Private Shared Function LocalReAlloc(ByVal hMem As IntPtr, _
ByVal uBytes As Integer, ByVal fuFlags As Integer) As IntPtr
End Function
Private Const LMEM_FIXED As Integer = 0
Private Const LMEM_MOVEABLE As Integer = 2
Private Const LMEM_ZEROINIT As Integer = &H40
Private Const LPTR = (LMEM_FIXED Or LMEM_ZEROINIT)
' Allocates a block of memory using LocalAlloc
Public Shared Function AllocHLocal(ByVal cb As Integer) As IntPtr
Return LocalAlloc(LPTR, cb)
End Function
' Frees memory allocated by AllocHLocal
Public Shared Sub FreeHLocal(ByVal hlocal As IntPtr)
If Not hlocal.Equals(IntPtr.Zero) Then
If Not IntPtr.Zero.Equals(LocalFree(hlocal)) Then
Throw New Win32Exception(Marshal.GetLastWin32Error())
End If
hlocal = IntPtr.Zero
End If
End Sub
' Resizes a block of memory previously allocated with AllocHLocal
Public Shared Function ReAllocHLocal(ByVal pv As IntPtr, _
ByVal cb As Integer) As IntPtr
Dim newMem As IntPtr = LocalReAlloc(pv, cb, LMEM_MOVEABLE)
If newMem.Equals(IntPtr.Zero) Then
Throw New OutOfMemoryException
End If
Return newMem
End Function
' Copies the contents of a managed string to unmanaged memory
Public Shared Function StringToHLocalUni( _
ByVal s As String) As IntPtr
If s Is Nothing Then
Return IntPtr.Zero
Else
Dim nc As Integer = s.Length
Dim len As Integer = 2 * (1 + nc)
Dim hLocal As IntPtr = AllocHLocal(len)
If hLocal.Equals(IntPtr.Zero) Then
Throw New OutOfMemoryException
Else
Marshal.Copy(s.ToCharArray(), 0, hLocal, s.Length)
Return hLocal
End If
End If
End Function
End Class
As shown in the listing, the Once the Public Structure StringPtr
Private szString As IntPtr
Public Sub New(ByVal s As String)
Me.szString = Memory.StringToHLocalUni(s)
End Sub
Public Overrides Function ToString() As String
Return Marshal.PtrToStringUni(Me.szString)
End Function
Public Sub Free()
Memory.FreeHLocal(Me.szString)
End Sub
End Structure
As you'll notice, the Both the To use the private struct CE_NOTIFICATION_TRIGGER: IDisposable
{
public uint dwSize;
public CNT_TYPE dwType;
public NOTIFICATION_EVENT dwEvent;
public StringPtr lpszApplication;
public StringPtr lpszArguments;
public SYSTEMTIME startTime;
public SYSTEMTIME endTime;
public CE_NOTIFICATION_TRIGGER( string application, string arguments,
DateTime start )
{
dwSize = (uint)Marshal.SizeOf( typeof(CE_NOTIFICATION_TRIGGER) );
dwType = CNT_TYPE.CNT_TIME;
dwEvent = NOTIFICATION_EVENT.NONE;
lpszApplication = new StringPtr( application );
lpszArguments = new StringPtr( arguments );
startTime = DateTimeToSystemTime( start );
endTime = new SYSTEMTIME();
}
//other constructors possible here
public void Dispose()
{
lpszApplication.Free();
lpszArguments.Free();
}
}
Finally, the public static void RunApplication( string application, string arguments,
DateTime start )
{
CE_NOTIFICATION_TRIGGER nt =
new CE_NOTIFICATION_TRIGGER( application, arguments, start );
using( nt )
{
IntPtr hNotify = CeSetUserNotificationEx(
IntPtr.Zero, ref nt, IntPtr.Zero );
if( hNotify == IntPtr.Zero )
{
int errorNum = Marshal.GetLastWin32Error();
HandleCeError(New WinCeException(
"Could not set notification ", errorNum),
"RunApplication");
}
}
}
Marshaling Fixed-Length Strings In StructuresThe second situation that arises when using the P/Invoke service of the .NET Compact Framework involves marshaling fixed-length strings or character arrays inside a structure. For example, the typedef struct _NOTIFYICONDATA { DWORD cbSize; HWND hWnd; UINT uID; UINT uFlags; UINT uCallbackMessage; HICON hIcon; WCHAR szTip[64]; } NOTIFYICONDATA, *PNOTIFYICONDATA; You'll notice that the final field in the structure, that holds the text of the tooltip to display when the cursor is over the icon, is defined as a 64-element array of characters. A straightforward translation of this structure in VB .NET would then look as follows: Private Structure NOTIFYICONDATA
Public cbSize As Integer
Public hWnd As IntPtr
Public uID As Integer
Public uFlags As Integer
Public uCallbackMessage As Integer
Public hIcon As IntPtr
Public szTip() As Char
Public Sub New(ByVal toolTip As String)
szTip = toolTip.ToCharArray
End Sub
End Structure
Unfortunately, this simple translation won't work, since, even though Because of this behavior, you have essentially two options. The first involves creating a byte array of the correct total size, and then copying the individual fields of the structure into and out of the byte array, as appropriate, using the methods of the Note: The key to this technique is that the To use this technique, you must first make the appropriate unmanaged declarations in your class. As before, a best practice is to declare these as private functions in a class that then exposes Private Structure NOTIFYICONDATA
Public cbSize As Integer
Public hWnd As IntPtr
Public uID As Integer
Public uFlags As Integer
Public uCallbackMessage As Integer
Public hIcon As IntPtr
End Structure
<DllImport("coredll", SetLastError:=True)> _
Private Shared Function RegisterWindowMessage(ByVal _
lpMessage As String) As Integer
End Function
<DllImport("coredll", SetLastError:=True)> _
Private Shared Function DestroyIcon(ByVal hIcon As IntPtr) As IntPtr
End Function
<DllImport("coredll", SetLastError:=True)> _
Private Shared Function Shell_NotifyIcon( _
ByVal dwMessage As TrayConstants, _
ByVal lpData As IntPtr) As Boolean
End Function
Private Enum TrayConstants As Integer
NIM_ADD = 0
NIM_DELETE = 2
NIF_ICON = 2
NIF_MESSAGE = 1
NIF_TIP = 4
NIF_ALL = NIF_ICON Or NIF_MESSAGE Or NIF_TIP
End Enum
The key point to note here is that the The call to the ' Sets up the notify icon and returns the message to hook events for
Public Shared Function CreateNotifyIcon( _
ByVal notifyWindow As MessageWindow, _
ByVal icon As IntPtr, _
ByVal Tooltip As String) As Integer
Dim structPtr As IntPtr
' Create the structure
Dim nid As New NOTIFYICONDATA
' Calculate the size of the structure needed
Dim size As Integer = Marshal.SizeOf(nid) + _
(64 * Marshal.SystemDefaultCharSize)
Try
' Register the callback event
Dim msg As Integer = RegisterWindowMessage("NotifyIconMsg")
' Fill in the fields of the structure
With nid
.cbSize = size
.hIcon = icon
.hWnd = notifyWindow.Hwnd
.uCallbackMessage = msg
.uID = 1 'Application defined identifier
.uFlags = TrayConstants.NIF_ALL
End With
' Allocate the memory
structPtr = Memory.AllocHLocal(size)
' Copy the structure to the pointer
Marshal.StructureToPtr(nid, structPtr, False)
' Add the tooltip
Dim arrTooltip() As Char = Tooltip.ToCharArray()
Dim toolOffset As New IntPtr(structPtr.ToInt32() + _
Marshal.SizeOf(nid))
Marshal.Copy(arrTooltip, 0, toolOffset, arrTooltip.Length)
' Call the function
Dim ret As Boolean = Shell_NotifyIcon( _
TrayConstants.NIM_ADD, structPtr)
If ret = False Then
' Go get the error
Dim errorNum As Integer = Marshal.GetLastWin32Error()
Throw New WinCeException("Could not set the notify icon ", _
errorNum)
Else
' Success!
Return msg
End If
Catch ex As Exception
HandleCeError(ex, "CreateNotifyIcon", False)
Finally
' Free memory
Memory.FreeHLocal(structPtr)
End Try
End Function
You'll notice that this method accepts the window to notify when the icon is acted upon defined as a Note: One technique to create the pointer to the icon is to use the
First, this method declares an unmanaged pointer that will be used to hold the pointer to the structure, and creates a new instance of the Once the structure's correct size has been determined, the At this point, the structure can be copied into unmanaged memory, and a pointer returned. This can be done by using the However, at this point, only the first 24 bytes of the memory pointed to by The It is important to note, however, that the Note: Those developers who will need to marshal reference types and character arrays like those shown in this paper may alternatively want to invest time in creating a custom
For completeness, the associated ' Clear the notify icon and deallocates the icon
Public Shared Sub DestroyNotifyIcon(ByVal notifyWindow As MessageWindow, _
ByVal icon As IntPtr)
Dim structPtr As IntPtr
Dim nid As New NOTIFYICONDATA
Dim size As Integer = Marshal.SizeOf(nid) + _
(64 * Marshal.SystemDefaultCharSize)
nid.cbSize = size
nid.hWnd = notifyWindow.Hwnd
nid.uID = 1
Try
' Allocate the memory
structPtr = Memory.AllocHLocal(size)
' Create the pointer to the structure and remove the icon
Marshal.StructureToPtr(nid, structPtr, False)
Dim ret As Boolean = Shell_NotifyIcon( _
TrayConstants.NIM_DELETE, structPtr)
If ret = False Then
' Go get the error
Dim errorNum As Integer = Marshal.GetLastWin32Error()
Throw New WinCeException("Could not destroy icon ", errorNum)
End If
Catch ex As Exception
HandleCeError(ex, "DestroyNotifyIcon", False)
Finally
' Free memory
Memory.FreeHLocal(structPtr)
' Also we need to destroy the icon to recover resources
DestroyIcon(icon)
End Try
End Sub
Note: In order for the To use the using Atomic.CeApi;
public class Form1 : System.Windows.Forms.Form
{
private EventWindow eventWnd;
private IntPtr hIcon;
private int iconMsg;
public Form1()
{
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.Closed += new System.EventHandler(this.Form1_Closed);
}
private void Form1_Load(object sender, System.EventArgs e)
{
hIcon = IntPtr.Zero;
// Get the icon handle here
// Create the MessageWindow to process messages
eventWnd = new EventWindow();
// Place the icon in the tray
iconMsg = Forms.CreateNotifyIcon(eventWnd,
hIcon,"My application tooltip");
// Set up event handler to handle incoming messages
eventWnd.msgId = iconMsg;
eventWnd.MsgProcssedEvent += new
EventWindow.MsgProcessedEventHandler(
eventWnd_MsgProcessed);
}
private void Form1_Closed(object sender, System.EventArgs e)
{
// Clean up the icon
Forms.DestroyNotifyIcon(eventWnd,hIcon);
}
private void eventWnd_MsgProcessed (ref Message msg)
{
// Process the message
}
}
// Class that receives messages from tray icon
public class EventWindow: MessageWindow
{
public delegate void MsgProcessedEventHandler(ref Message msg);
public event MsgProcessedEventHandler MsgProcessedEvent;
public int msgId
protected override void WndProc(ref Message m)
{
base.WndProc (ref m);
if (m.Msg == this.msgId)
{
MsgProcessedEvent(ref m);
}
}
}
As you can see, the To receive those notifications, however, the method returns the message identifier, which is then placed in the An event handler, the SummaryAlthough the .NET Compact Framework marshaler used by the P/Invoke service does not contain all of the functionality of the full .NET Framework marshaler, it can still be used effectively, even in fairly complex cases, using a variety of techniques. In this whitepaper, we showed how passing both string pointers and embedded character arrays inside structure can be accomplished using a thunking layer, pointers and unsafe code, creating your own string pointer class, and doing a little custom marshaling. Hopefully, you'll be able to use and build on these techniques as you create great .NET Compact Framework-based applications. Links
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||