| 适用于: Microsoft® .NET Framework 精简版 1.0
 Microsoft Visual Studio® .NET 2003
 摘要:学习如何从基于 
            .NET Framework 精简版的应用程序访问电话 API。 下载 PhoneAPI.msi(英文) 目录简介本文旨在通过另外一个示例,说明在用 
            C# 和 VB.NET 编写的托管代码中调用本机 Microsoft® 
            Win32® API 是大有裨益的。下面,我们看看 Pocket PC 
            Phone Edition 支持的通用电话 API。 使用 
            C# 创建电话呼叫本文下面所引用的所有代码都可以从此处下载(英文)。创建呼叫是一个基本操作;我们传递 
            PhoneMakeCall (Win32 API),它是一个字符串,指示目标地址以及是否需要在呼叫前确认等操作。我们需要进行多项声明。 private static long PMCF_DEFAULT               = 0x00000001;
private static long PMCF_PROMPTBEFORECALLING   = 0x00000002;
 然后定义一个对我们的目的用途不太大的结构。 private struct PhoneMakeCallInfo
   {
      public IntPtr cbSize;
      public IntPtr dwFlags;
      public IntPtr pszDestAddress;
      public IntPtr pszAppName;
      public IntPtr pszCalledParty;
      public IntPtr pszComment;
   }
cbSize 表明 PhoneMakeCallInfo 结构的大小。dwFlags 
            是一个选项位,用于指定呼叫前是否提示用户。pszDestAddress 
            是一个指针,指向要拨打的电话号码。当前不支持 
            pszAppName。pszCalledParty 
            为可选项,表示被叫方的姓名,大小有限。当前不支持 
            pszComment。现在,我们激活 PInvoke 并调用 DLLImport,以便访问 
            API 函数 PhoneMakeCall。 [DllImport("phone.dll")]
private static extern IntPtr PhoneMakeCall(ref PhoneMakeCallInfo ppmci);
为了方便起见,我们包含了一个辅助函数,以便省略拨号前确认 
            Boolean。 /// <summary>
/// 拨打指定的电话号码。
/// </summary>
/// <param name="PhoneNumber">要拨打的电话号码。</param>
public static void MakeCall(string PhoneNumber)
{
MakeCall(PhoneNumber, false);
}
下面介绍一下 MakeCall 
            的功能。简而言之,我们将 PhoneNumber 
            参数(作为字符串传递)分割成一个字符数组。 PhoneNumber += '\0';
char[] cPhoneNumber = PhoneNumber.ToCharArray();
 指向字符数组在内存中的内存地址,并为此变量加上 
            fixed 
            关键字,表示不希望内存回收器移动固定括号内的内容。    PhoneMakeCallInfo info = new PhoneMakeCallInfo();
   info.cbSize = (IntPtr)Marshal.SizeOf(info);
   info.pszDestAddress = (IntPtr)pAddr;
   if (PromptBeforeCall)
   {
      info.dwFlags = (IntPtr)PMCF_PROMPTBEFORECALLING;
   }
   else
   {
      info.dwFlags = (IntPtr)PMCF_DEFAULT;
   }
创建一个新的 PhoneMakeCallInfo 
            结构实例,指示是否希望在拨号之前进行确认以及是否将电话号码插入 
            pszDestAddress 
            字段。最后,我们将该结构实例作为引用传入 
            PhoneMakeCall。此函数使用 unsafe 
            关键字进行了修饰,因为它使用指针和直接内存访问。 使用 
            VB.NET 创建电话呼叫用 VB.NET 编写的 MakeCall 代码与使用 C# 
            编写的代码相似(详细代码如上文所示),几乎不需要太大的改动。我们使用 
            IntPtr 
            变量保存多数函数的值。两者的不同之处在于,在 
            MakeCall 的前向声明中,我们指定 PhoneMakeCallInfo 
            结构实例将作为引用传递。     <System.Runtime.InteropServices.DllImport("phone.dll")> _
    Private Shared Function PhoneMakeCall(ByRef ppmci As 
      PhoneMakeCallInfo) As IntPtr
    End Function
我们基本上按照前面的方法处理 
            PhoneMakeCallInfo 结构。将 PhoneNumber 
            字符串分割为一个字符数组,然后将 iPhoneNumber 
            用作内存调整指针写入内存。 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
将 pszDestAddress 
            成员指向此内存空间并设置拨号前确认选项后,将此结构实例传入 
            PhoneMakeCall。 If PromptBeforeCall Then
   info.dwFlags = PMCF_PROMPTBEFORECALLING
Else
   info.dwFlags = PMCF_DEFAULT
End If
   res = PhoneMakeCall(info)
 使用 
            C# 访问 SIM 信息虽然称不上是对 Pocket PC Phone Edition 
            平台上的可用 SIM 
            访问函数的详细介绍,但我们将通过介绍如何检索 
            SIM 
            所有者的主要电话号码和服务提供程序的名称,进一步说明 
            P/Invoke 的用途。我们首先定义一个结构,用以存放 
            ping SIM 卡时获得的值,如下所示。       [StructLayout(LayoutKind.Sequential)]
      private struct SimRecord 
      {
         public IntPtr cbSize;
         public IntPtr dwParams;
         public IntPtr dwRecordType;
         public IntPtr dwItemCount;
         public IntPtr dwSize;
      }
因为在本机代码和托管代码之间只能自动封送顺序布局结构,所以我们用顺序布局标记修饰我们的结构。cbSize 
            是所传递结构的大小。dwParams 
            是参数值,这里我们并不用担心。dwRecordType 
            表示记录的格式。选项包括: 
              
                
                  | 值 | 说明 |  
                  | SIM_RECORDTYPE_UNKNOWN | 未知的文件类型。 |  
                  | SIM_RECORDTYPE_TRANSPARENT | 单个变长记录。 |  
                  | SIM_RECORDTYPE_CYCLIC | 循环记录集,每个记录具有相同长度。 |  
                  | SIM_RECORDTYPE_LINEAR | 线性记录集,每个记录具有相同长度。 |  
                  | SIM_RECORDTYPE_MASTER | 每个 SIM 
                    都有一个主记录,实际上是主节点。 |  
                  | SIM_RECORDTYPE_DEDICATED | 实际上是作为其他记录的上级记录的“目录”文件。 |  dwItemCount 是记录中的项数。dwSize 
            是记录中每项的大小。接下来,我们将有许多利用了 
            DLLImport 的函数声明。       [DllImport("sms.dll")]
      private static extern IntPtr SmsGetPhoneNumber(IntPtr psmsaAddress);
检索 SIM 所有者的电话号码。       [DllImport("cellcore.dll")]
      private static extern IntPtr SimInitialize(IntPtr dwFlags, IntPtr 
        lpfnCallBack, IntPtr dwParam, out IntPtr lphSim);
此函数是调用任何 SIM 
            访问函数所必需的。如果执行成功,则返回一个指向 
            HSIM 句柄的指针,以便在随后的调用中使用。       [DllImport("cellcore.dll")]
      private static extern IntPtr SimGetRecordInfo(IntPtr hSim, IntPtr
        dwAddress, ref SimRecord lpSimRecordInfo);
此函数接受从 SimInitialize 返回的 HSIM 
            句柄以及一个 SIM 地址 (dwAddress) 和一个 SIM 
            记录结构,并用请求的信息填充此结构。       [DllImport("cellcore.dll")]
      private static extern IntPtr SimReadRecord(IntPtr hSim, IntPtr
        dwAddress, IntPtr dwRecordType, IntPtr dwIndex, byte[] lpData,
        IntPtr dwBufferSize, ref IntPtr lpdwBytesRead);
此函数接受从 SimInitialize 返回的 HSIM 
            句柄,以及一个 SIM 地址 (dwAddress)、一个 dwRecordType(请参见上表)、dwIndex(如果使用 
            SIM_RECORDTYPE_CYCLIC 或 SIM_RECORDTYPE_LINEAR)、lpData(指向数据缓冲区)、dwBufferSize(表示缓冲区大小)以及 
            lpdwBytesRead(表示要读取的字节数)。       [DllImport("cellcore.dll")]
      private static extern IntPtr SimDeinitialize(IntPtr hSim );
此函数发布在 SimInitialize 中创建的 HSIM 
            句柄的资源。 我们将实现两种重要的电话 API 
            调用:获取当前 SIM 所有者的电话号码,并获取 SIM 
            当前访问的服务提供程序的名称。 在 GetPhoneNumber 
            中,我们所做的只是定义一个大型字节缓冲区并将其传入 
            SmsGetPhoneNumber。          Byte[] buffer = new Byte[516];
         fixed (byte* pAddr = buffer)
         {
            IntPtr res = SmsGetPhoneNumber((IntPtr)pAddr);
            if (res != IntPtr.Zero)
               throw new Exception("无法从 SIM 中获取电话号码");
然后获取返回的 PhoneAddress 类型和 
            PhoneAddress 电话号码。          byte *pCurrent = pAddr;
         phoneaddr.AddressType = 
           (AddressType)Marshal.ReadInt32((IntPtr)pCurrent);
         pCurrent += Marshal.SizeOf(phoneaddr.AddressType);
         phoneaddr.Address = Marshal.PtrToStringUni((IntPtr)pCurrent);
      }
此函数声明为 unsafe,因为我们是直接从内存中的托管代码读取。 在下一个函数 GetServiceProvider 
            中,我们首先初始化 SIM          res = SimInitialize(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out 
           hSim);
         if (res != IntPtr.Zero)
            throw new Exception("无法初始化 SIM");
然后声明一个 SIM 
            记录结构实例,并请求在调用 SimGetRecordInfo时检索包含 SERVICE_PROVIDER 的值的 SimRecord 的句柄。          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("无法从 SIM 中读取服务提供程序的信息");
然后,我们分配一个字节数组,用于存储 
            SIM 记录(包含 SERVICE_PROVIDER 详细信息)的内容。          byte[] bData = new byte[(int)rec.dwSize + 1];
         IntPtr dwBytesRead = IntPtr.Zero;
然后请求读取 SERVICE_PROVIDER 
            记录的内容。       res = SimReadRecord(hSim, (IntPtr)SERVICE_PROVIDER, 
        rec.dwRecordType, IntPtr.Zero, bData, (IntPtr)bData.Length, ref 
        dwBytesRead);
      if (res != IntPtr.Zero)
         throw new Exception("无法从 SIM 中读取服务提供程序");
然后查看返回的结构,并删除在显示最终结果时可能会破坏代码的任何非标准 
            ASCII 字符。          byte[] bScrubbed = new byte[(int)dwBytesRead];
         int nPos = 0;
         // 删除非 ASCII 字符
         for (int i = 0; i < (int)dwBytesRead; i ++)
         {
            if (((int)bData[i] > 19) && ((int)bData[i] < 125))
            {
               bScrubbed[nPos] = bData[i];
               nPos++;
            }
         }
然后取消初始化 SIM 并释放其资源。       SimDeinitialize(hSim);
 最后,返回“已删除”字节缓冲区的字符串表示形式。 使用 
            VB.NET 访问 SIM 信息初始类声明与 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 
  IntPtr
    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
GetPhoneNumber 函数相当于 C# 
            实现。首先创建缓冲区空间,并使用 P/Invoke 调用 
            SmsGetPhoneNumber 函数。    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
   Try
      res = SmsGetPhoneNumber(ipAddr)
   Catch ex As Exception
      MessageBox.Show(ex.Message)
   End Try
   If (res.ToInt32 <> 0) Then
      Throw New Exception("无法从 SIM 中获取电话号码")
   End If
然后从返回的结构中获取地址类型信息。    phoneaddr.AddressType = 
     System.Runtime.InteropServices.Marshal.ReadInt32(ipAddr)
将返回的电话号码缓冲区转换为一个字符串,然后返回完整的 
            PhoneAddress 结构。 GetServiceProvider 函数的情况也与 C# 
            版本中的情况非常相似。    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("无法初始化 SIM。")
   End If
首先初始化 SIM,以便从中检索数据。    Dim rec As SimRecord = New SimRecord
   rec.cbSize = 
     Marshal.AllocHLocal(System.Runtime.InteropServices
       .Marshal.SizeOf(temp))
   rec.cbSize = IntPtr.op_Explicit(System.Runtime.InteropServices
     .Marshal.SizeOf(rec))
创建一个新的 SimRecord 
            结构实例且只设置 cbSize 成员(并用 SimRecord 
            结构的大小填充该成员)。    res = SimGetRecordInfo(hSim, IntPtr.op_Explicit(SERVICE_PROVIDER), rec)
   If (res.ToInt32 <> 0) Then
      Throw New Exception("无法从 SMS 中读取服务提供程序的
信息。")
   End If
调用 SimGetRecordInfo,获取包含 
            SERVICE_PROVIDER 数据的 SIM 记录的句柄。    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("无法从 SMS 中读取服务提供程序。")
End If
然后,正如在 C# 
            代码中执行的操作一样,必须从产生的字节缓冲区中删除任何非 
            ASCII 字符,然后转换为字符串并返回该字符串值。         Dim bScrubbed(dwBytesRead.ToInt32) As Byte
        Dim nPos As Int32 = 0
        Dim i As Int32
        '删除非 ASCII 字符
        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
        SimDeinitialize(hSim)
        Return System.Text.ASCIIEncoding.ASCII.GetString(bScrubbed, 0,
          bScrubbed.Length)
代码使用要使用 C# 
            从代码中创建电话呼叫,请调用: Microsoft.Wireless.Phone.MakeCall("电话号码");
 要使用 C# 从代码中获取 SIM 
            用户的电话号码,请调用: Microsoft.Wireless.Sim.GetPhoneNumber()(该函数返回一个字符串)
 要使用 C# 从代码中获取 SIM 
            用户的服务提供程序,请调用: Microsoft.Wireless.Sim.GetServiceProvider()(该函数返回一个字符串)
 要使用 VB.NET 
            从代码中创建电话呼叫,请调用: Phone.MakeCall("电话号码");
 要使用 VB.NET 从代码中获取 SIM 
            用户的电话号码,请调用: Sim.GetPhoneNumber()(该函数返回一个字符串)
 要使用 VB.NET 从代码中获取 SIM 
            用户的服务提供程序,请调用: Sim.GetServiceProvider()(该函数返回一个字符串)
 小结本文旨在进一步说明从托管代码中调用重要的 
            Win32 API 
            函数类是非常有用且非常容易的。文中简要介绍了电话 
            API 类的使用。很明显,使用 P/Invoke 可以更好地利用 
            Win32 API 编程的功能。 
            
            
 
 |