Derek Mitchell
DEVBUZZ.COM, Inc.
S'applique à :
Microsoft® .NET Compact
Framework 1.0
Microsoft Visual Studio®
.NET 2003
Résumé : Apprenez à envoyer des
messages SMS (Short Message Service) à partir de
votre application basée sur .NET Compact Framework.
Téléchargez
SendSMS.msi (
)
Sommaire
Introduction
Envoi
de SMS en C#
Envoi
de SMS en VB.NET
Utilisation du code
Conclusion
Introduction
Étant donné que Microsoft® .NET Compact
Framework v1.0 a une fonctionnalité de sous-ensemble,
il est parfois nécessaire de recourir aux appels
traditionnels du système d'exploitation, exposés via
la couche de programmation de l'API Microsoft
Windows® CE. En code natif, c'est un jeu
d'enfant ; en code géré en revanche, il faut
effectuer quelques manipulations pour assurer la
compatibilité avec les codes non gérés. Dans ce
document, nous étudions un exemple spécifique où
ces manipulations s'avèrent nécessaires :
l'envoi de messages courts SMS (Short Message
Service) à partir de code C# et de code Microsoft
Visual Basic® .NET. SMS vous permet d'envoyer des messages
pouvant contenir jusqu'à 160 caractères d'un
appareil portable à un autre. À mesure que les
appareils utilisant le système mondial de communications
mobiles (réseau GSM) se multiplient, le système SMS
constitue un moyen de plus en plus courant d'envoyer des
messages courts. S'il est facile d'intégrer la prise en
charge des SMS dans du code natif, nous essayons ici d'aller
plus loin et de vous montrer comment réaliser cela dans du
code géré.
Envoi de SMS en C#
Tout le code qui apparaît ci-dessous peut être
téléchargé. Pour réaliser et exécuter
du code, il nous faut réaliser deux tâches
indispensables. Premièrement, nous devons déclarer un
ensemble de constantes qui sont passées à notre
opérateur GSM de communications mobiles pour qu'il puisse
comprendre comment nous souhaitons que le message soit
délivré. Par exemple, l'envoi du SMS doit-il
être réitéré jusqu'à ce que la
transmission aboutisse ou l'opération doit-elle être
interrompue au premier échec ? Pour des raisons
pratiques, l'ensemble du programme sera associé à
l'espace de noms Microsoft.Wireless. Cet espace de noms a une
grande capacité d'extension dans d'autres domaines de
fonctionnalités sans fil. Tout d'abord, nous allons
définir une énumération indiquant ce que le
numéro de téléphone de notre destinataire
représente :
public enum AddressType
{
Unknown,
International,
National,
NetworkSpecific,
Subscriber,
Alphanumeric,
Abbreviated
}
Ensuite, nous définirons en quoi consiste une adresse
téléphonique. Nous appelons ce type
PhoneAddress. Ici, nous indiquons qu'il s'agit
d'un AddressType accompagné d'une chaîne
représentant les chiffres du numéro. Nous
écrivons ce qui suit :
public struct PhoneAddress
{
public AddressType AddressType;
public String Address;
}
Maintenant, nous pouvons commencer à définir les
comportements souhaités pour cet exemple en encapsulant
les opérations SMS de base (ouvrir, envoyer et fermer)
dans une classe C# que nous appelons
SMS. La structure SMS requiert
plusieurs constantes. Nous les définissons au début
de notre classe :
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 indique que le protocole utilisé est
Microsoft Text SMS Protocol. SMS_MODE_SEND indique le mode
envoi. SMS_OPTION_DELIVERY_NONE indique qu'aucune option
particulière n'est définie tandis que
SMS_OPTION_DELIVERY_NO_RETRY demande que la livraison du
message ne soit pas reprogrammée selon les politiques du
routeur. Sans cette option, le SMS serait renvoyé
jusqu'à abandon du routeur. PS_MESSAGE_OPTION_NONE indique
qu'aucune information de notification ne sera renvoyée
à l'application. Ensuite, nous incluons plusieurs
énumérations qui décrivent également divers
comportements prescrit pour le message SMS.
private enum SMS_DATA_ENCODING
{
SMSDE_OPTIMAL=0,
SMSDE_GSM,
SMSDE_UCS2,
}
SMSDE_OPTIMAL prend la méthode de codage des
données qui représente le mieux
l'intégralité du message avec la taille de
transmission minimum. SMSDE_GSM oblige à utiliser un
codage 7 bits comme indiqué dans la
spécification GSM. Les caractères qui ne peuvent pas
être codés comme il se doit ne pourront pas être
traités correctement. Enfin, SMSDE_UCS2 utilise le codage
UCS2.
private enum
PROVIDER_SPECIFIC_MESSAGE_CLASS
{
PS_MESSAGE_CLASS0 = 0,
PS_MESSAGE_CLASS1,
PS_MESSAGE_CLASS2,
PS_MESSAGE_CLASS3,
}
PS_MESSAGE_CLASS0 indique que le message doit être
affiché immédiatement sans être stocké dans
le module d'identification de l'abonné (Subscriber
Identity Module, SIM). PS_MESSAGE_CLASS1 indique que le
centre de services qui gère le message doit être
averti de la transmission du message et que celui-ci doit
être stocké. PS_MESSAGE_CLASS2 indique que le message
doit être d'abord envoyé au champ de données SMS
dans la carte SIM de l'utilisateur, avant que la notification
soit envoyée au centre de services gérant le message.
Si la carte SIM est remplie, un message d'erreur est
envoyé au centre de services. PS_MESSAGE_CLASS3 indique
que le message est arrivé à destination et peut
être stocké sur la carte SIM ; le centre de
services recevra une 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,
}
Les options mentionnées ci-dessus spécifient (le
cas échéant) quelle est, parmi les notifications
précédentes, celle qui doit être remplacée
par le message en cours. Le type spécifié par
l'option garantit que toutes les notifications
précédentes de ce type seront remplacées par la
notification en cours. Par exemple, si PSRO_REPLACE_TYPE3 est
défini, toutes les notifications de type 3 seront
remplacées par la notification actuelle. PSRO_RETURN_CALL
indique que l'adresse d'origine prendra en charge le rappel
téléphonique.
private struct
TEXT_PROVIDER_SPECIFIC_DATA
{
public
IntPtr
dwMessageOptions;
public
PROVIDER_SPECIFIC_MESSAGE_CLASS psMessageClass;
public
PROVIDER_SPECIFIC_REPLACE_OPTION psReplaceOption;
}
Ces champs utilisent tous les énumérations que
nous avons définies ci-dessus. Leur transmission se fait
lors de l'envoi/réception d'un SMS sous forme d'une
chaîne. dwMessageOptions, sous forme d'un entier,
spécifie divers détails décrits dans la
spécification 3.40. Pour plus de simplicité,
nous n'avons pas attaché d'options. Dans la plupart des
cas, elles ne sont pas nécessaires. Dans les deux champs
suivants, nous nous contentons de spécifier les options
à partir des énumérations
précédentes.
Nous pouvons enfin tirer parti de P/Invoke et appeler les
fonctions de l'API Win32. Nous gérons d'abord SmsOpen, qui
ouvre le composant de messagerie SMS pour permettre l'envoi et
la réception des messages. Sa signature est la
suivante :
HRESULT SmsOpen (
const LPCTSTR ptsMessageProtocol,
const DWORD dwMessageModes,
SMS_HANDLE* const psmshHandle,
HANDLE* const phMessageAvailableEvent);
ptsMessageProtocol est une chaîne qui indique qu'il
faut utiliser le protocole SMS. dwMessageModes spécifie si
nous voulons être en mode envoi ou en mode réception.
psmshHandle est un pointeur vers le handle de la session SMS.
Il n'est valide que si le renvoi de la fonction se fait de
façon correcte. phMessageAvailableEvent est le handle qui
pointe vers un handle d'événement Win32 pouvant
être utilisé pour déterminer quand le prochain
message pourra être lu. Il n'est pas nécessaire de
fermer ce handle car SmsClose le fait automatiquement. Nous
utilisons DllImport pour rendre cette fonction disponible dans
notre code géré.
[DllImport("sms.dll")]
private static extern IntPtr SmsOpen(String ptsMessageProtocol,
IntPtr dwMessageModes,
ref
IntPtr psmshHandle, IntPtr
phMessageAvailableEvent);
Ensuite nous nous intéressons à SmsSendMessage.
Dans son formulaire d'API Win32, sa signature est la
suivante.
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 est le handle renvoyé dans psmshHandle par
SmsOpen. psmsaSMSCAddress est un paramètre optionnel qui
indique le centre de messages SMS à utiliser. Si NULL est
spécifié, c'est le centre de messages SMS par
défaut de l'utilisateur qui sera utilisé.
psmsaDestinationAddress indique où le message doit
être délivré. pstValidityPeriod se distingue de
la structure standard SYSTEMTIME dans la mesure où il
s'agit du laps de temps nécessaire à l'envoi d'un SMS
durant lequel le message est encore considéré comme
valide. pbData est la représentation en octets de la
portion de données du message. Elle peut être
définie sur NULL. dwDataSize est la taille en octets de la
portion de données du message. pbProviderSpecificData
correspond aux informations supplémentaires exigées
par certains fournisseurs pour permettre la transmission
correcte d'un SMS. dwProviderSpecificDataSize est la taille en
octets du champ précédemment mentionné.
smsdeDataEncoding est une option qui se trouve dans
l'énumération SMS_DATA_ENCODING décrite
précédemment. dwOptions correspond (actuellement)
à deux indicateurs qui indiquent si le SMS doit
échouer après une tentative ou si l'envoi doit
être réitéré jusqu'à abandon du
routeur. Enfin, psmsmidMessageID est non nul lorsque le
traitement de la fonction réussit. Là encore, c'est
en utilisant DllImport que nous pouvons accéder à
SmsSendMessage via notre code géré :
[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);
La dernière fonction que nous utiliserons pour l'envoi
de SMS est SmsClose. Cette fonction tente de fermer proprement
les requêtes de services de messages SMS. Nous faisons
passer notre smshHandle présenté ci-dessus et nous
obtenons en retour une valeur indiquant les éventuelles
erreurs rencontrées lors de l'envoi.
HRESULT SmsClose (
const SMS_HANDLE oCommandBarPopup);
Dans notre code, nous déclarons cela par
[DllImport("sms.dll")]
private static extern IntPtr SmsClose(IntPtr smshHandle);
Enfin, nous pouvons fouiller dans notre première
implémentation de fonction, SendMessage. Pour commencer,
nous déclarons cette fonction comme non
sécurisée parce qu'elle utilise les pointeurs. Nous
devons d'abord appeler SmsOpen pour informer le système
d'exploitation que nous souhaitons accéder à la carte
SIM, et nous gérons le cas où cette opération
échoue.
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.");
Nous créons ensuite la représentation en octets du
numéro de téléphone de destination (passé
dans une chaîne). Nous déclarons un pointeur pAddr
(qui pointe à l'origine vers la même adresse que
bDest) ; nous faisons en sorte que le nettoyage de la
mémoire ne déplace pas cette variable de façon
imprévisible. En dehors des parenthèses qui
l'enferment, pAddr n'est plus fixe et son accès n'est pas
fiable.
Byte[] bDest =
new Byte[516];
fixed (byte*
pAddr = bDest)
Nous écrivons maintenant la première partie de
PhoneAddress dans notre IntPtr en indiquant qu'il s'agit d'un
format inconnu (le système d'exploitation décidera
pour nous). Nous utilisons WriteInt32 parce que nous
accédons directement à la mémoire.
byte *pCurrent = pAddr;
Marshal.WriteInt32((IntPtr)pCurrent, (
int
)AddressType.Unknown);
pCurrent +=4;
À présent, nous segmentons en octets la
chaîne de représentation de notre numéro de
destination et nous écrivons ces octets dans notre
structure de type PhoneAddress, mais en incluant le
décalage de 4 octets nécessaire à
l'alignement de la structure.
foreach (byte b
in
Encoding.Unicode.GetBytes(sPhoneNumber))
{
Marshal.WriteByte((IntPtr)pCurrent, b);
pCurrent++;
}
Comme nous ne souhaitons pas placer d'option sur la
structure de données du fournisseur, nous allouons de
l'espace pour sa structure de représentation et gardons
ces champs vides.
Byte[] ProvData =
new Byte[12];
Puis nous segmentons notre message SMS (passé dans une
chaîne) en octets que nous pouvons transmettre.
byte[] bMessage = Encoding.Unicode.GetBytes(sMessage);
int
nMsgSize = bMessage.Length;
Ensuite, nous appelons la fonction qui utilise les variables
que nous avons créées précédemment.
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);
Si cet appel renvoie autre chose que zéro, nous savons
que nous avons rencontré des problèmes. Enfin, nous
appelons SmsClose pour clore correctement cette session.
Envoi de SMS en VB.NET
Notre code pour VB.NET est un portage direct de ce que nous
avons fait en C# ; par conséquent, nous nous
contenterons de souligner les différences de syntaxe
importantes. Toutes nos énumérations peuvent
être copiées sur C#, sauf la syntaxe propre à
VB.NET. Nous utilisons des variables IntPtr pour définir
la valeur de la plupart de nos fonctions. Nous appelons SmsOpen
comme précédemment et recherchons les erreurs.
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
Ensuite, nous mettons les représentations d'AddressType
et d'address sous forme d'octets et remplissons la variable
smsAddressTag comme s'il s'agissait de notre structure
PhoneAddress.
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)
Puis nous segmentons notre chaîne de message en octets
et remplissons un tableau d'octets avec ces éléments.
Nous pouvons alors transmettre toutes nos variables au travers
de 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)
Nous vérifions les erreurs puis effectuons le
renvoi.
Utilisation du code
Pour envoyer un SMS à partir de votre code C#, appelez
notre code de la façon suivante :
Microsoft.Wireless.SMS.SendMessage("Phone Number","Message");
To send an SMS from within your VB.NET code, call our code by:
Conclusion
Cet article avait pour intention de montrer avec quelle
simplicité vous pouvez appeler des API Win32 natives
importantes à partir de C# et de VB.NET. De plus, en
modifiant très légèrement la syntaxe, il est
possible d'effectuer un « portage » de C#
vers VB.NET en un temps record. Notons que la portabilité
souligne l'une des caractéristiques les plus
séduisantes de .NET Compact Framework : sa
capacité à créer des objets dans un langage et
de les utiliser dans un autre langage compatible avec .NET.
Nous aurions pu créer notre code principal en C# et
l'envelopper dans un Windows Form créé en VB.NET.
Dernière
mise à jour le lundi 5 mai 2003