23.2 WinInet 和 FTP

来源:互联网 发布:淘宝特卖2016女装 编辑:程序博客网 时间:2024/05/17 06:11

摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P1093

        WinInet API 是一些高层函数的集合主要用于协助程序使用三个最常见的互联网协议用于 WWW 的超文本传输协议(HTTP)文件传输协议(FTP)另一个称为 Gopher 的文件传输协议。WinInet 函数的语法与通常的 Windows 文件操作函数的语法很相似,因而应用起来就像使用本地磁盘上的文件一样容易。WinInet API 的文档位于 /MSDN Library/Win32 and COM Development/Networking/Network Protocols/Windows Internet/WinINet Reference/WinINet Functions。

        下面的范例程序将掩饰如何使用 WinInet API 中 FTP 部分的函数。很多公司的网站有匿名 FTP,即允许用户无需输入用户名和密码就可以下载文件。例如,如果在 Internet Explorer 的地址栏输入 ftp://ftp.microsoft.com,就可以访问微软的匿名 FTP 站点,浏览文件目录和下载文件。如果访问 ftp://ftp.cpetzold.com/cpetzold.com/ProgWin/UpdDemo,就可以在笔者的匿名 FTP 上发现与很快将要示范的程序一起使用的文件列表。

        如今,虽然 FTP 被公认为对大多数网络浏览用户不 太友好,但它仍然很有用。例如,应用程序可以在后台利用 FTP 从匿名 FTP 网站上下载文件而几乎无需用户干预。下面很快要讨论的 UPDDEMO 程序正是基于这个思想。

23.2.1  FTP API 概述

        使用 WinInet 函数的源程序必须包含 WININET.H 头文件。除此以外,程序还需要链接 WININET.LIB。你可以在 Visual C++ 的 Project Settings 对话框的 Link 选项卡中进行设置。此后运行时,程序就可以链接到 WININET.DLL 动态链接库。

        在下面的讨论中,我不打算深入讨论函数的语法细节,因为其语法在某些地方很复杂而且有很多不同的选项。作为初学者,可以将 UPDDEMO 的源代码当成一个“烹饪速成指南”。其中最重要的是了解各个步骤和 FTP 函数所涵盖的大致范围

        使用 Windows Internet API 的第一件事是调用 InternetOpen 函数。之后,就可以使用 WinInet 支持的任何协议了。InternetOpen 函数提供了一个 Internet 会话的句柄(handle),它被存储在一个 HINTERNET 类型的变量中。当使用完 WinInet API 之后,应该调用 InternetCloseHandle 函数来释放这个句柄

        要使用 FTP,接下来需要调用 InternetConnect 函数。这个函数需要使用由 InternetOpen 函数创建的 Internet 会话句柄,并返回一个 FTP 会话句柄。该句柄将作为调用所有以 Ftp 开头的函数时的第一个参数。在函数 InternetConnect 的参数中,需要指明希望使用 FTP 协议,并且给出服务器名称,例如 ftp.cpetzold.com。该函数还需要用户名和密码。如果访问的是匿名 FTP 站点,这两个变量可以设为 NULL。应用程序调用 InternetConnect 时,如果计算机尚未连接到互联网,Windows 98 将显示一个拨号连接对话框。应用程序使用 FTP 结束时,应该调用 InternetCloseHandle 函数来释放句柄。

        在此之后,便可以开始使用以 Ftp 为前缀的函数了。你会发现,这些函数与一些普通的 Windows 文件 I/O 函数很相似。为了避免与其协议重复,一些以 Internet 为前缀的函数也可以和 FTP 一起使用。

        以下四个函数用于目录操作:

fSuccess = FtpCreateDirectory(hFtpSession, szDirectory);fSuccess = FtpRemoveDirectory(hFtpSession, szDirectory);fSuccess = FtpSetCurrentDirectory(hFtpSession, szDirectory);fSuccess = FtpGetCurrentDirectory(hFtpSession, szDirectory, &dwCharacterCount);
注意,这些函数和我们已经熟悉的 Windows 提供的用于操作本地文件系统的 CreateDirectory、RemoveDirectory、SetCurrentDirectory 和 GetCurrentDirectory 函数很相似。

        当然,匿名访问 FTP 站点的应用程序不能创建或删除一个目录。并且,程序也不应假设 FTP 目录和 Windows 文件系统的树状结构是一样的。具体来说,使用相对目录的程序不应该对 FTP 服务器的新的“绝对路径”作出任何假设。如果程序需要知道最终目录的“绝对路径”,就应该在调用 SetCurrentDirectory 函数之后调用 GetCurrentDirectory 函数。GetCurrentDirectory 函数的字符串参数应该至少能容纳 MAX_PATH 个字符,并且,最后一个参数应该是一个指针,它指向的变量包含字符串中字符的个数。

        下面这两个函数用于删除或重命名文件(但不能用于匿名 FTP 站点):

fSuccess = FtpDeleteFile (hFtpSession, szFileName);fSuccess = FtpRenameFile (hFtpSession, szOldName, szNewName);

        可以调用 FtpFindFirstFile 函数来查找一个文件(或者与含通配符的模板匹配的多个文件)。该函数和 FindFirstFile 函数非常相似,它们甚至使用同样的 WIN32_FIND_DATA 结构。函数返回一个用于枚举文件的句柄。将这个句柄传入 InternetFindNextFile 函数,可以得到文件名。结束时,需要调用 InternetCloseHandle 函数来释放这个句柄。

       FtpFileOpen 函数用于打开一个文件。该函数返回一个文件句柄,该句柄可用于 InternetReadFile、InternetReadFileEx、InternetWrite 和 InternetSetFilePointer 函数的调用。结束时,需要调用 InternetCloseHandle 函数来释放这个句柄。

        最后,介绍两个非常有用的高层函数:FtpGetFile 和 FtpPutFile。FtpGetFile 函数用来将 FTP 服务器上的文件复制到本地。它集成了 FtpFileOpen、CreateFile、InternetReadFile、WriteFile、InternetCloseHandle 和 CloseHandle 函数调用。传给 FtpGetFile 函数的参数中有一个指示标志,可以用它来指示如果在本地已经有一个名称相同的文件,函数就应该失败并返回。相应地,FtpPutFile 函数用于将一个本地文件复制到 FTP 服务器

23.2.2  UPDDEMO 程序

        图 23-2 所示的程序 UPDDEMO(“update demo”)将演示如何在一个二级线程中使用 WinInet FTP 中的一系列函数从一个匿名 FTP 站点下载文件。(译注:该程序所用的 FTP 服务器 ftp.cpetzold.com 现在已经不再有效,用户除非自己设置一个 FTP 服务器,否则将无法测试该程序。一个变通方法是把 FTPSERVER 常量改为 ftp.charlespetzold.com,然后把 DIRECTORY 和 TEMPLATE 常量改为服务器上相应的目录和文件名。可以通过使用【资源管理器】连接到 ftp.charlespetzold.com 服务器来获得具体的目录和文件名。)

/*------------------------------------------------UPDDEMO.C -- Demonstrates Anonymous FTP Access               (c) Charles Petzold, 1998------------------------------------------------*/#include <windows.h>#include <wininet.h>#include <process.h>#include "resource.h"// User-defined messages used in WndProc#define WM_USER_CHECKFILES (WM_USER + 1)#define WM_USER_GETFILES (WM_USER + 2)// Information for FTP download// #define FTPSERVER TEXT ("ftp.cpetzold.com")// #define DIRECTORY TEXT ("cpetzold.com/ProgWin/UpdDemo")// #define TEMPLATE  TEXT ("UD??????.TXT")#define FTPSERVER TEXT ("ftp.charlespetzold.com")#define DIRECTORY TEXT ("charlespetzold.com/ProgWin5/Chap23/UpdDemo/")#define TEMPLATE TEXT("Upd????.c")// Structures used for storing filenames and contentstypedef struct{TCHAR * szFilename;char  * szContents;}FILEINFO;typedef struct{int iNum;FILEINFO info[1];}FILELIST;// Structure used for second threadtypedef struct{BOOL bContinue;HWND hwnd;}PARAMS;// Declarations of all functions in programLRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);VOIDFtpThread(PVOID);VOIDButtonSwitch(HWND, HWND, TCHAR *);FILELIST *GetFileList(VOID);intCompare(const FILEINFO *, const FILEINFO *);// A couple globalsHINSTANCE hInst;TCHAR  szAppName[] = TEXT("UpdDemo");int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow){HWND hwnd;MSG msg;WNDCLASS wndclass;hInst = hInstance;wndclass.style = 0;wndclass.lpfnWndProc = WndProc;wndclass.cbClsExtra = 0;wndclass.cbWndExtra = 0;wndclass.hInstance = hInstance;wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);wndclass.hCursor = NULL;wndclass.hbrBackground = GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName = NULL;wndclass.lpszClassName = szAppName;if (!RegisterClass(&wndclass)){MessageBox(NULL, TEXT("This program requires Windows NT!"),szAppName, MB_ICONERROR);return 0;}hwnd = CreateWindow(szAppName, TEXT("Update Demo with Anonymous FTP"),WS_OVERLAPPEDWINDOW | WS_VSCROLL,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL);ShowWindow(hwnd, iCmdShow);UpdateWindow(hwnd);// After window is displayed, check if the latest file existsSendMessage(hwnd, WM_USER_CHECKFILES, 0, 0);while (GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}return msg.wParam;}LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static FILELIST * plist;static int  cxClient, cyClient, cxChar, cyChar;HDC  hdc;int  i;PAINTSTRUCT  ps;SCROLLINFO  si;SYSTEMTIME  st;TCHAR  szFilename[MAX_PATH];switch (message){case WM_CREATE:cxChar = LOWORD(GetDialogBaseUnits());cyChar = HIWORD(GetDialogBaseUnits());return 0;case WM_SIZE:cxClient = LOWORD(lParam);cyClient = HIWORD(lParam);si.cbSize = sizeof(SCROLLINFO);si.fMask = SIF_RANGE | SIF_PAGE;si.nMin = 0;si.nMax = plist ? plist->iNum - 1 : 0;si.nPage = cyClient / cyChar;SetScrollInfo(hwnd, SB_VERT, &si, TRUE);return 0;case WM_VSCROLL:si.cbSize = sizeof(SCROLLINFO);si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;GetScrollInfo(hwnd, SB_VERT, &si);switch (LOWORD(wParam)){case SB_LINEDOWN:si.nPos += 1;  break;case SB_LINEUP:si.nPos -= 1;  break;case SB_PAGEDOWN:si.nPos += si.nPage;  break;case SB_PAGEUP:si.nPos -= si.nPage;  break;case SB_THUMBPOSITION:si.nPos = HIWORD(wParam); break;default:return 0;}si.fMask = SIF_POS;SetScrollInfo(hwnd, SB_VERT, &si, TRUE);InvalidateRect(hwnd, NULL, TRUE);return 0;case WM_USER_CHECKFILES:// Get the system date & form filename from year and monthGetSystemTime(&st);wsprintf(szFilename, TEXT("UD%04i%02i.TXT"), st.wYear, st.wMonth);// Check if the file exists; if so, read all the filesif (GetFileAttributes(szFilename) != (DWORD)-1){SendMessage(hwnd, WM_USER_GETFILES, 0, 0);return 0;}// Otherwise, get files from Internet.// But first check so we don't try to copy files to a CD-ROMif (GetDriveType(NULL) == DRIVE_CDROM){MessageBox(hwnd, TEXT("Cannot run this program from CD-ROM!"),szAppName, MB_OK | MB_ICONEXCLAMATION);return 0;}// Ask user if an Internet connection is desiredif (IDYES == MessageBox(hwnd,TEXT("Update information from Internet?"),szAppName, MB_YESNO | MB_ICONQUESTION))// Invoke dialog boxDialogBox(hInst, szAppName, hwnd, DlgProc);// Update displaySendMessage(hwnd, WM_USER_GETFILES, 0, 0);return 0;case WM_USER_GETFILES:SetCursor(LoadCursor(NULL, IDC_WAIT));ShowCursor(TRUE);// Read in all the disk filesplist = GetFileList();ShowCursor(FALSE);SetCursor(LoadCursor(NULL, IDC_ARROW));// Simulate a WM_SIZE message to alter scroll bar & repaintSendMessage(hwnd, WM_SIZE, 0, MAKELONG(cxClient, cyClient));InvalidateRect(hwnd, NULL, TRUE);return 0;case WM_PAINT:hdc = BeginPaint(hwnd, &ps);SetTextAlign(hdc, TA_UPDATECP);si.cbSize = sizeof(SCROLLINFO);si.fMask = SIF_POS;GetScrollInfo(hwnd, SB_VERT, &si);if (plist){for (i = 0; i < plist->iNum; i++){MoveToEx(hdc, cxChar, (i - si.nPos) * cyChar, NULL);TextOut(hdc, 0, 0, plist->info[i].szFilename,lstrlen(plist->info[i].szFilename));TextOut(hdc, 0, 0, TEXT(": "), 2);TextOutA(hdc, 0, 0, plist->info[i].szContents, strlen(plist->info[i].szContents));}}EndPaint(hwnd, &ps);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, message, wParam, lParam);}BOOL CALLBACK DlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static PARAMS params;switch (message){case WM_INITDIALOG:params.bContinue = TRUE;params.hwnd = hwnd;_beginthread(FtpThread, 0, ¶ms);return TRUE;case WM_COMMAND:switch (LOWORD(wParam)){case IDCANCEL:// button for user to abort downloadparams.bContinue = FALSE;return TRUE;case IDOK:// button to make dialog box go awayEndDialog(hwnd, 0);return TRUE;}}return FALSE;}/*-------------------------------------------------------------------------FtpThread: Reads files from FTP server and copies them to local disk--------------------------------------------------------------------------*/void FtpThread(PVOID parg){BOOLbSuccess;HINTERNEThIntSession, hFtpSession, hFind;HWNDhwndStatus, hwndButton;PARAMS *  pparams;TCHARszBuffer[64];WIN32_FIND_DATAfinddata;pparams = parg;hwndStatus = GetDlgItem(pparams->hwnd, IDC_STATUS);hwndButton = GetDlgItem(pparams->hwnd, IDCANCEL);// Open an internet sessionhIntSession = InternetOpen(szAppName, INTERNET_OPEN_TYPE_PRECONFIG,NULL, NULL, INTERNET_FLAG_ASYNC);if (hIntSession == NULL){wsprintf(szBuffer, TEXT("InternetOpen error %i"), GetLastError());ButtonSwitch(hwndStatus, hwndButton, szBuffer);_endthread();}SetWindowText(hwndStatus, TEXT("Internet session opened..."));// Check if user has pressed Cancelif (!pparams->bContinue){InternetCloseHandle(hIntSession);ButtonSwitch(hwndStatus, hwndButton, NULL);_endthread();}// Open an FTP session.hFtpSession = InternetConnect(hIntSession, FTPSERVER, INTERNET_DEFAULT_FTP_PORT,NULL, NULL, INTERNET_SERVICE_FTP, 0, 0);if (hFtpSession == NULL){InternetCloseHandle(hIntSession);wsprintf(szBuffer, TEXT("InternetConnect error %i"),GetLastError());ButtonSwitch(hwndStatus, hwndButton, szBuffer);_endthread();}SetWindowText(hwndStatus, TEXT("FTP Session opened..."));// Check if user has pressed Cancelif (!pparams->bContinue){InternetCloseHandle(hFtpSession);InternetCloseHandle(hIntSession);ButtonSwitch(hwndStatus, hwndButton, NULL);_endthread();}// Set the directorybSuccess = FtpSetCurrentDirectory(hFtpSession, DIRECTORY);if (!bSuccess){InternetCloseHandle(hFtpSession);InternetCloseHandle(hIntSession);wsprintf(szBuffer, TEXT("Cannot set directory to %s"),DIRECTORY);ButtonSwitch(hwndStatus, hwndButton, szBuffer);_endthread();}SetWindowText(hwndStatus, TEXT("Directory found..."));// Check if user has pressed Cancelif (!pparams->bContinue){InternetCloseHandle(hFtpSession);InternetCloseHandle(hIntSession);ButtonSwitch(hwndStatus, hwndButton, NULL);_endthread();}// Get the first file fitting the templatehFind = FtpFindFirstFile(hFtpSession, TEMPLATE,&finddata, 0, 0);if (hFind == NULL){InternetCloseHandle(hFtpSession);InternetCloseHandle(hIntSession);ButtonSwitch(hwndStatus, hwndButton, TEXT("Cannot find files"));_endthread();}do{// Check if user has pressed Cancelif (!pparams->bContinue){InternetCloseHandle(hFind);InternetCloseHandle(hFtpSession);InternetCloseHandle(hIntSession);ButtonSwitch(hwndStatus, hwndButton, NULL);_endthread();}// Copy file from internet to local hard disk, but fail// if the file already exists locallywsprintf(szBuffer, TEXT("Reading file %s..."), finddata.cFileName);SetWindowText(hwndStatus, szBuffer);FtpGetFile(hFtpSession, finddata.cFileName, finddata.cFileName, TRUE,FILE_ATTRIBUTE_NORMAL, FTP_TRANSFER_TYPE_BINARY, 0);} while (InternetFindNextFile(hFind, &finddata));InternetCloseHandle(hFind);InternetCloseHandle(hFtpSession);InternetCloseHandle(hIntSession);ButtonSwitch(hwndStatus, hwndButton, TEXT("Internet Download Complete"));}/*-------------------------------------------------------------------------ButtonSwitch: Displays final status message and changes Cancel to OK--------------------------------------------------------------------------*/VOID ButtonSwitch(HWND hwndStatus, HWND hwndButton, TCHAR * szText){if (szText)SetWindowText(hwndStatus, szText);elseSetWindowText(hwndStatus, TEXT("Internet Session Cancelled"));SetWindowText(hwndButton, TEXT("OK"));SetWindowLong(hwndButton, GWL_ID, IDOK);}/*-------------------------------------------------------------------------GetFileList: Reads files from disk and saves their names and contents--------------------------------------------------------------------------*/FILELIST * GetFileList(void){DWORD  dwRead;FILELIST* plist;HANDLE  hFile, hFind;int  iSize, iNum;WIN32_FIND_DATA finddata;hFind = FindFirstFile(TEMPLATE, &finddata);if (hFind == INVALID_HANDLE_VALUE)return NULL;plist = NULL;iNum = 0;do{// Open the file and get the sizehFile = CreateFile(finddata.cFileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, 0, NULL);if (hFile == INVALID_HANDLE_VALUE)continue;iSize = GetFileSize(hFile, NULL);if (iSize == (DWORD)-1){CloseHandle(hFile);continue;}// Realloc the FILELIST structure for a new entryplist = realloc(plist, sizeof(FILELIST) + iNum * sizeof(FILEINFO));// Allocate space and save the filenameplist->info[iNum].szFilename = malloc(lstrlen(finddata.cFileName) + sizeof(TCHAR));lstrcpy(plist->info[iNum].szFilename, finddata.cFileName);// Allocate space and save the contentsplist->info[iNum].szContents = malloc(iSize + 1);ReadFile(hFile, plist->info[iNum].szContents, iSize, &dwRead, NULL);plist->info[iNum].szContents[iSize] = 0;CloseHandle(hFile);iNum++;} while (FindNextFile(hFind, &finddata));FindClose(hFind);// Sort the files by filenameqsort(plist->info, iNum, sizeof(FILEINFO), Compare);plist->iNum = iNum;return plist;}/*-------------------------------Compare function for qsort---------------------------------*/int Compare(const FILEINFO * pinfo1, const FILEINFO * pinfo2){return lstrcmp(pinfo2->szFilename, pinfo1->szFilename);}
UPDDEMO.RC (excerpts)// Microsoft Visual C++ 生成的资源脚本。//#include "resource.h"///////////////////////////////////////////////////////////////////////////////// Dialog//UPDDEMO DIALOG DISCARDABLE  20, 20, 186, 95STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENUCAPTION "Internet Download"FONT 8, "MS Sans Serif"BEGIN    PUSHBUTTON      "Cancel", IDCANCEL, 69, 74, 50, 14    CTEXT           "", IDC_STATUS, 7, 29, 172, 21END
RESOURCE.H (excerpts)// Microsoft Visual C++ generated include file.// Used by UpdDemo.rc#define IDC_STATUS                      40001

        UPDDEMO 使用的文件以 UDyyyymm.TXT 的格式命名,其中 yyyy 是 4 位阿拉伯数字的年份,mm 是 2 为阿拉伯数字的月份。这里的假设是程序可以受益于每月都有更新的文件。这些文件可能是整个月刊杂志,为了提高效率,程序将其下载到本地存储器。

        WinMain 在调用 ShowWindow 和 UpdateWindow 函数来显示 UPDDEMO 的主窗口之后,它发送给 WndProc 一条程序自定义的 WM_USER_CHECKFILES 消息。WndProc 以如下方式来处理这个消息:首先获取当前年月的信息,然后在默认的目录里查找是否有以此年月份命名的 UDyyyymm.txt 文件。如果有,则意味着 UPDDEMO 已经被完全更新了。(当然,有时也不尽然。它可能会遗漏一些以前的文件。如果要做更深入的检测,则需要一个更完整的程序。)在本例中,UPDDEMO 发送一条 WM_USER_GETFILES 消息给自己,然后它再调用 GetFileList 函数来处理这个消息。这是 UPDDEMO.C 中一个很长的函数,但它读起来并不是很有意思,它所作的就是将所有符合 UDyyyymm.TXT 格式的文件读到一个动态分配的数据结构 FILELIST 中去(这个数据结构的定义在程序顶部)。然后,程序在其客户区显示这些文件的内容。

        如果 UPDDEMO 没有最新的文件,它就必须通过访问互联网来更新。程序首先询问用是否可以这么做。如果用户同意,它将显示一个带有 Cancel 按钮和一个 ID 是 IDC_STATUS 的静态文本框的简单对话框。它用于向用户显示下载进程,并允许用户取消过于缓慢的会话。这个对话框过程的名称为 DlgProc。

        DlgProc 很短。它负责建立一个 PARAMS 类型的结构(该结构内含自身的窗口句柄和一个名为 bContinue 的布尔变量),然后调用 _beginthread 来执行一个二级线程。

        FtpThread 函数通过以下一系列函数来执行真正的传送:InternetOpen、InternetConnect、FtpSetCurrentDirectory、FtpFindFirstFile、InternetFindNextFile、FtpGetFile 和 InternetCloseHandle(三次)。如果去除冗繁的错误检测、给用户报告进展和允许用户随时取消整个过程的部分,这个线程函数可以简短很多。FtpThread 函数通过使用 hwndStatus 句柄(即对话框中的静态文本框的句柄)来调用 SetWindowText 函数,从而保证用户得知进展情况。

        这个线程可以通过以下三种方式之一来终结。

        第一种,FtpThread 可能遇到某个 WinInet 函数出错而传回的错误。在这种情况下,它执行清理操作,并发送一个格式化的错误字符串(同时还有指向对话框中文本框和 Cancel 按钮的句柄)给 ButtonSwitch 函数。ButtonSwitch 是一个很短的函数,它用来显示字符串,并且把 Cancel 按钮转换为 OK 按钮——不仅是按钮的文本,按钮的控件 ID 也会转换。这样讲允许用户使用 OK 按钮来结束对话框。

        第二种,FtpThread 在没有任何错误的情况成功结束所有任务。其处理与前面出错处理的方式几乎一样。唯一不同的是对话框中显式的文本是“Internet Download Complete”。

        第三种,用户选择在下载过程中取消了下载。在这种情况下,DlgProc 将 PARAMS 结构中的 bContinue 字段设置为 FALSE。FtpThread 不时地检查这个值,如果 bContinue 是 FALSE,函数将会做一些清理工作,并且用 NULL 文本参数来调用 ButtonSwitch,指示其显式“Internet Session Cancelled”。同样,用户必须按 OK 按钮来关闭对话框。

        虽然对于每个文件 UPDDEMO 仅显式一行,但是我可以用它来向读者通知本书的更新或其他有关本书的信息(访问我的网站,了解更多详细情况)。因此,UPDDEMO 实际上是我在本书结束之后向你传播信息的一种渠道。

0 0
原创粉丝点击