分类: C/C++
2008-03-12 20:06:15
HINTERNET h = InternetOpen(...); HINTERNET hftp = InternetConnect(..,INTERNET_SERVICE_FTP,..); HINTERNET hftpfile = FtpOpenFile(...); InternetReadFile(...);
下面就让我们深入细节,享受精彩。为了方便代码的重用,我写了类 CWebVersion,这个类对所有细节进行了封装,实现的功能很简单:就是通过 Web 来获取程序版本信息,实现版本的检查。这个类的使用也很方便:
if (CWebVersion::Online()) { CWebVersion ver("pub.chinafsdu.net"); if (ver.ReadVersion("version.txt"),"pub","pub") { DWORD maj = ver.dwVersionMS; DWORD min = ver.dwVersionLS; } }
下面是CWebVersion的声明:
//////////////////////////////////////////////////////////////// // WebVersion.h // #pragma once class CWebVersion { protected: enum { BUFSIZE = 64 }; LPCTSTR m_lpServer; // server name DWORD m_dwError; // most recent error code TCHAR m_errInfo[256]; // extended error info TCHAR m_version[BUFSIZ]; // version number as text void SaveErrorInfo(); // helper to save error info public: DWORD dwVersionMS; // version number: most-sig 32 bits DWORD dwVersionLS; // version number: least-sig 32 bits CWebVersion(LPCTSTR server) : m_lpServer(server) { } ~CWebVersion() { } static BOOL Online(); BOOL ReadVersion(LPCTSTR lpFileName,LPCSTR lpszUserName,LPCSTR lpszPassword); LPCTSTR GetVersionText() { return m_version; } DWORD GetError() { return m_dwError; } LPCTSTR GetExtendedErrorInfo() { return m_errInfo; } }; CWebVersion 的实现文件 //////////////////////////////////////////////////////////////// // WebVersion.cpp // #include "stdafx.h" #include "WebVersion.h" #include "InetHandle.h" ////////////////// // Check if connected to Internet. // BOOL CWebVersion::Online() { DWORD dwState = 0; DWORD dwSize = sizeof(DWORD); return InternetQueryOption(NULL, INTERNET_OPTION_CONNECTED_STATE, &dwState, &dwSize) && (dwState & INTERNET_STATE_CONNECTED); } ////////////////// // Read version number as string into buffer // BOOL CWebVersion::ReadVersion(LPCTSTR lpFileName,LPCSTR lpszUserName,LPCSTR lpszPassword) { CInternetHandle hInternet; CInternetHandle hFtpSession; CInternetHandle hFtpFile; m_version[0] = 0; m_dwError=0; // assume success m_errInfo[0]=0; // .. DWORD nRead=0; hInternet = InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); if (hInternet!=NULL) { hFtpSession = InternetConnect(hInternet, m_lpServer, INTERNET_DEFAULT_FTP_PORT, lpszUserName, lpszPassword, INTERNET_SERVICE_FTP, 0, NULL); if (hFtpSession!=NULL) { hFtpFile = FtpOpenFile(hFtpSession, lpFileName, GENERIC_READ, FTP_TRANSFER_TYPE_ASCII, NULL); if (hFtpFile!=NULL) { InternetReadFile(hFtpFile, m_version, BUFSIZE, &nRead); if (nRead>0) { m_version[nRead] = 0; int Mhi,Mlo,mhi,mlo; _stscanf(m_version, "%x,%x,%x,%x", &Mhi, &Mlo, &mhi, &mlo); dwVersionMS = MAKELONG(Mlo,Mhi); dwVersionLS = MAKELONG(mlo,mhi); return TRUE; } } } } // Failed: save error code and extended error info if any. m_dwError = GetLastError(); if (m_dwError==ERROR_INTERNET_EXTENDED_ERROR) { DWORD dwErr; DWORD len = sizeof(m_errInfo)/sizeof(m_errInfo[0]); InternetGetLastResponseInfo(&dwErr, m_errInfo, &len); } return FALSE; }WebVersion::Online 检查用户是否连接到 Internet。如果用户不在线,我会让你决定做什么——建议什么也别做。如果用户已经连接到 Internet,则显示一个“版本检查”按钮,这样用户可以明确地发起与FTP服务器的连接(使用 WinInet 的InternetAttemptConnect 函数)。不论做什么都要先询问,再连接,不要太粗鲁。现在很多人都是用猫上网,一定要有一个友好的用户界面。 假设程序已经与 FTP 服务器连接上,为了读取版本信息,只要建立一个 CWebVersion 对象实例,同时将 FTP 服务器地址传入,然后用版本文件名作为参数调用 CWebVersion::ReadVersion 即可。ReadVersion 期望读到下面这样一行内容:
4,3,0,0
这是用逗号分隔的四个数字,格式与应用程序 VS_VERSION_INFO 资源中的 FILEVERSION 和 PRODUCTVERSION 版本号相同。ReadVersion 将这个数字读入两个32位的 DWORDs,dwVersionMS 和 dwVersionLS。Windows 使用64位的版本号,所以你可以发布 18,446,744,070,000,000,000 版本。在美国,这是 18 乘以一百万的三次方,而在欧洲这是 18 乘以一百万的五次方。如果每秒钟发布一个新版本,这些版本要八百万个程序员发布 75,000 年。这样的话,我们最好赶快练一下自己的打字速度。
图一是本文的控制台例子程序(cslVerChkApp.exe)运行画面,此程序使用 CWebVersion 通过 FTP 读取版本号。
图一 控制台例子程序运行画面
下面我们来看看 CWebVersion 的工作原理。其大多数函数都是自解释的;为了检查系统是否连接,CWebVersion::Online调用了一个WinInet 函数 InternetQueryOption 来获取 INTERNET_OPTION_CONNECTED_STATE。如果返回的状态具备INTERNET_STATE_CONNECTED标志,说明有一个活动的连接。为了访问 Internet 并读取版本文件,CWebVersion 还用到了另外一个定制的C++类:CInternetHandle:
//////////////////////////////////////////////////////////////// // InternetHandle.h // #pragma once class CInternetHandle { protected: HINTERNET m_handle; // underlying handle public: CInternetHandle() : m_handle(NULL) { } CInternetHandle(HINTERNET h) : m_handle(h) { } ~CInternetHandle() { Close(); } // Close handle and set to NULL so I don''t close again. void Close() { if (m_handle) { InternetCloseHandle(m_handle); m_handle = NULL; } } // Assignment from HINTERNET CInternetHandle& operator= (HINTERNET h) { ASSERT(m_handle==NULL); // only initial assignment, not copy m_handle = h; return *this; } // cast to HINTERNET operator HINTERNET() { return m_handle; } };这个类主要是封装 HINTERNET。其作用是为了方便进行错误处理。如果某个WinInet 函数调用失败,调用 GetLastError 会获取失败原因么。但 CloseInternetHandle 会清除出错代码,所以,必须在 CloseInternetHandle 之前调用 GetLastError。看看下面的代码就会明白:
//////////////////////////////////////////////////////////////// // 如果没有 CInternetHandle 类。代码可能会是下面这个样子。 // 必须在几个地方调用 GetLastError ,否则CloseInternetHandle 会 // 清除出错代码. // DWORD err; HINTERNET h = InternetOpen(...); if (h!=NULL) { HINTERNET hftp = InternetConnect(..,INTERNET_SERVICE_FTP,..); if (hftp!=NULL) { HINTERNET hftpfile = FtpOpenFile(...); if (hftpfile!=NULL) { InternetReadFile(...); if (/* success */) { // do something } else { err = GetLastError(); } InternetCloseHandle(hftpfile); } else { err = GetLastError(); } InternetCloseHandle(hftp); } else { err = GetLastError(); } InternetCloseHandle(h); } else { err = GetLastError(); }有了 CInternetHandle 类,一切都便得简单明了;ReadVersion 只需在最后检查出错情况。当控制流离开函数时,C++会自动关闭所有的 CInternetHandles。
图二 更新程序版本的对话框
为了进一步示范 CWebVersion 的使用,我写了一个对话框程序,如图二所示,每次启动程序时,都会对版本进行检查,读取新版本数据是在 InitInstance()函数中进行的:
// 从 Web 读取当前版本号 if (CWebVersion::Online()) { CWebVersion ver(_T("pub.chinafsdu.net")); if (ver.ReadVersion(_T("version.txt"),_T("pub"),_T("pub"))) m_dwNewVersion = ver.dwVersionMS; }将读取的版本号存储在 m_dwNewVersion 数据成员中,这里例子程序使用在32位的版本号。接着在对话框的 OnInitDialog() 例程中对版本号进行比较,当前的版本号存储在程序的 VS_VERSION_INFO 资源中,这个信息在编译程序时就已经产生了,获取它很容易,参见另外一个C++类:CMyVersionInfo。通过比较:
// 比较当前的程序版本与最新的程序版本 if (App.m_dwNewVersion && vi.dwProductVersionMS < App.m_dwNewVersion) { // current version is newer: display link s.Format(_T("更新版本 %d.%02d"), HIWORD(App.m_dwNewVersion),LOWORD(App.m_dwNewVersion)); m_wndVerChkLink.SetWindowText(s); } else { // current version not available: hide link m_wndVerChkLink.ShowWindow(SW_HIDE); }如果Web版本较新,则对话框显示一个“更新版本 XXX”链接(如图二所示),否则隐藏链接,什么也不显示。在程序启动时用FTP读取10个字节的文件应该是很快的,但实际情况往往与想象的并不一样,对话框的显示总是会有一个延时,延时的长短要看FTP服务器的位置以及繁忙状态。因此,要想用户界面的响应速度够快,可以考虑在单独的线程进行文件的下载。