Pocket PC and P/Invoke
The last topic we are going to cover regarding the .NET Compact Framework is its ability to call into unmanaged code using Platform Invoke (P/Invoke). As you have seen throughout this book, most of the APIs that are supported on a Pocket PC platform are exported by using dynamic link libraries (DLLs) that your application imports. By using the P/Invoke service, you can also access the same API functions from within a .NET application. This enables you to integrate much of the functionality that is native to the Pocket PC, and not natively supported by the Compact Framework. For example, the Pocket PC Phone Edition supports the capability to send and receive SMS messages (see Chapter 8). Although the Compact Framework does not come with any classes to support this, you can use P/Invoke to enable your managed code to call into the unmanaged SMS API found in the cellcore.dll library.
To declare within your application a method that will use P/Invoke, you need to use the DllImport attribute, which supports the fields described in Table 12.16.
Table 12.16. DllImport Attributes
Field |
Description |
---|---|
EntryPoint |
The function name that you want to call into |
CharSet |
Specifies how the string arguments should be marshaled |
CallingConvention |
Specifies the calling convention to use when passing arguments |
SetLastError |
Set this value to TRUE to enable calling the Marshal.GetLastWin32Error method to check if an error occurred when invoking this method |
For example, the following code shows how you can use the MessageBox() function from a managed application by using P/Invoke:
using System; using System.Data; using System.Runtime.InteropServices; namespace invokeTest { class Class1 { // Hook up Windows API methods [DllImport("coredll.dll", EntryPoint="MessageBox", CharSet=CharSet.Unicode, SetLastError=true)] static extern Int32 MessageBox(Int32 hWnd, string stText, string stCaption, Int32 mbType); static void Main(string[] args) { // Call into the MessageBox function MessageBox(0, "MessageText", "MessageCaption", 0); } } }
Once a function has been declared with the DllImport attribute, you can then call it in the same manner as any other managed function.
Note a few minor differences regarding P/Invoke on the .NET Compact Framework when comparing it to its desktop counterpart:
-
There is no Unicode-to-ANSI string conversion. All string pointers are passed to an unmanaged function as a Unicode string.
-
There is no marshaling of objects contained within structures.
-
If a function returns a pointer to a structure, it is not marshaled to a managed structure. You need to create a wrapper function that handles simple data types.
-
Platform Invoke services does not support COM interoperability with the Compact Framework. If you wish to call into COM objects, you need to create a wrapper DLL that exports non-COM-based functions.
-
The DllImport attribute supports only the CharSet.Unicode and CharSet.Auto character sets.
-
The DllImport attribute supports only the CallingConvention.Winapi calling convention.
Sending an SMS Message from .NET
The following example shows a slightly more complicated way of using the Platform Invoke services. Because the Compact Framework does not support the marshaling of objects that are contained within a structure, you need to create a C++ “wrapper” library in order to call the Pocket PC Phone Edition’s SMS API functions (see Chapter 8).
First, create the wrapper library using Embedded Visual C++ 3.0. The code for the library will look as follows:
// First is the definition file for the DLL // smsinvoke.def LIBRARY SMSINVOKE EXPORTS SendSMSInvokeMsg @1 // Here is the wrapper DLL // smsinvoke.cpp #include <windows.h> #include <sms.h> #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) BOOL SendSMSInvokeMsg(TCHAR *tchPhoneNumber, TCHAR *tchMessage); #ifdef __cplusplus } #endif BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason, LPVOID lpvReserved) { return TRUE; } BOOL SendSMSInvokeMsg(TCHAR *tchPhoneNumber, TCHAR *tchMessage) { SMS_HANDLE hSms = NULL; HANDLE hSmsEvent = NULL; HRESULT hr = S_OK; BOOL fReturn = FALSE; // Make sure we have a number and a message if(!tchPhoneNumber || !tchMessage) return fReturn; // Open up SMS hr = SmsOpen(SMS_MSGTYPE_TEXT, SMS_MODE_SEND, &hSms, &hSmsEvent); if(FAILED(hr)) { OutputDebugString(TEXT("Could not open a handle to the SMS text message service.")); return fReturn; } // Wait for SMS to become signaled as ready DWORD dwReturn = 0; dwReturn = WaitForSingleObject(hSmsEvent, INFINITE); // SMS Event has become signaled if(dwReturn == WAIT_ABANDONED || dwReturn == WAIT_TIMEOUT) {OutputDebugString(TEXT("No longer waiting for a message")); SmsClose(hSms); return fReturn; } // Send an SMS Message through default SMSC SMS_ADDRESS smsDestination; SMS_MESSAGE_ID smsMsgId = 0; // Set the destination address for the message memset(&smsDestination, 0, sizeof(SMS_ADDRESS)); smsDestination.smsatAddressType = SMSAT_INTERNATIONAL; _tcsncpy(smsDestination.ptsAddress, tchPhoneNumber, SMS_MAX_ADDRESS_LENGTH); // Create the message DWORD dwMessageLength = 0; dwMessageLength = lstrlen(tchMessage)*sizeof(TCHAR); // Configure the Text Provider TEXT_PROVIDER_SPECIFIC_DATA txtProviderData; DWORD dwProviderLength = 0; memset(&txtProviderData, 0, sizeof(TEXT_PROVIDER_ SPECIFIC_DATA)); txtProviderData.dwMessageOptions = PS_MESSAGE_OPTION_STATUSREPORT; txtProviderData.psMessageClass = PS_MESSAGE_CLASS0; txtProviderData.psReplaceOption = PSRO_NONE; dwProviderLength = sizeof(TEXT_PROVIDER_SPECIFIC_DATA); // Send the message hr = SmsSendMessage(hSms, NULL, &smsDestination, NULL, (BYTE *)tchMessage, dwMessageLength, (LPBYTE)&txtProviderData, dwProviderLength, SMSDE_OPTIMAL, SMS_OPTION_DELIVERY_NONE, &smsMsgId); if(FAILED(hr)) OutputDebugString(TEXT("Could not send SMS Text Message.")); else { OutputDebugString(TEXT("Message has been sent.")); fReturn = TRUE; } SmsClose(hSms); return fReturn; }
Second, use P/Invoke from C# to send an SMS by calling into the wrapper function, as follows:
using System; using System.Data; using System.Runtime.InteropServices; namespace SmsInvokeTest { class Class1 { // Hook up to wrapper function [DllImport("smsinvoke.dll", EntryPoint= "SendSMSInvokeMsg", CharSet=CharSet.Unicode, SetLastError=true)] static extern Int32 SendSmsMessage(string stPhoneNumber, string stMessage); static void Main(string[] args) { // Create a message, and send it via SMS string stPhone = "4254432273"; string stMessage = "Hi there from the Compact Framework!"; int nResult = 0; nResult = SendSmsMessage(stPhone, stMessage); } } }