`
mp013mp
  • 浏览: 12137 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

windows 程序设计之「NetTime.C」范例分析笔记

 
阅读更多

windows 程序设计之「NetTime.C」范例分析笔记
2011年01月06日
  /*------------------------------------------------ -------    NETTIME.C -- Sets System Clock from Internet Services                 (c) Charles Petzold, 1998   -------------------------------------------------- -----*/ #include #include"resource.h" #define WM_SOCKET_NOTIFY (WM_USER + 1) #define ID_TIMER         1 LRESULT CALLBACK WndProc   (HWND, UINT, WPARAM, LPARAM) ; BOOL    CALLBACK MainDlg   (HWND, UINT, WPARAM, LPARAM) ; BOOL    CALLBACK ServerDlg (HWND, UINT, WPARAM, LPARAM) ; void ChangeSystemTime (HWND hwndEdit, ULONG ulTime) ; void FormatUpdatedTime (HWND hwndEdit, SYSTEMTIME * pstOld,                                         SYSTEMTIME * pstNew) ; void EditPrintf (HWND hwndEdit, TCHAR * szFormat, ...) ; HINSTANCE hInst ; HWND      hwndModeless ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,                     PSTR szCmdLine, int iCmdShow) {      static TCHAR szAppName[] = TEXT ("NetTime") ;      HWND         hwnd ;      MSG          msg ;      RECT         rect ;      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 = NULL ;      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 ("Set System Clock from Internet"),                           WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |                                WS_BORDER | WS_MINIMIZEBOX,                           CW_USEDEFAULT, CW_USEDEFAULT,                           CW_USEDEFAULT, CW_USEDEFAULT,                           NULL, NULL, hInstance, NULL) ;           // 创建模态对话框
  hwndModeless = CreateDialog (hInstance, szAppName, hwnd, MainDlg) ;
  // 获取模态对话框大小
  GetWindowRect (hwndModeless, &rect) ;
  // 该函数依据所需客户矩形的大小,计算需要的窗口矩形的大小
  // 参数二指定将被计算尺寸的窗口的窗口风格
  // 参数三指定窗口是否拥有菜单
  AdjustWindowRect (&rect, WS_CAPTION | WS_BORDER, FALSE) ;
  // 该函数改变一个子窗口,弹出式窗口或顶层窗口的尺寸,位置和Z序
  // SWP_NOMOVE:维持当前位置(忽略参数三、四)
  // 该函数更改程序主窗口大小,以保证能被hwndModeless对话框覆盖
  SetWindowPos (hwnd, NULL, 0, 0, rect.right - rect.left,
  rect.bottom - rect.top, SWP_NOMOVE) ;
  // 该函数设置指定窗口的显示状态
  // SW_SHOW 在窗口原来的位置以原来的尺寸激活和显示窗口
  ShowWindow (hwndModeless, SW_SHOW) ;
  // 显示程序主窗口
  ShowWindow (hwnd, iCmdShow) ;
  UpdateWindow (hwnd) ;
  // Normal message loop when a modeless dialog box is used.
  while (GetMessage (&msg, NULL, 0, 0))
  {
  if (hwndModeless == 0 || !IsDialogMessage (hwndModeless, &msg))
  {
  TranslateMessage (&msg) ;
  DispatchMessage (&msg) ;
  }
  }
  return msg.wParam ;
  }
  LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  {
  switch (message)
  {
  case WM_SETFOCUS: // 传递焦点
  SetFocus (hwndModeless) ;
  return 0 ;
  case WM_DESTROY:
  PostQuitMessage (0) ;
  return 0 ;
  }
  return DefWindowProc (hwnd, message, wParam, lParam) ;
  }
  BOOL CALLBACK MainDlg (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  {
  staticchar   szIPAddr[32] = { "132.163.135.130" } ;
  static HWND   hwndButton, hwndEdit ;
  static SOCKET sock ;
  staticstruct sockaddr_in sa ;
  static TCHAR  szOKLabel[32] ;
  int           iError, iSize ;
  unsignedlong ulTime ;
  WORD          wEvent, wError ;
  WSADATA       WSAData ;     
  switch (message)      {      case WM_INITDIALOG:          // 获取对话框窗口中指定控件的句柄
  hwndButton = GetDlgItem (hwnd, IDOK) ;
  hwndEdit = GetDlgItem (hwnd, IDC_TEXTOUT) ;
  return TRUE ;
  case WM_COMMAND:
  switch (LOWORD (wParam))
  {
  case IDC_SERVER: // 点击服务器列表按钮时
  // 创建模态对话框,用于显示服务器列表
  // 该对话框的回调函数为ServerDlg,自定义传递参数为szIPAddr(用于获取服务器IP)
  DialogBoxParam (hInst, TEXT ("Servers"), hwnd, ServerDlg, 
  (LPARAM) szIPAddr) ;
  return TRUE ;
  case IDOK:
  // WSAStartup 函数完成对Winsock服务的初始化
  // 第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本。
  // MAKEWORD 宏创建一个被指定变量连接而成的WORD变量。返回一个WORD变量。参数一为低8位。
  // WSAData 用于装载操作系统利用第二个参数返回请求的Socket的版本信息
  // 返回值 0 表示成功
  if (iError = WSAStartup (MAKEWORD(2,0), &WSAData))
  {
  // 如果有错误代码返回,则在文本控件中显示错误代码
  EditPrintf (hwndEdit, TEXT ("Startup error #%i.\r\n"), 
  iError) ;
  return TRUE ;
  }
  // WSAData.szDescription 记录了获取的Winsock简要信息
  EditPrintf (hwndEdit, TEXT ("Started up %hs\r\n"), 
  WSAData.szDescription);
  // socket 函数创建一个能够进行网络通信的套接字
  // AF_INET 指定应用程序使用的通信协议的协议族.表示此处是某种Internet地址
  // SOCK_STREAM 指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据封包套接字类型为SOCK_DGRAM
  // IPPROTO_TCP 指定应用程序所使用的通信协议。
  // 调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET
  sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) ;
  if (sock == INVALID_SOCKET)
  {
  EditPrintf (hwndEdit, 
  TEXT ("Socket creation error #%i.\r\n"), 
  // 获得上次失败操作的错误代码
  WSAGetLastError ()) ;
  // 中止Windows Sockets DLL的使用
  WSACleanup () ;
  return TRUE ;
  }
  // 显示创建的套接字的描述符
  EditPrintf (hwndEdit, TEXT ("Socket %i created.\r\n"), sock) ;
  // WSAAsyncSelect函数强制阻碍性的函数转为非阻碍性的,即在函数执行完之前把控制权传回给程序
  // WM_SOCKET_NOTIFY 程序自订消息,当满足触发条件时,由系统发送给参数二指定的窗口处理程序
  // FD_CONNECT 设置触发条件,欲接收已连接好时。
  // FD_READ 设置触发条件,欲接收读准备好时。
  // 返回值 0 表示成功
  if (SOCKET_ERROR == WSAAsyncSelect (sock, hwnd, WM_SOCKET_NOTIFY, 
  FD_CONNECT | FD_READ))
  {
  EditPrintf (hwndEdit,                                  TEXT ("WSAAsyncSelect error #%i.\r\n"),                                 WSAGetLastError ()) ;                     // 该函数关闭一个套接口,它释放套接口描述符sock
  closesocket (sock) ;
  WSACleanup () ;
  return TRUE ;
  }
  // AF_INET 用于表示地址种类,此处是某种Internet地址
  sa.sin_family           = AF_INET ;
  // sin_port 指定为欲连接的端口
  // 当大多数数字通过Internet时,这个端口号字段必须是(big-endian)的,即最高的字节排第一个。
  // Intel微处理器是little endian ,低位在前。所以用htons将一个无符号短整型数值转换为网络字节序,即(big-endian)
  sa.sin_port             = htons (IPPORT_TIMESERVER) ; 
  // inet_addr函数将储存在szIPAddr字符串中的服务器地址转化为无正负号长整数
  sa.sin_addr.S_un.S_addr = inet_addr (szIPAddr) ;
  // 建立与一个端的连接
  // 由于之前呼叫了WSAAsyncSelect,所以connect不会等待连结,它会立即传回SOCKET_ERROR的值。
  // 这并不是出现了错误,这只是表示现在还没有联机成功而已。当连接成功或失败时,会触发WM_SOCKET_NOTIFY消息
  connect(sock, (SOCKADDR *) &sa, sizeof (sa)) ;
  /* WSAEWOULDBLOCK 表示 Output Buffer 已经满了,无法再写入数据。
  可以理解为当程序发送数据给对方,对方的接收速度没有程序发送的快或者对方的接受缓冲区已被填满,
  所以就返回一个"忙"的标志,而这时程序再发多少数据都没任何意义,这时系统就抛出该异常通知。*/
  if (WSAEWOULDBLOCK != (iError = WSAGetLastError ()))
  {
  EditPrintf (hwndEdit, TEXT ("Connect error #%i.\r\n"), 
  iError) ;
  closesocket (sock) ;
  WSACleanup () ;
  return TRUE ;
  }
  // 显示正在连接服务器
  EditPrintf (hwndEdit, TEXT ("Connecting to %hs..."), szIPAddr) ;
  // 设置一个定时器,用于打印"."
  SetTimer (hwnd, ID_TIMER, 1000, NULL) ;
  // 获取hwndButton按钮的文字,保存到szOKLabel缓冲区
  GetWindowText (hwndButton, szOKLabel, sizeof (szOKLabel) /
  sizeof (TCHAR)) ;
  // 更改hwndButton按钮的文字。
  SetWindowText (hwndButton, TEXT ("Cancel")) ;
  // GWL_ID 设置一个新的窗口标识符
  SetWindowLong (hwndButton, GWL_ID, IDCANCEL) ;
  return TRUE ;
  // 取消连接被点击时
  case IDCANCEL:
  closesocket (sock) ;
  sock = 0 ;
  WSACleanup () ;
  SetWindowText (hwndButton, szOKLabel) ;
  SetWindowLong (hwndButton, GWL_ID, IDOK) ;
  KillTimer (hwnd, ID_TIMER) ;
  EditPrintf (hwndEdit, TEXT ("\r\nSocket closed.\r\n")) ;
  return TRUE ;
  // 点击关闭窗口时
  case IDC_CLOSE:
  if (sock)
  SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;                // GetParent 函数获得一个指定子窗口的父窗口句柄
  DestroyWindow (GetParent (hwnd)) ;
  return TRUE ;
  }
  return FALSE ;
  case WM_TIMER:
  EditPrintf (hwndEdit, TEXT (".")) ;
  return TRUE ;
  // WSAAsyncSelect函数中的自订消息
  case WM_SOCKET_NOTIFY:
  // 低字组包含了当前消息的触发条件
  wEvent = WSAGETSELECTEVENT (lParam) ;   // ie, LOWORD
  // 高字组包含了错误代码,0表示没有错误
  wError = WSAGETSELECTERROR (lParam) ;   // ie, HIWORD
  // Process two events specified in WSAAsyncSelect
  switch (wEvent)
  {
  // 当连接成功或失败时
  case FD_CONNECT:
  EditPrintf (hwndEdit, TEXT ("\r\n")) ;
  // 判断是否为连接失败
  if (wError)
  {
  EditPrintf (hwndEdit, TEXT ("Connect error #%i."), 
  wError) ;
  SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
  return TRUE ;
  }
  EditPrintf (hwndEdit, TEXT ("Connected to %hs.\r\n"), szIPAddr) ;
  /* 试图从一个套接口接收数据
  sock 已连接上的套接口的描述符
  ulTime 用于接收数据的缓冲区
  4 缓冲区长度
  MSG_PEEK 指定数据将被复制到缓冲区中,但并不从输入队列中删除。
  由于之前呼叫了WSAAsyncSelect,recv并不等待接收,而是立即传回错误代码,表示函数通常受阻,但这时没有受阻。
  理论上来说(但由于网络延迟所以不大可能),函数至少能传回数据的一部分,然后再次呼叫以获得其余的32个字节值。
  这就是呼叫recv函数时带有MSG_PEEK选项的原因。
  当服务器端准备好,程序可以接收数据时,将再次触发WM_SOCKET_NOTIFY消息,并包含FD_READ值 */
  recv (sock, (char *) &ulTime, 4, MSG_PEEK) ;
  EditPrintf (hwndEdit, TEXT ("Waiting to receive...")) ;
  return TRUE ;
  // 当可以接收数据时
  case FD_READ:
  KillTimer (hwnd, ID_TIMER) ;
  EditPrintf (hwndEdit, TEXT ("\r\n")) ;
  // 做错误检测
  if (wError)
  {
  EditPrintf (hwndEdit, TEXT ("FD_READ error #%i."), 
  wError) ;
  SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
  return TRUE ;
  }
  // 正式接收时间数据,最后的参数是0,用于从队列中删除数据                // 返回值若无错误发生,返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误
  iSize = recv (sock, (char *) &ulTime, 4, 0) ;
  // ntohl 将一个无符号长整形数从网络字节顺序转换为计算机字节顺序。
  ulTime = ntohl (ulTime) ;
  // 接收的32位的ulTime值是从1900年1月1日0:00开始的UTC秒数,但是为(big-endian)字序
  EditPrintf (hwndEdit, 
  TEXT ("Received current time of %u seconds ")
  TEXT ("since Jan. 1 1900.\r\n"), ulTime) ;
  // 更改系统时间,发送取消连接消息
  ChangeSystemTime (hwndEdit, ulTime) ;
  SendMessage (hwnd, WM_COMMAND, IDCANCEL, 0) ;
  return TRUE ;
  }
  return FALSE ;
  }
  return FALSE ;
  }
  BOOL CALLBACK ServerDlg (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  {
  staticchar * szServer ;
  static WORD   wServer = IDC_SERVER1 ;
  char          szLabel [64] ;
  switch (message)
  {
  case WM_INITDIALOG:
  // 保存服务器IP传递缓冲区
  szServer = (char *) lParam ;
  // 该函数给一组单选按钮中的一个指定按钮加上选中标志,并且清除组中其他按钮的选中标志
  // 默认情况下所有的单选按钮都在一个组内
  CheckRadioButton (hwnd, IDC_SERVER1, IDC_SERVER10, wServer) ;
  return TRUE ;
  case WM_COMMAND:
  switch (LOWORD (wParam))
  {
  case IDC_SERVER1:
  case IDC_SERVER2:
  case IDC_SERVER3:
  case IDC_SERVER4:
  case IDC_SERVER5:
  case IDC_SERVER6:
  case IDC_SERVER7:
  case IDC_SERVER8:
  case IDC_SERVER9:
  case IDC_SERVER10:
  // 保存选中的单选按钮标识符
  wServer = LOWORD (wParam) ;
  return TRUE ;
  case IDOK:
  // 获取选中的单选按钮的文本到szLabel缓冲区
  GetDlgItemTextA (hwnd, wServer, szLabel, sizeof (szLabel)) ;
  // 分解字符串为一组字符串,首次调用时,参数一指向要分解的字符串,之后再次调用要把参数一设成NULL
  // 参数二为分隔符字符串,函数在szLabel中查找包含在参数二中的字符并用NULL来替换,直到找遍整个字符串。
  // 成功执行后,szLabel被分割成了N个NULL隔开的字符串组。szLabel指向第一个分隔符后的字符串首地址。
  // 该函数已被strsep函数替代。
  strtok (szLabel, "(") ;
  // strtok 的第二次调用,从"("之后的第一个字符开始执行分割。返回值正好为IP地址字符串                // 然后复制IP地址,到用于传递的缓冲区中。
  strcpy (szServer, strtok (NULL, ")")) ;
  EndDialog (hwnd, TRUE) ;
  return TRUE ;
  case IDCANCEL:
  EndDialog (hwnd, FALSE) ;
  return TRUE ;
  }
  break ;
  }
  return FALSE ;
  }
  void ChangeSystemTime (HWND hwndEdit, ULONG ulTime)
  {
  FILETIME      ftNew ;
  LARGE_INTEGER li ;
  SYSTEMTIME    stOld, stNew ;
  // 该函数用来获取本机当前系统日期和时间
  GetLocalTime (&stOld) ;
  stNew.wYear         = 1900 ;
  stNew.wMonth        = 1 ;
  stNew.wDay          = 1 ;
  stNew.wHour         = 0 ;
  stNew.wMinute       = 0 ;
  stNew.wSecond       = 0 ;
  stNew.wMilliseconds = 0 ;
  // 该函数根据参数一的数据,填写FILETIME结构
  // FILETIME结构实际上只是由两个32位的DWORD一起组成64位的整数;
  // 用来表示从1601年1月1日至参数一中的时间间隔为100纳秒(nanosecond,十亿分之一秒)的间隔数。
  SystemTimeToFileTime (&stNew, &ftNew) ;
  // 将ftNew地址强制转换为LARGE_INTEGER类型的指针,再解除引用
  // LARGE_INTEGER 是一个union,允许64位的值可以被当成两个32位的值使用;
  // 或者当成一个__int64数据型态的64位整数使用(__int64数据型态是Microsoft编译器对ANSI C标准的扩充)。
  li = * (LARGE_INTEGER *) &ftNew ;
  // ulTime值是从1900年1月1日0:00开始的UTC秒数,乘以一千万倍。
  // li.QuadPart 最终等于1601年1月1日至ulTime时间,间隔为100纳秒的间隔数
  li.QuadPart += (LONGLONG) 10000000 * ulTime ; 
  ftNew = * (FILETIME *) &li ;
  // 将作为最终计算结果的FILETIME值转换回SYSTEMTIME结构
  FileTimeToSystemTime (&ftNew, &stNew) ;
  // 设置当前系统的时间和日期;执行成功,返回值为TRUE,如果发生了错误,则返回FALSE。
  // 调用者必须具有SE_SYSTEMTIME_NAME权限,函数的执行才会成功.
  if (SetSystemTime (&stNew))
  {
  // 获取系统新的时间,和之前的旧时间一同发送给自定义函数,用于显示更新状态
  GetLocalTime (&stNew) ;
  FormatUpdatedTime (hwndEdit, &stOld, &stNew) ;
  }
  else
  // 执行失败时,显示错误消息
  EditPrintf (hwndEdit, TEXT ("Could NOT set new date and time.")) ;
  }
  void FormatUpdatedTime (HWND hwndEdit, SYSTEMTIME * pstOld, SYSTEMTIME * pstNew)
  {
  TCHAR szDateOld [64], szTimeOld [64], szDateNew [64], szTimeNew [64] ;
  // 针对指定的"当地"格式,对一个系统日期进行格式化      // 参数一用于决定格式的地方ID。参数四中指定的任何信息(倘若不是NULL)都优先于特定于地方的信息
  // LOCALE_USER_DEFAULT 指定使用用户或进程的默认区域设置,该常数的值为0x0400
  // 参数二如指定了lpFormat,该参数应该为零。否则可设为LOCALE_NOUSEROVERRIDE,强制使用系统地方参数;
  // DATE_SHORTDATE 或 DATE_LONGDATE 用于选择不同的日期格式
  // pstOld 包含了一个系统日期的结构
  // 参数四用于指定使用特定于不同地方的值;例如d,dd,ddd,dddd或m,mm,mmm,mmmm或y,yy,yyyy这样的字符串
  // szDateOld 指定一个缓冲区,用于容纳格式化过后的字串
  // 最后是缓冲区的长度
  GetDateFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | DATE_SHORTDATE,
  pstOld, NULL, szDateOld, sizeof (szDateOld)) ;
  // 针对指定的"当地"格式,对一个系统时间进行格式化
  // TIME_NOTIMEMARKER 表示不使用时间标记;例如"AM"和"PM"
  // TIME_FORCE24HOURFORMAT 表示始终使用24小时时间格式
  GetTimeFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | 
  TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER ,
  pstOld, NULL, szTimeOld, sizeof (szTimeOld)) ;
  GetDateFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | DATE_SHORTDATE,
  pstNew, NULL, szDateNew, sizeof (szDateNew)) ;
  GetTimeFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | 
  TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT,
  pstNew, NULL, szTimeNew, sizeof (szTimeNew)) ;
  // 显示更新前后时间信息
  EditPrintf (hwndEdit, 
  TEXT ("System date and time successfully changed ")
  TEXT ("from\r\n\t%s, %s.%03i to\r\n\t%s, %s.%03i."), 
  szDateOld, szTimeOld, pstOld->wMilliseconds,
  szDateNew, szTimeNew, pstNew->wMilliseconds) ;
  }
  void EditPrintf (HWND hwndEdit, TCHAR * szFormat, ...)
  {
  TCHAR   szBuffer [1024] ;
  va_list pArgList ;
  va_start (pArgList, szFormat) ;
  wvsprintf (szBuffer, szFormat, pArgList) ;
  va_end (pArgList) ;
  SendMessage (hwndEdit, EM_SETSEL, (WPARAM) -1, (LPARAM) -1) ;
  SendMessage (hwndEdit, EM_REPLACESEL, FALSE, (LPARAM) szBuffer) ;
  SendMessage (hwndEdit, EM_SCROLLCARET, 0, 0) ;
  }
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics