Export (0) Print
Expand All

Sending SMSs from your Microsoft .NET Compact Framework-based Applications

.NET Compact Framework 1.0
 

Derek Mitchell
DEVBUZZ.COM, Inc.

Contributions by Jeffrey Paul
Microsoft Corporation

March 2002

Applies to:
    Microsoft® .NET Compact Framework 1.0
    Microsoft Visual Studio® .NET 2003

Summary: Learn how to send Short Message Service messages from your .NET Compact Framework-based application. (9 printed pages)

Download SendSMS.msi.


Contents

Introduction
SMS Sending in C#
SMS Sending in VB.NET
Code Use
Conclusion

Introduction

Given the subset functionality of the Microsoft® .NET Compact Framework v1.0, it is sometimes necessary to fall back to legacy OS system calls that are exposed via the Microsoft Windows® CE API programming layer. In native code, this is done without giving the act another thought; however, in managed code, a little plumbing is required to ensure compatibility with these unmanaged calls. In this document, we explore a specific example where this becomes relevant: sending Short Message Service (SMS) messages from both C# and Microsoft Visual Basic® .NET code. SMS allows you to send messages of up to 160 characters from one mobile device to another. As devices with Global System for Mobile Communications (network) radios become more prevalent, SMS will become an increasingly popular way to send small pieces of text. It is a simple affair to SMS-enable native code, but here we take things one step further and show how it can be done in your managed code.

SMS Sending in C#

All of the code that you will see referenced below is available for download. We have two key responsibilities in getting our code up and running. First, we must declare a host of constants that are passed onto our GSM Mobile Operator such that it understands how we want the message delivered. For example, should the SMS be sent and re-sent until transmission is successful, or should the operation be halted after the first failure? For convenience, this entire program will be adhered to the namespace Microsoft.Wireless. Such a namespace implies great expandability into other wireless functionality areas. First, let's define an enumeration that indicates what our destination phone number represents:

   public enum AddressType
   {
   Unknown,
      International,
      National,
      NetworkSpecific,
      Subscriber,
      Alphanumeric,
      Abbreviated
   }

Next, we will define what a phone address consists of. We call this type a PhoneAddress. Here, we say it is an AddressType along with a string representing the digits of the number. We write the following:

   public struct PhoneAddress
   {
      public AddressType AddressType;
      public String Address;
   }

At this point we can begin defining the intended behaviors of this example by encapsulating basic SMS operations, namely open, send, and close, in a C# class that we call SMS. Several constants are required by the SMS framework, and we define those at the top of our class:

      private static string   SMS_MSGTYPE_TEXT            = "Microsoft
        Text SMS Protocol";
      private static long      SMS_MODE_SEND              = 0x00000002;
      private static long      SMS_OPTION_DELIVERY_NONE   = 0x00000000;
      private static long      SMS_OPTION_DELIVERY_NO_RETRY  = 0x00000001;
      private static long      PS_MESSAGE_OPTION_NONE     = 0x00000000;

SMS_MSGTYPE_TEXT indicates usage of the Microsoft Text SMS Protocol. SMS_MODE_SEND indicates send mode. SMS_OPTION_DELIVERY_NONE indicates no special options whereas SMS_OPTION_DELIVERY_NO_RETRY asks that the message be not re-scheduled for delivery according to the router's policies. Without this option, the SMS will be re-sent until router gives up. PS_MESSAGE_OPTION_NONE indicates that no notification information will be sent back to the application. Next we include a bunch of enumerations that also describe various prescribed behavior of the SMS message.

      private enum SMS_DATA_ENCODING
      {
         SMSDE_OPTIMAL=0,
         SMSDE_GSM,
         SMSDE_UCS2,
      }

SMSDE_OPTIMAL picks the data encoding method that best represents the entirety of the message at the smallest transmission size. SMSDE_GSM will force a 7-bit encoding as specified within the GSM specification. Characters unable to be encoded properly will not be processed correctly. Finally, SMSDE_UCS2 uses UCS2 encoding.

private enum PROVIDER_SPECIFIC_MESSAGE_CLASS

{

PS_MESSAGE_CLASS0 = 0,

PS_MESSAGE_CLASS1,

PS_MESSAGE_CLASS2,

PS_MESSAGE_CLASS3,

}

PS_MESSAGE_CLASS0 indicates the message should be displayed immediately but not stored on the Subscriber Identity Module (SIM). PS_MESSAGE_CLASS1 indicates the service center handling the message should be notified when the message goes through and it should be stored. PS_MESSAGE_CLASS2 indicates the message should be first sent to the SMS data field within the user's SIM before notification is sent to the service center handling the message. If the SIM is filled, an error message is sent to the service center. PS_MESSAGE_CLASS3 indicates when the message has successfully been sent to the destination and can be stored to SIM, the service center will receive a notification.

      private enum PROVIDER_SPECIFIC_REPLACE_OPTION
      {
         PSRO_NONE = 0,
         PSRO_REPLACE_TYPE1,
         PSRO_REPLACE_TYPE2,
         PSRO_REPLACE_TYPE3,
         PSRO_REPLACE_TYPE4,
         PSRO_REPLACE_TYPE5,
         PSRO_REPLACE_TYPE6,
         PSRO_REPLACE_TYPE7,
         PSRO_RETURN_CALL,
         PSRO_DEPERSONALIZATION,
      }

The aforementioned options specify which (if any) of previous notifications sent to the service center should be replaced by the current message. The type specified by the option ensures that all previous notifications of its type get replaced with the current notification. For example, if PSRO_REPLACE_TYPE3 is set, all previous notifications of type 3 will be displaced with the current notification. PSRO_RETURN_CALL indicates the originating address will support a return phone call.

private struct TEXT_PROVIDER_SPECIFIC_DATA

{

public IntPtr dwMessageOptions;

public PROVIDER_SPECIFIC_MESSAGE_CLASS psMessageClass;

public PROVIDER_SPECIFIC_REPLACE_OPTION psReplaceOption;

}

These fields all call on the enumerations we defined above and are transmitted when sending/receiving an SMS in string form. dwMessageOptions, in an integer form, specifies some miscellaneous details spelled out in the GSM specification 3.40. We keep it simple by not attaching an options, and for most purposes, they're not needed. The next two fields are simply us specifying options from the above enumerations.

Finally we're ready to benefit from P/Invoke and call forth Win32 API functions. We first handle SmsOpen, which opens the SMS messaging component to allow sending or receiving of messages. Its signature is:

HRESULT SmsOpen (
const LPCTSTR ptsMessageProtocol,
const DWORD dwMessageModes,
SMS_HANDLE* const psmshHandle,
HANDLE* const phMessageAvailableEvent);

ptsMessageProtocol is a string denoting that SMS protocol to use. dwMessageModes specifies whether we want to be in send or receive mode. psmshHandle is a pointer to the handle of the SMS session and is valid only if the function returns properly. phMessageAvailableEvent is the handle to a Win32 event handle that can be used to determine when the next message is available to be read. It is not necessary to close this handle because SmsClose does it automatically. We use DllImport to make this function available to us in our managed code.

      [DllImport("sms.dll")]
   private static extern IntPtr SmsOpen(String ptsMessageProtocol,
     IntPtr dwMessageModes, ref IntPtr psmshHandle, IntPtr
     phMessageAvailableEvent);

Next we take a look at SmsSendMessage. In its Win32 API form, its signature is as follows.

HRESULT SmsSendMessage (
const SMS_HANDLE smshHandle,
const SMS_ADDRESS * const psmsaSMSCAddress,
const SMS_ADDRESS * const psmsaDestinationAddress,
const SYSTEMTIME * const pstValidityPeriod,
const BYTE * const pbData,
const DWORD dwDataSize,
const BYTE * const pbProviderSpecificData,
const DWORD dwProviderSpecificDataSize,
const SMS_DATA_ENCODING smsdeDataEncoding,
const DWORD dwOptions,
SMS_MESSAGE_ID * psmsmidMessageID);

smshHandle is the handle returned in psmshHandle by SmsOpen. psmsaSMSCAddress is an optional parameter specifying which SMS Message Center is to be used. If NULL is used, the user's default SMSMC will be used. psmsaDestinationAddress is where the message is to be delivered. pstValidityPeriod breaks from the standard SYSTEMTIME structure in that it is the amount of time past sending of an SMS during which the message still is considered valid. pbData is the byte representation of the message's data portion. This can be NULL. dwDataSize is the size in bytes of the message's data portion. pbProviderSpecificData is additional information required by some providers to allow an SMS to transmit correctly. dwProviderSpecificDataSize is the size in bytes of the previously mentioned field. smsdeDataEncoding is an option found within the SMS_DATA_ENCODING enumeration detailed above. dwOptions are (currently) two flags that will fail an SMS after one attempt or will allow it to be redelivered until the router gives up. Finally, psmsmidMessageID will be non-null if this function returns successfully. Again using DllImport, here is how we gain access to SmsSendMessage via our managed code:

      [DllImport("sms.dll")]
private static extern IntPtr SmsSendMessage(IntPtr smshHandle, IntPtr
  psmsaSMSCAddress, IntPtr psmsaDestinationAddress, IntPtr
  pstValidityPeriod, byte[] pbData, IntPtr dwDataSize, byte[]
  pbProviderSpecificData, IntPtr dwProviderSpecificDataSize,
  SMS_DATA_ENCODING smsdeDataEncoding, IntPtr dwOptions,  IntPtr
  psmsmidMessageID);

The last SMS sending-related function we deal with is SmsClose, which attempts to gracefully close an SMS message service request. We pass in our smshHandle from above and we get back a value indicating any errors encountered during sending.

HRESULT SmsClose (
const SMS_HANDLE oCommandBarPopup);

In our code we declare it as

   [DllImport("sms.dll")]
   private static extern IntPtr SmsClose(IntPtr smshHandle);

Finally, we're ready to delve into our first function implementation, SendMessage. To begin, we declare this function as unsafe due to its usage of pointers. We must first call SmsOpen to inform the OS that we wish to access the SIM, and we handle the case where this operation fails.

IntPtr res = SmsOpen(SMS_MSGTYPE_TEXT, (IntPtr)SMS_MODE_SEND, ref hSms,
  IntPtr.Zero);
if (res != IntPtr.Zero)
      throw new Exception("Could not open SMS.");

We then build up the byte representation of the destination phone number (passed in as string). We declare a pointer pAddr (originally pointing at the same address as bDest); we do this so that garbage collection does not relocate this variable unpredictably. Outside the enclosing brackets, pAddr is no longer fixed and cannot be accessed reliably.

      Byte[] bDest = new Byte[516];
      fixed (byte* pAddr = bDest)

Now we write the first part of the PhoneAddress into our IntPtr by indicating it is an unknown format (let the OS decide for us). We use WriteInt32 because we're directly to memory.

      byte *pCurrent = pAddr;
      Marshal.WriteInt32((IntPtr)pCurrent, (int)AddressType.Unknown);
      pCurrent +=4;

Now we chunk our string representation of destination number into bytes and write those into our PhoneAddress-like structure, but including the 4-byte offset required by structure alignment.

      foreach (byte b in Encoding.Unicode.GetBytes(sPhoneNumber))
      {
         Marshal.WriteByte((IntPtr)pCurrent, b);
         pCurrent++;
      }

We don't care to place any options on the provider data structure, so we allocate space for its representative structure and keep its fields blank.

      Byte[] ProvData = new Byte[12];

Then we chunk our SMS message (passed in as a string) into bytes that we can pass on.

      byte[] bMessage = Encoding.Unicode.GetBytes(sMessage);
      int nMsgSize = bMessage.Length;

Then we call the function using the variables we have properly constructed above.

      res = SmsSendMessage(hSms, IntPtr.Zero, (IntPtr)pAddr, IntPtr.Zero,
        bMessage, (IntPtr)nMsgSize, ProvData, (IntPtr)ProvData.Length,
        SMS_DATA_ENCODING.SMSDE_OPTIMAL, (IntPtr)SMS_OPTION_DELIVERY_NONE,
        IntPtr.Zero);

If this call returns something other than zero, we know we encountered problems. Lastly, we call SmsClose to gracefully terminate this session.

SMS Sending in VB.NET

Our code for VB.NET is a straightforward port of what we did in C#; as such, we only highlight the substantial differences in syntax. All of our enumerations carry over from C#, except with proper VB.NET syntax. We use IntPtr variables to hold value for most of our functionality. We call SmsOpen as we did before and check for errors.

retVal = SmsOpen(SMS_MSGTYPE_TEXT, SMS_MODE_SEND, smsHandle, IntPtr.Zero)
If retVal.ToInt32 <> 0 Then
   Throw New Exception("Could not open SMS.")
End If

Next, we put both the AddressType and address representations into byte form and fill up the variable smsAddressTag as if it were our PhoneAddress structure.

Dim smsatAddressType As Byte() =
  BitConverter.GetBytes(SMS_ADDRESS_TYPE.SMSAT_UNKNOWN)
Dim ptsAddress As Byte() = 
  System.Text.Encoding.Unicode.GetBytes(sPhoneNumber)
Dim smsAddressTag(smsatAddressType.Length + ptsAddress.Length) As Byte
Array.Copy(smsatAddressType, 0, smsAddressTag, 0, smsatAddressType.Length)
Array.Copy(ptsAddress, 0, smsAddressTag, smsatAddressType.Length,
  ptsAddress.Length)
Dim smsAddress As IntPtr = Marshal.AllocHLocal(smsAddressTag.Length)
System.Runtime.InteropServices.Marshal.Copy(smsAddressTag, 0, smsAddress,
  smsAddressTag.Length)

Next we chunk our message string into bytes and fill a byte array with those pieces. We can then pass in all of our variables through SmsSendMessage.

Dim smsMessageTag As Byte() =
  System.Text.Encoding.Unicode.GetBytes(sMessage)
smsMessage = Marshal.AllocHLocal(smsMessageTag.Length)
System.Runtime.InteropServices.Marshal.Copy(smsMessageTag, 0, smsMessage,
  smsMessageTag.Length)

retVal = SmsSendMessage(smsHandle, 0, smsAddress, 0, smsMessage,
  smsMessageTag.Length, ProvData, 12, SMS_DATA_ENCODING.SMSDE_OPTIMAL,
  SMS_OPTION_DELIVERY_NONE, 0)

We check for errors and return.

Code Use

To send an SMS from within your C# code, call our code by:

Microsoft.Wireless.SMS.SendMessage("Phone Number","Message");
To send an SMS from within your VB.NET code, call our code by:

Conclusion

The intent of this document was to illustrate the simplicity with which you can call important native Win32 APIs from both C# and VB .NET. By making minor changes to the syntax, additionally, one can "port" from C# to VB .NET in a short amount of time. Most importantly, the portability highlights one of the nicest features of the .NET Compact Framework: the ability to create objects in one language and leverage them in any other .NET-compatible language. We could have done our backend code in C# and wrapped around it a Windows Form created in VB .NET.

Show:
© 2014 Microsoft