Accessing Phone APIs from the Microsoft .NET Compact Framework

.NET Compact Framework 1.0

Derek Mitchell

April 2003

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

Summary: Learn how to access Phone APIs from your .NET Compact Framework-based application. (11 printed pages)

Download PhoneAPI.msi


Making a Phone Call in C#
Making a Phone Call in VB.NET
Accessing SIM Information in C#
Accessing SIM Information in VB.NET
Code Use


The purpose of this article is to provide another example in which calling native Microsoft® Win32® APIs is beneficial within the scope of managed code, written in both C# and VB.NET. Here, we look at common Phone APIs supported by both Pocket PC Phone Editions.

Making a Phone Call in C#

All of the code you will see referenced below is available for download. Making a call is a basic operation; we pass PhoneMakeCall (Win32 API), a string indicating the destination address and any options regarding whether or not we should ask for confirmation before placing the call. We have several declarations to make.

private static long PMCF_DEFAULT               = 0x00000001;
private static long PMCF_PROMPTBEFORECALLING   = 0x00000002;

We next define a structure that will go largely unused for our purposes.

private struct PhoneMakeCallInfo
      public IntPtr cbSize;
      public IntPtr dwFlags;
      public IntPtr pszDestAddress;
      public IntPtr pszAppName;
      public IntPtr pszCalledParty;
      public IntPtr pszComment;

cbSize indicates the size of the PhoneMakeCallInfo structure. dwFlags is an option bit that specifies whether the user should be prompted before the call is placed. pszDestAddress is a pointer to the number to be dialed. pszAppName is currently unsupported. pszCalledParty is optional and can indicate the name of the party to be called and is limited in size. pszComment is currently unsupported. Now we call forth the magic of PInvoke and make a DLLImport call to give us access to our API function PhoneMakeCall.

private static extern IntPtr PhoneMakeCall(ref PhoneMakeCallInfo ppmci);

We include an auxiliary function for convenience that bypasses the need for a confirm-before-dial Boolean.

/// <summary>
/// Dials the specified phone number.
/// </summary>
/// <param name="PhoneNumber">Phone number to dial.</param>
public static void MakeCall(string PhoneNumber)
MakeCall(PhoneNumber, false);

Now let's take a look at what MakeCall does. In short, we break our PhoneNumber parameter (passed in as a string) into a character array.

PhoneNumber += '\0';
char[] cPhoneNumber = PhoneNumber.ToCharArray();

We point to the memory address at which the character array appears in memory and decorate this variable with the fixed keyword to indicate we don't want the garbage collector to move its contents within the scope of the fixed brackets.

   PhoneMakeCallInfo info = new PhoneMakeCallInfo();
   info.cbSize = (IntPtr)Marshal.SizeOf(info);
   info.pszDestAddress = (IntPtr)pAddr;

   if (PromptBeforeCall)
      info.dwFlags = (IntPtr)PMCF_PROMPTBEFORECALLING;
      info.dwFlags = (IntPtr)PMCF_DEFAULT;

We create a new PhoneMakeCallInfo structure instance and indicate whether or not we want confirmation before dialing and we insert the phone number into the pszDestAddress field. Finally, we pass the structure instance into PhoneMakeCall as a reference. This function is decorated with the unsafe keyword because it utilizes pointers and direct memory access.

Making a Phone Call in VB.NET

Our code for MakeCall in VB.NET ports with few major modifications from the C# code detailed above. We use IntPtr variables to hold value for most of our functionality. One difference is that in the forward declaration of MakeCall, we specify that the PhoneMakeCallInfo structure instance will be passed in as a reference.

    <System.Runtime.InteropServices.DllImport("phone.dll")> _
    Private Shared Function PhoneMakeCall(ByRef ppmci As 
      PhoneMakeCallInfo) As IntPtr
    End Function

We process the PhoneMakeCallInfo structure almost identically to as before. We break the PhoneNumber string into a character array then write to memory using iPhoneNumber as a memory alignment pointer.

PhoneNumber.Insert(PhoneNumber.Length, " ")
Dim cPhoneNumber() As Char = PhoneNumber.ToCharArray()
Dim pAddr() As Char = cPhoneNumber

Dim info As PhoneMakeCallInfo = New PhoneMakeCallInfo
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(info)
Dim iPhoneNumber As IntPtr = Marshal.AllocHLocal(cPhoneNumber.Length)
        System.Runtime.InteropServices.Marshal.Copy(cPhoneNumber, 0, 
          iPhoneNumber, cPhoneNumber.Length)
info.pszDestAddress = iPhoneNumber

After we point the pszDestAddress member to this memory space and set the confirm before dial option, we pass the structure instance into PhoneMakeCall.

If PromptBeforeCall Then
   info.dwFlags = PMCF_DEFAULT
End If
   res = PhoneMakeCall(info)

Accessing SIM Information in C#

Though by no means an exhaustive exploration of the SIM access functions available on the Pocket PC Phone Edition platform, we will take a look at how you can retrieve the SIM owner's primary phone number and service provider name, to further drive home the usefulness of P/Invoke. First we define a structure to hold the values received from pinging the SIM card as follows.

      private struct SimRecord 
         public IntPtr cbSize;
         public IntPtr dwParams;
         public IntPtr dwRecordType;
         public IntPtr dwItemCount;
         public IntPtr dwSize;

Because only sequential-layout structures can be marshaled automatically between native and managed code, we decorate our structure with the sequential layout tag. cbSize is the size of the structure passed. dwParams are parameter values we won't worry about here. dwRecordType indicates the form of the record. Options include:

Value Description
SIM_RECORDTYPE_UNKNOWN An unknown file type.
SIM_RECORDTYPE_TRANSPARENT A single variable-length record.
SIM_RECORDTYPE_CYCLIC A cyclic set of records, each of the same length.
SIM_RECORDTYPE_LINEAR A linear set of records, each of the same length.
SIM_RECORDTYPE_MASTER Every SIM has a single master record, effectively the head node.
SIM_RECORDTYPE_DEDICATED Effectively a "directory" file that is a parent of other records.

dwItemCount is the number of items in the record. dwSize is the size of each item in the record. Next we have a bunch of function declarations that utilize DLLImport.

      private static extern IntPtr SmsGetPhoneNumber(IntPtr psmsaAddress);

Retrieves SIM owner's phone number.

      private static extern IntPtr SimInitialize(IntPtr dwFlags, IntPtr 
        lpfnCallBack, IntPtr dwParam, out IntPtr lphSim);

This function is required to call any of the SIM access functions. A pointer to an HSIM handle is returned upon successful execution, to be used with subsequent calls.

      private static extern IntPtr SimGetRecordInfo(IntPtr hSim, IntPtr
        dwAddress, ref SimRecord lpSimRecordInfo);

This function takes in the HSIM handle returned from SimInitialize along with a SIM address (dwAddress) and a SIM record structure and fills the structure with the requested information.

      private static extern IntPtr SimReadRecord(IntPtr hSim, IntPtr
        dwAddress, IntPtr dwRecordType, IntPtr dwIndex, byte[] lpData,
        IntPtr dwBufferSize, ref IntPtr lpdwBytesRead);

This function takes in the HSIM handle returned from SimInitialize along with a SIM address (dwAddress), a dwRecordType (see table above), dwIndex (if SIM_RECORDTYPE_CYCLIC or SIM_RECORDTYPE_LINEAR are used), lpData pointing to the data buffer, dwBufferSize indicating the buffer size, and lpdwBytesRead, indicating the number of bytes to read.

      private static extern IntPtr SimDeinitialize(IntPtr hSim );

This function releases the resources of the HSIM handle created in SimInitialize.

We will implement two important Phone API calls: obtain the phone number of the current SIM owner and get the service provider name currently being accessed by the SIM.

In GetPhoneNumber, all we do is define a large byte buffer and pass it into SmsGetPhoneNumber.

         Byte[] buffer = new Byte[516];
         fixed (byte* pAddr = buffer)
            IntPtr res = SmsGetPhoneNumber((IntPtr)pAddr);
            if (res != IntPtr.Zero)
               throw new Exception("Could not get phone number from SIM");

And then we pick off both the returned PhoneAddress type and PhoneAddress phone number.

         byte *pCurrent = pAddr;
         phoneaddr.AddressType = 
         pCurrent += Marshal.SizeOf(phoneaddr.AddressType);
         phoneaddr.Address = Marshal.PtrToStringUni((IntPtr)pCurrent);

This function is declared unsafe as we're directly reading from memory from managed code.

In our next function, GetServiceProvider, we first initialize the SIM

         res = SimInitialize(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out 
         if (res != IntPtr.Zero)
            throw new Exception("Could not initialize SIM");

Then, declare a SIM record structure instance and request that we retrieve a handle to the SimRecord holding the value of the SERVICE_PROVIDER when we call SimGetRecordInfo.

         SimRecord rec = new SimRecord();
         rec.cbSize = (IntPtr)Marshal.SizeOf(rec);

         res = SimGetRecordInfo(hSim, (IntPtr)SERVICE_PROVIDER, ref rec);
         if (res != IntPtr.Zero)
            throw new Exception("Could not read the service provider 
              information from the SIM");

We then allocate a byte array to hold the contents of the SIM record with the SERVICE_PROVIDER details.

         byte[] bData = new byte[(int)rec.dwSize + 1];
         IntPtr dwBytesRead = IntPtr.Zero;

Then, request to read the contents of the SERVICE_PROVIDER record.

      res = SimReadRecord(hSim, (IntPtr)SERVICE_PROVIDER, 
        rec.dwRecordType, IntPtr.Zero, bData, (IntPtr)bData.Length, ref 
      if (res != IntPtr.Zero)
         throw new Exception("Could not read the service provider from the 

Then, go through the structure returned and remove any non-standard ASCII characters that could break our code when we display the final result.

         byte[] bScrubbed = new byte[(int)dwBytesRead];
         int nPos = 0;

         // Scrub the non-ascii characters
         for (int i = 0; i < (int)dwBytesRead; i ++)
            if (((int)bData[i] > 19) && ((int)bData[i] < 125))
               bScrubbed[nPos] = bData[i];

Then, we de-initialize the SIM and free up its resources.


And finally, we return the string representation of our "scrubbed" byte buffer.

Accessing SIM Information in VB.NET

Our initial class declarations are the same as what we saw in C#:

    Private Shared SERVICE_PROVIDER As Long = &H6F46

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure SimRecord
        Public cbSize As IntPtr
        Public dwParams As IntPtr
        Public dwRecordType As IntPtr
        Public dwItemCount As IntPtr
        Public dwSize As IntPtr
    End Structure

    <System.Runtime.InteropServices.DllImport("sms.dll")> _
Private Shared Function SmsGetPhoneNumber(ByVal psmsaAddress As IntPtr) As 
    End Function

    <System.Runtime.InteropServices.DllImport("cellcore.dll")> _
Private Shared Function SimInitialize(ByVal dwFlags As IntPtr, ByVal 
  lpfnCallBack As IntPtr, ByVal dwParam As IntPtr, ByRef lphSim As IntPtr) 
  As IntPtr
    End Function

    <System.Runtime.InteropServices.DllImport("cellcore.dll")> _
Private Shared Function SimGetRecordInfo(ByVal hSim As IntPtr, ByVal 
  dwAddress As IntPtr, ByRef lpSimRecordInfo As SimRecord) As IntPtr
    End Function

    <System.Runtime.InteropServices.DllImport("cellcore.dll")> _
Private Shared Function SimReadRecord(ByVal hSim As IntPtr, ByVal 
  dwAddress As IntPtr, ByVal dwRecordType As IntPtr, _
ByVal dwIndex As IntPtr, ByVal lpData() As Byte, ByVal dwBufferSize As 
  IntPtr, ByRef lpdwBytesRead As IntPtr) As IntPtr
    End Function

    <System.Runtime.InteropServices.DllImport("cellcore.dll")> _
Private Shared Function SimDeinitialize(ByVal hSim As IntPtr) As IntPtr
    End Function

Our GetPhoneNumber function parallels well with the C# implementation. First we create our buffer space and call the SmsGetPhoneNumber function using P/Invoke.

   Dim phoneaddr As PhoneAddress = New PhoneAddress
   Dim buffer(512) As Byte
   Dim pAddr() As Byte = buffer
   Dim ipAddr As IntPtr = Marshal.AllocHLocal(pAddr.Length)
   Dim res As IntPtr = IntPtr.Zero

      res = SmsGetPhoneNumber(ipAddr)
   Catch ex As Exception
   End Try

   If (res.ToInt32 <> 0) Then
      Throw New Exception("Could not get phone number from SIM")
   End If

Next, we pick off the address type information from the returned structure.

   phoneaddr.AddressType = 

And cast the returned phone number buffer into a string and we return the complete PhoneAddress structure.

The GetServiceProvider function is also quite similar in behavior to our C# version.

   Dim hSim, res As IntPtr
   hSim = IntPtr.Zero
   Dim temp As Long

   res = SimInitialize(IntPtr.Zero, Nothing, IntPtr.Zero, hSim)
   If (res.ToInt32 <> 0) Then
      Throw New Exception("Could not initialize SMS.")
   End If

First, we initialize the SIM so we can retrieve data from it.

   Dim rec As SimRecord = New SimRecord
   rec.cbSize = 
   rec.cbSize = IntPtr.op_Explicit(System.Runtime.InteropServices

We create a new SimRecord structure instance and set only the cbSize member (and fill it with the size of the SimRecord structure).

   res = SimGetRecordInfo(hSim, IntPtr.op_Explicit(SERVICE_PROVIDER), rec)
   If (res.ToInt32 <> 0) Then
      Throw New Exception("Could not read service provider info from 
   End If

We call SimGetRecordInfo to get a handle to the SIM record containing the SERVICE_PROVIDER data.

   Dim bData((rec.dwSize).ToInt32 + 1) As Byte
   Dim dwBytesRead As IntPtr = IntPtr.Zero

   res = SimReadRecord(hSim, IntPtr.op_Explicit(SERVICE_PROVIDER),
     rec.dwRecordType, IntPtr.Zero, bData, 
     IntPtr.op_Explicit(bData.Length), dwBytesRead)
   If (res.ToInt32 <> 0) Then
     Throw New Exception("Could not read service provider  from SMS.")
End If

Then, as we had to do in the C# code, we must strip any non-ASCII characters from the resulting byte buffer before we convert to string and return that string value.

        Dim bScrubbed(dwBytesRead.ToInt32) As Byte
        Dim nPos As Int32 = 0
        Dim i As Int32

        'Scrub the non-ascii characters
        For i = 0 To dwBytesRead.ToInt32
            If bData(i) > 19 And bData(i) < 125 Then
                bScrubbed(nPos) = bData(i)
                nPos = nPos + 1
            End If
        Next i


        Return System.Text.ASCIIEncoding.ASCII.GetString(bScrubbed, 0,

Code Use

To make a phone call from your code using C#, call

Microsoft.Wireless.Phone.MakeCall("Phone number");

To get the SIM user's phone number from your code using C#, call:

Microsoft.Wireless.Sim.GetPhoneNumber() (which returns a string)

To get the service provider of the SIM user from your code using C#, call:

Microsoft.Wireless.Sim.GetServiceProvider() (which returns a string)

To make a phone call from your code using VB.NET, call

Phone.MakeCall("Phone number");

To get the SIM user's phone number from your code using VB.NET, call:

Sim.GetPhoneNumber() (which returns a string)

To get the service provider of the SIM user from your code using VB.NET, call:

Sim.GetServiceProvider() (which returns a string)


This document served to further illustrate the usefulness and ease of use with which one can call important classes of Win32 API functions from managed code. Here, we illustrated the use of the Phone API class, though we only scratched the surface. It should be apparent that by using P/Invoke, one can leverage the power of Win32 API programming to a high degree.