RAPI 功能示例与说明
扫描二维码
随时随地手机看文章
先来 RAPI 概述
Windows CE 有了 RAPI 以后,PC 应用程序就能远程管理 Windows CE 设备。
导出函数将处理文件系统、注册表和数据库以及用于查询系统配置的函数。
大多数 RAPI 是 Windows CE API 函数中的副本,只有少数的函数扩展了 API。
RAPI 函数被列在 Windows CE API 参考中,但是将有 PC 应用程序来调用而不是由 Widows CE 应用程序调用。
在函数的名称都有前缀 Ce 来与他们的 Window CE 的副本进行区分;
例如:Windows CE 中的函数 GetStoreInformation 将在该函数的 RAPI 版本中称为 CeGetStoreInformation。
在 Windows CE 应用程序将不会调用 RAPI 函数。
知道大家不想看文字,直接上示例(功能:复制文件到设备上)代码:
RAPI 被封装为一个类,先看头文件。
CeRAPI.h
// CeRAPI.h: interface for the CeRAPI class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_VOGUERAPI_H__10D13979_C6F2_4922_A4D7_B105644F969F__INCLUDED_) #define AFX_VOGUERAPI_H__10D13979_C6F2_4922_A4D7_B105644F969F__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #define BUFFER_SIZE 10240 typedef struct _RAPIINIT { DWORD cbSize; HANDLE heRapiInit; HRESULT hrRapiInit; } RAPIINIT; typedef struct _CE_FIND_DATA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwOID; WCHAR cFileName[MAX_PATH]; } CE_FIND_DATA, *LPCE_FIND_DATA; class CCeRAPI { public: HINSTANCE hInst; typedef HANDLE (FAR PASCAL * pfnFunc0)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); typedef BOOL (FAR PASCAL * pfnFunc1)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED); typedef BOOL (FAR PASCAL * pfnFunc2)(HANDLE); typedef HANDLE (FAR PASCAL * pfnFunc3)(LPCWSTR, LPCE_FIND_DATA); typedef DWORD (FAR PASCAL * pfnFunc4)(HANDLE, LPDWORD); typedef BOOL (FAR PASCAL * pfnFunc5)(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED); typedef BOOL (FAR PASCAL * pfnFunc6)(HANDLE, LPCE_FIND_DATA); typedef BOOL (FAR PASCAL * pfnFunc7)(LPCWSTR, LPSECURITY_ATTRIBUTES); typedef BOOL (FAR PASCAL * pfnFunc8)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPWSTR, LPSTARTUPINFO, LPPROCESS_INFORMATION); typedef VOID (FAR PASCAL * pfnFunc9)(LPSYSTEM_INFO); typedef HRESULT (FAR PASCAL *pfnFnucA)(RAPIINIT*); typedef BOOL (FAR PASCAL *pfnFuncB)(LPCWSTR,LPCWSTR,BOOL); typedef DWORD (FAR PASCAL *pfnFuncC)(); typedef HRESULT (FAR PASCAL *pfnFuncD)(); pfnFunc0 CeCreateFile; pfnFunc1 CeWriteFile; pfnFunc2 CeCloseHandle; pfnFunc3 CeFindFirstFile; pfnFunc4 CeGetFileSize; pfnFunc5 CeReadFile; pfnFunc6 CeFindNextFile; pfnFunc7 CeCreateDirectory; pfnFunc8 CeCreateProcess; pfnFunc9 CeGetSystemInfo; FARPROC CeRapiUninit; //FARPROC CeRapiInit; pfnFuncB CeCopyFile; pfnFuncC CeGetLastError; pfnFuncD CeRapiGetError; //Leo Add pfnFnucA CeRapiInitEx; BOOL m_bRapiInitFlag; BOOL RapiConnectDevice(); BOOL CopyFiletoWinCE(CString strFileNamePC, CString strFileNamePPC); BOOL CCeRAPI::CopyFiletoPC(CString csCEFilename,CString csPCFilename); BOOL InitRapi(); // CString GetCStringFromFile(CString strFileName); CCeRAPI(); virtual ~CCeRAPI(); }; #endif // !defined(AFX_VOGUERAPI_H__10D13979_C6F2_4922_A4D7_B105644F969F__INCLUDED_)
类的实现文件:CeRAPI.cpp
// CeRAPI.cpp: implementation of the CeRAPI class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "RapiTest.h" #include "CeRAPI.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CCeRAPI::CCeRAPI() { m_bRapiInitFlag = FALSE; } CCeRAPI::~CCeRAPI() { } BOOL CCeRAPI::InitRapi() { LPTSTR pDllPath = new TCHAR[MAX_PATH + 10]; GetSystemDirectory(pDllPath, MAX_PATH); CString strDllPathName(pDllPath); strDllPathName += "\Rapi.dll"; TRACE("%sn",strDllPathName); //C:WINDOWSsystem32rapi.dll hInst = LoadLibrary(strDllPathName); if(hInst) { //CeRapiInit = (FARPROC) GetProcAddress(hInst, "CeRapiInit"); CeRapiInitEx = (pfnFnucA) GetProcAddress(hInst, "CeRapiInitEx"); CeRapiUninit = (FARPROC) GetProcAddress(hInst, "CeRapiUninit"); CeCreateFile = (pfnFunc0)GetProcAddress(hInst, "CeCreateFile"); CeWriteFile = (pfnFunc1)GetProcAddress(hInst, "CeWriteFile"); CeCloseHandle = (pfnFunc2)GetProcAddress(hInst, "CeCloseHandle"); CeFindFirstFile = (pfnFunc3)GetProcAddress(hInst, "CeFindFirstFile"); CeGetFileSize = (pfnFunc4)GetProcAddress(hInst, "CeGetFileSize"); CeReadFile = (pfnFunc5)GetProcAddress(hInst, "CeReadFile"); CeFindNextFile = (pfnFunc6)GetProcAddress(hInst, "CeFindNextFile"); CeCreateDirectory = (pfnFunc7)GetProcAddress(hInst, "CeCreateDirectory"); CeCreateProcess = (pfnFunc8)GetProcAddress(hInst, "CeCreateProcess"); CeGetSystemInfo = (pfnFunc9)GetProcAddress(hInst, "CeGetSystemInfo"); CeCopyFile = (pfnFuncB)GetProcAddress(hInst, "CeCopyFile"); CeGetLastError = (pfnFuncC)GetProcAddress(hInst, "CeGetLastError"); CeRapiGetError = (pfnFuncD)GetProcAddress(hInst, "CeRapiGetError"); return TRUE; } else { FreeLibrary(hInst); return FALSE; } return FALSE; } BOOL CCeRAPI::CopyFiletoPC(CString csCEFilename,CString csPCFilename) { HANDLE hCEFile; DWORD dwFileSize = 0; int iFileBlockSize = 0; DWORD dwBytes = 0; char cBlockSize[BUFFER_SIZE]; int i = 0; DWORD dwSizeRet = 0 ; CFile PCFile; BSTR bstr = csCEFilename.AllocSysString(); SysFreeString(bstr); hCEFile = CeCreateFile(bstr, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(INVALID_HANDLE_VALUE == hCEFile) { TRACE("==Fail to open device file:%dn",GetLastError()); return FALSE; } dwSizeRet = CeGetFileSize(hCEFile,&dwFileSize); TRACE("==Read file size return value:%dn",dwSizeRet); dwFileSize = dwSizeRet + (dwFileSize << 32); iFileBlockSize = dwFileSize / BUFFER_SIZE; PCFile.Open(csPCFilename,CFile::modeReadWrite | CFile::typeBinary | CFile::modeCreate); for(i = 0;i < iFileBlockSize;i++) { memset(cBlockSize,0,sizeof(char) * BUFFER_SIZE); CeReadFile(hCEFile,cBlockSize,BUFFER_SIZE,&dwBytes,NULL); PCFile.Write(cBlockSize,BUFFER_SIZE); } if(0 != dwFileSize % BUFFER_SIZE) { memset(cBlockSize,0,sizeof(char) * BUFFER_SIZE); CeReadFile(hCEFile,cBlockSize,dwFileSize % BUFFER_SIZE,&dwBytes,NULL); PCFile.Write(cBlockSize,dwFileSize % BUFFER_SIZE); } CeCloseHandle(hCEFile); PCFile.Close(); return TRUE; } BOOL CCeRAPI::CopyFiletoWinCE(CString csPCFilename, CString csCEFilename) { CFile PCFile; HANDLE hCEFile; char cTemp[BUFFER_SIZE]; DWORD dwBytes = 0; int iFileLen = 0; int iFileNum = 0; int i = 0; PCFile.Open(csPCFilename, CFile::modeRead |CFile::typeBinary); iFileLen = PCFile.GetLength(); iFileNum = iFileLen / BUFFER_SIZE; BSTR bstr = csCEFilename.AllocSysString(); SysFreeString(bstr); hCEFile = CeCreateFile(bstr, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(INVALID_HANDLE_VALUE == hCEFile) //文件打开失败 { return FALSE; } for(i = 0;i < iFileNum;i++) { memset(cTemp,0,sizeof(char) * BUFFER_SIZE); if(PCFile.Read(cTemp, BUFFER_SIZE) >= 1) { CeWriteFile(hCEFile, cTemp, BUFFER_SIZE, &dwBytes, NULL); } } if(0 != iFileLen % BUFFER_SIZE) { memset(cTemp,0,sizeof(char) * BUFFER_SIZE); if(PCFile.Read(cTemp, iFileLen % BUFFER_SIZE) >= 1) { CeWriteFile(hCEFile, cTemp, iFileLen % BUFFER_SIZE, &dwBytes, NULL); } } CeCloseHandle(hCEFile); PCFile.Close(); return TRUE; } // CString CVogueRAPI::GetCStringFromFile(CString strFileName) // { // CString strOut; // LPSTR pText = NULL; // int iLen; // CFile f; // if (f.Open(strFileName, CFile::modeReadWrite)) // { // iLen = f.GetLength(); // pText = new char[iLen + 1]; // f.Read(pText, iLen); // pText[iLen] = ' '; // CString str(pText); // strOut = str; // delete pText; // f.Close(); // } // else // { // strOut = "Error"; // } // return strOut; // } BOOL CCeRAPI::RapiConnectDevice() { RAPIINIT struRapiInit; //这个是CeRapiInitEx函数要求的入口参数: 定义在rapi.h中, 为了不include "rapi.h", 将其定义复制到工程中 DWORD dwWaitResult = 0; //等待初始化完成事件的变量 HRESULT hRapiResult = NULL; //CeRapiInitEx的返回HANDLE if(m_bRapiInitFlag == FALSE) //全局的一个标志,如果初始化成功就不再重复 { struRapiInit.cbSize = sizeof(RAPIINIT); //填满该结构体仅有的三个成员 struRapiInit.hrRapiInit = NULL; //明知是输出参数也顺手填一下, 以防万一 struRapiInit.heRapiInit = NULL; hRapiResult = CeRapiInitEx(&struRapiInit); //关键点 dwWaitResult = WaitForSingleObject(struRapiInit.heRapiInit, 2000); //关键点 if(hRapiResult == S_OK && struRapiInit.hrRapiInit == S_OK && dwWaitResult != WAIT_TIMEOUT) //三个返回值都判断, 以保证正确性 { m_bRapiInitFlag = TRUE; return TRUE; } else { return FALSE; } } else { m_bRapiInitFlag = TRUE; return TRUE; } }
调试示例:
// RapiTestDlg.cpp : implementation file // #include "stdafx.h" #include "RapiTest.h" #include "RapiTestDlg.h" #include "CeRapi.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif BOOL gbInitRAPI = FALSE; ///////////////////////////////////////////////////////////////////////////// // CRapiTestDlg dialog CRapiTestDlg::CRapiTestDlg(CWnd* pParent /*=NULL*/) : CDialog(CRapiTestDlg::IDD, pParent) { //{{AFX_DATA_INIT(CRapiTestDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CRapiTestDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CRapiTestDlg) // NOTE: the ClassWizard will add DDX and DDV calls here //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CRapiTestDlg, CDialog) //{{AFX_MSG_MAP(CRapiTestDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(ID_CONNECT, OnConnect) ON_BN_CLICKED(IDC_PCTODEVICE, OnPCToDevice) ON_BN_CLICKED(IDC_EXITAPP, OnExitapp) ON_BN_CLICKED(IDC_DEVICETOPC, OnDeviceToPC) ON_WM_DESTROY() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CRapiTestDlg message handlers BOOL CRapiTestDlg::OnInitDialog() { CDialog::OnInitDialog(); ///////////////////////////////////// CEdit *pDesktop = (CEdit *)GetDlgItem(IDC_DESKTOP_FILE); CEdit *pCE = (CEdit *)GetDlgItem(IDC_CE_FILE); CButton *pToDevice = (CButton *)GetDlgItem(IDC_PCTODEVICE); CButton *pToPC = (CButton *)GetDlgItem(IDC_DEVICETOPC); pDesktop->SetWindowText(_T("F:\NAVoice.exe")); pDesktop->EnableWindow(FALSE); pCE->SetWindowText(_T("\NAVoice.exe")); pCE->EnableWindow(FALSE); pToDevice->EnableWindow(FALSE); pToPC->EnableWindow(FALSE); ///////////////////////////////////// return TRUE; // return TRUE unless you set the focus to a control } void CRapiTestDlg::OnSysCommand(UINT nID, LPARAM lParam) { CDialog::OnSysCommand(nID, lParam); } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CRapiTestDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } } // The system calls this to obtain the cursor to display while the user drags // the minimized window. HCURSOR CRapiTestDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; } void CRapiTestDlg::OnConnect() { gbInitRAPI = rapi.InitRapi(); //打开rapi.dll,导出API if(TRUE == gbInitRAPI) { BOOL bSuccess = rapi.RapiConnectDevice(); //连接设备 if(FALSE == bSuccess) { TRACE("==连接设备错误!n"); } else { CEdit *pDesktop = (CEdit *)GetDlgItem(IDC_DESKTOP_FILE); CEdit *pCE = (CEdit *)GetDlgItem(IDC_CE_FILE); CButton *pToDevice = (CButton *)GetDlgItem(IDC_PCTODEVICE); CButton *pToPC = (CButton *)GetDlgItem(IDC_DEVICETOPC); pDesktop->EnableWindow(TRUE); pCE->EnableWindow(TRUE); pToDevice->EnableWindow(TRUE); pToPC->EnableWindow(TRUE); } } } /* *在设备连接成功后,进行文件传输 */ void CRapiTestDlg::OnPCToDevice() { CEdit *pDesktop = (CEdit *)GetDlgItem(IDC_DESKTOP_FILE); CEdit *pCEDevice = (CEdit *)GetDlgItem(IDC_CE_FILE); CString csDesktopFile; CString csCEFile; pDesktop->GetWindowText(csDesktopFile); pCEDevice->GetWindowText(csCEFile); if(csCEFile.GetLength() > 1 && csDesktopFile.GetLength() > 1) { CString strOut; if(TRUE == rapi.CopyFiletoWinCE(csDesktopFile, csCEFile)) { strOut.Format(_T("File %s was transferred to WinCE as %s:"), csDesktopFile, csCEFile); AfxMessageBox(strOut); } else { strOut.Format(_T("文件打开失败!")); AfxMessageBox(strOut); } } else { AfxMessageBox(_T("Problem with filename's.... nTry Again!!!!")); pDesktop->SetWindowText(_T("")); pCEDevice->SetWindowText(_T("")); pDesktop->SetFocus(); } } void CRapiTestDlg::OnExitapp() { // TODO: Add your control notification handler code here PostQuitMessage(0); } void CRapiTestDlg::OnDeviceToPC() { // TODO: Add your control notification handler code here CEdit *pDesktop = (CEdit *)GetDlgItem(IDC_DESKTOP_FILE); CEdit *pCEDevice = (CEdit *)GetDlgItem(IDC_CE_FILE); CString csDesktopFile; CString csCEFile; pDesktop->GetWindowText(csDesktopFile); pCEDevice->GetWindowText(csCEFile); if(csCEFile.GetLength() > 1 && csDesktopFile.GetLength() > 1) { CString strOut; if(TRUE == rapi.CopyFiletoPC(csCEFile,csDesktopFile)) { strOut.Format(_T("File %s was transferred to PC as %s:"),csCEFile,csDesktopFile); AfxMessageBox(strOut); } else { strOut.Format(_T("文件打开失败!")); AfxMessageBox(strOut); } } else { AfxMessageBox(_T("Problem with filename's.... nTry Again!!!!")); pDesktop->SetWindowText(_T("")); pCEDevice->SetWindowText(_T("")); pDesktop->SetFocus(); } } void CRapiTestDlg::OnDestroy() { CDialog::OnDestroy(); // TODO: Add your message handler code here if(TRUE == gbInitRAPI) { rapi.CeRapiUninit(); } } //DEL void CRapiTestDlg::OnButton1() //DEL { //DEL // TODO: Add your control notification handler code here //DEL BOOL bRet = FALSE; //DEL //DEL bRet = rapi.CeCopyFile(L"\CELongPress.txt",L"\CELongPress2.txt",FALSE); //DEL if(FALSE == bRet) //DEL { //成功 //DEL TRACE("==CeCopyFile failure 1!n"); //DEL } //DEL //DEL bRet = rapi.CeCopyFile(L"F:\CELongPress.txt",L"\CELongPress3.txt",FALSE); //DEL if(FALSE == bRet) //DEL { //失败:3 - 系统找不到指定的路径。 //DEL TRACE("==CeCopyFile failure 2!n"); //DEL } //DEL //DEL bRet = rapi.CeCopyFile(L"\CELongPress.txt",L"F:\CELongPress2.txt",FALSE); //DEL if(FALSE == bRet) //DEL { //失败:3 - 系统找不到指定的路径。 //DEL TRACE("==CeCopyFile failure 3!n"); //DEL } //DEL }
最后来一点函数的说明吧
1.初始化RAPI
调用 RAPI 函数之前,你必须调用函数 CeRapiInit 或 CeRapiInitEx 来初始化 RAPI 库。
这两个函数的区别在于 CeRapiInit 会阻塞(等待与 Windows CE 设备连接成功),而 CeRapiInitEx 不会等待连接成功。
两个函数的原形如下:
HRESULT CeRapiIni(void);
HRESULT CeRapiInitEx(RAPIINIT *pRapiInit);
typedef struct _RAPIINIT
{
DWORD cbSize;
HANDLE heRapiInit;
STDAPI hrRapiInit;
} RAPIINIT;
在调用 CeRapiInitEx 之前,必须填写 cbSize 字段。
2.结束 RAPI 会话
结束所有必要的 RAPI 调用时,你应该调用下面这个函数来进行清除。
函数的原形如下:
HRESULT CeRapiUninit(void);
这个函数很好地关闭与远程设备地 RAPI 通信。如果 RAPI 会话没有被初始化 CeRapiUninit 将返回 E_FAIL。
3.预定义的 RAPI 函数
RAPI 服务包括很多预定义的 RAPI 函数,这些在连接时,PC 端复制 Windows CE 函数。
如GetStoreInformation 将把对象存储哭得大小和空间返回到 Windows CE 程序,CeGetStoreInformation 将把关于连接的 Windows CE 设备的相同信息返回到基于 PC 的应用程序。
3.1 RAPI 系统信息函数
大多数函数都是 Windows CE 函数的副本,除了 CeGetPassWord 和 CeRapiInvoke 系统信息函数。
函数的原形如下:
STDAPI_(VOID) CeGetSystemInfo(LPSYSTEM_INFO lpSystemInfo);
功能: 返回当前系统信息
STDAPI_(INT) CeGetSystemMetrics(INT nIndex);
功能: 获取Windows元素的尺寸和系统设置
STDAPI_(BOOL) CeGetVersionEx(LPCEOSVERSIONINFO lpVersionInformation);
功能: 获取当前运行的操作系统版本的扩展信息
STDAPI_(BOOL) CeGetSystemPowerStatusEx(PSYSTEM_POWER_STATUS_EX pstatus,BOOL fUpdate);
功能: 获取电池状态
STDAPI_(void) CeGlobalMemoryStatus(LPMEMORYSTATUS lpmst);
功能: 获取系统物理内存和虚拟内存信息
STDAPI_(BOOL) CeGetStoreInformation(LPSTORE_INFORMATION lpsi);
功能: 获取存储器信息并填入STORE_INFORMATION结构
说明: 以下函数偶只列出函数名和功能,不再给出原型。有需要的请查 MSDN。
3.2 RAPI文件和目录管理函数
下面的列表显示的是RAPI文件管理函数,这个列表说明几乎所有可用于Windows CE应用程序的文件函数都可以用于基于PC的程序。
CeCopyFile
功能: 复制文件
CeCreateDirectory
功能: 创建目录
CeCreateFile
功能: 创建,打开文件、管道、通讯资源、磁盘设备或者控制台。返回一个句柄用来访问对象。
CeDeleteFile
功能: 删除文件
CeFindAllFiles
功能: 从指定的Windows CE目录中获取所有文件和目录的信息,并且复制到一个包含CE_FIND_DATA结构的数组中
CeFindFirstFile
功能: 在目录中查找匹配给定文件名的一个文件
CeFindClose
功能: 关闭指定的查找句柄,CeFindFirstFile和CeFindNextFile 函数用这个句柄查找文件
CeFindNextFile
功能: 从上一次访问的CeFindFirstFile继续查找文件
CeGetFileAttributes
功能: 返回指定文件或目录的属性
CeGetFileSize
功能: 获取指定文件的字节大小
CeGetFileTime
功能: 获取文件创建日期时间,最后访问日期时间和最后修改日期时间
CeMoveFile
功能: 移动(重命名)一个文件或者目录
CeReadFile
功能: 从文件指针处读取文件数据
CeWriteFile
功能: 从文件指针处写入文件数据
注:有一个新函数 CeFindAllFiles,它对 Windows CE 应用程序不可用。