windows 程序设计之「NetTime.C」范例分析笔记
2015-10-31 15:44
459 查看
/*------------------------------------------------------- NETTIME.C -- Sets System Clock from Internet Services (c) Charles Petzold, 1998 -------------------------------------------------------*/ #include <windows.h> #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) { static char szIPAddr[32] = { "132.163.135.130" } ; static HWND hwndButton, hwndEdit ; static SOCKET sock ; static struct sockaddr_in sa ; static TCHAR szOKLabel[32] ; int iError, iSize ; unsigned long 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) { static char * 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) ; }
相关文章推荐
- Qt 高仿QQ窗口抖动效果【原】
- jQuery实现DOM加载方法源码分析
- SVMshow
- 【英语】没有光环的主角
- 跨域解决方案 & 使用angularJS和jQuery进行Ajax请求的差异
- 移通学院 2015校内赛
- c语言,将字符串逆置,如"i am a student"逆置为"student a am i"
- linux操作口令
- 第一个
- Java HashSet类
- 回文字符串的判断
- bash编程之case语句:用法格式
- [转]CreateDIBitmap与CreateDIBSection
- Android实验一(在Android Studio中创建项目和模拟器)
- Java ArrayList类
- Hive与Hbase的区别
- 【原创】高仿360安全卫士 9.2 界面 (纯qml实现)
- 适配器模式
- Charles 安装图解(Mac 抓包工具)
- Java LinkedList类链表