代码详见:http://download.csdn.net/detail/swanabin/6771465

需求:截获桌面窗口鼠标单击事件,解析所选中的桌面 Item,并将解析后的 item 信息发送给主调程序,并将信息显示在一个窗口上面。如下图:

思路:

1. 确定HOOK的类型。很明显,这一个进程外的HOOK,我们的应用程序DesktopCaptor2.exe 需要捕获 Explorer.exe 这个进程的桌面窗口所在的线程的消息。因此,需要将HOOK过程放在一个独立的DLL 中去,然后使用 SetWindowsHookEx 将HOOK过程安装到HOOK链中去。

2. 如何解析点击的桌面 Item 信息?这个其实也比较好做,由于桌面窗口本身是一个 listview 控件(不解释),因此,我们可以通过 listview 拿到桌面窗口的简单信息。

3. 如何将解析后的桌面 Item 信息发送给主调程序,让它弹出一个窗口,并显示桌面 Item 信息?我们这里采用WM_COPYDATA 将从桌面进程获取到信息发送到我们的应用程序。

工程目录如下:

在主调程序(DesktopCaptor2.exe)是一个简单Win32 Dialog的程序。在这个程序中,我们干了两件事情:

1. 调用 DekstopHook 工程中的 DesktopHook.h 中的两个导出函数,通过这两个函数,对 HOOK 过程安装和卸载。

2. 接收来自于Explorer.exe 进程发送的 WM_COPYDATA 消息,还原桌面 Item 数据,并将在点击桌面 Item 的位置弹出一个窗口出来,显示 Item 信息。至于这个窗口如何制作,我就不再描述了。

以下为部分代码:

  1. #include "CommonDef.h"
  2. #include "DesktopHook.h"
  3. #include "FloatWin.h"
  4. const UINT WM_DESKTOP_CLICKED_ITEM = RegisterWindowMessage(L"WM_DESKTOP_CLICKED_ITEM");
  5. BOOL g_isCaptured = FALSE;
  6. CFloatWin* g_floatWin = NULL;
  7. INT_PTR WINAPI DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) ;
  8. int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int)
  9. {
  10. HWND hwnd = FindWindow(TEXT("#32770"), TEXT("DesktopCaptor2"));
  11. if (IsWindow(hwnd))
  12. {
  13. // An instance is already running, show a messagebox
  14. MessageBox(GetForegroundWindow(), L"An instance is already running", L"Error", MB_ICONERROR);
  15. }
  16. else
  17. {
  18. DialogBox(hinstExe, MAKEINTRESOURCE(IDD_DESKTOP_CAPTOR), NULL, DlgProc);
  19. }
  20. return(0);
  21. }
  22. INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  23. {
  24. UNREFERENCED_PARAMETER(lParam);
  25. switch (message)
  26. {
  27. case WM_INITDIALOG:
  28. {
  29. // Set icon for the application
  30. SendMessage(hDlg, WM_SETICON, ICON_BIG,  (LPARAM)
  31. LoadIcon((HINSTANCE) GetWindowLongPtr(hDlg, GWLP_HINSTANCE),
  32. MAKEINTRESOURCE(IDI_DESKTOPCAPTOR2)));
  33. // Set dialog's position
  34. int nScreenWidth = ::GetSystemMetrics(SM_CXSCREEN);
  35. int nScreenHeight = ::GetSystemMetrics(SM_CYSCREEN);
  36. RECT rect = { 0 };
  37. GetWindowRect(hDlg, &rect);
  38. SetWindowPos(
  39. hDlg,
  40. HWND_TOP,
  41. nScreenWidth - (rect.right - rect.left),
  42. 0,
  43. 0, 0,
  44. SWP_NOSIZE);
  45. g_floatWin = CFloatWin::getInstance();
  46. }
  47. return (INT_PTR)TRUE;
  48. case WM_COMMAND:
  49. {
  50. UINT wmId = LOWORD(wParam);
  51. UINT wmEvent = HIWORD(wParam);
  52. switch (wmId)
  53. {
  54. case IDOK:
  55. case IDCANCEL:
  56. EndDialog(hDlg, LOWORD(wParam));
  57. return (INT_PTR)TRUE;
  58. case IDC_START_CAPTOR:
  59. if (FALSE == g_isCaptured)
  60. {
  61. <strong> </strong>g_isCaptured = CreateDesktopEventCaptor(hDlg);
  62. }
  63. break;
  64. case IDC_STOP_CAPTOR:
  65. if (TRUE == g_isCaptured)
  66. {
  67. CloseDesktopEventCaptor();
  68. g_isCaptured = FALSE;
  69. }
  70. break;
  71. default:
  72. return DefWindowProc(hDlg, message, wParam, lParam);
  73. }
  74. }
  75. break;
  76. case WM_COPYDATA:
  77. {
  78. COPYDATASTRUCT* pCopyData = (COPYDATASTRUCT*)lParam;
  79. if (pCopyData->dwData == WM_DESKTOP_CLICKED_ITEM)
  80. {
  81. DesktopItemData itemData(*(DesktopItemData*)pCopyData->lpData);
  82. g_floatWin->ShowWindow(TRUE, &itemData);
  83. }
  84. }
  85. break;
  86. }
  87. return (INT_PTR)FALSE;
  88. }

我们重点说明一下DekstopHookDLL中的内容。

DesktopHook.h 中定义的是一些导出接口,代码如下:

  1. #ifndef _DESKTOPHOOK_H_
  2. #define _DESKTOPHOOK_H_
  3. //#ifdef __cplusplus
  4. //extern "C" {
  5. //#endif
  6. #ifdef DESKTOPHOOK_EXPORTS
  7. #define DESKTOPHOOK_API __declspec(dllexport)
  8. #else
  9. #define DESKTOPHOOK_API __declspec(dllimport)
  10. #endif
  11. typedef struct DESKTOPHOOK_API _DesktopItemData
  12. {
  13. POINT point;
  14. WCHAR szName[MAX_PATH];  // The desktop item's name.
  15. int nIndex;              // The desktop item's index on desktop.
  16. _DesktopItemData()
  17. {
  18. ZeroMemory(szName, MAX_PATH);
  19. point.x = point.y = 0;
  20. nIndex = -1;
  21. }
  22. _DesktopItemData(POINT pt, WCHAR* pszName, int nIx)
  23. {
  24. if (NULL != pszName)
  25. {
  26. point.x = pt.x;
  27. point.y = pt.y;
  28. ZeroMemory(szName, MAX_PATH);
  29. _tcscpy_s(szName, MAX_PATH, pszName);
  30. nIndex = nIx;
  31. }
  32. }
  33. _DesktopItemData(const _DesktopItemData& itemDataRef)
  34. {
  35. point.x = itemDataRef.point.x;
  36. point.y = itemDataRef.point.y;
  37. ZeroMemory(szName, MAX_PATH);
  38. _tcscpy_s(szName, MAX_PATH, itemDataRef.szName);
  39. nIndex = itemDataRef.nIndex;
  40. }
  41. _DesktopItemData& operator = (const _DesktopItemData& itemDataRef)
  42. {
  43. point.x = itemDataRef.point.x;
  44. point.y = itemDataRef.point.y;
  45. ZeroMemory(szName, MAX_PATH);
  46. _tcscpy_s(szName, MAX_PATH, itemDataRef.szName);
  47. nIndex = itemDataRef.nIndex;
  48. return *this;
  49. }
  50. } DesktopItemData, *LPDesktopItemData;
  51. EXTERN_C DESKTOPHOOK_API BOOL CreateDesktopEventCaptor(HWND hNotifierhWnd);
  52. EXTERN_C DESKTOPHOOK_API void CloseDesktopEventCaptor();
  53. //#ifdef __cplusplus
  54. //}
  55. //#endif
  56. #endif // _DESKTOPHOOK_H_

其中:

1. DesktopItemData 结构体是用来存放解析桌面 Item 的数据。

2. CreateDesktopEventCaptor 函数是用来安装 HOOK;

3. CloseDesktopEventCaptor 函数是用来卸载 HOOK;

以下是 DesktopHook.cpp 中的实现代码:

  1. // DesktopHook.cpp : Defines the exported functions for the DLL application.
  2. //
  3. #include "stdafx.h"
  4. #include "DesktopHook.h"
  5. #include "DesktopItem.h"
  6. #pragma data_seg("SHARED_DATA")
  7. HWND  g_hNotifierWnd = NULL;
  8. HHOOK g_hPostMsgHook = NULL;
  9. WCHAR g_szBuf[MAX_PATH] = {0};
  10. #pragma data_seg()
  11. #pragma comment(linker, "/SECTION:SHARED_DATA,RWS")
  12. // Global data
  13. const UINT WM_DESKTOP_CLICKED_ITEM = RegisterWindowMessage(L"WM_DESKTOP_CLICKED_ITEM");
  14. HMODULE g_hModule;
  15. HWND  g_hDesktopWnd  = NULL;
  16. // The low-order word specifies the x-coordinate of the cursor.
  17. // The high-order word specifies the y-coordinate of the cursor.
  18. BOOL g_bDoubleClick = FALSE;
  19. UINT_PTR g_timerID = 0;
  20. POINT g_clickPt;
  21. CDesktopItem g_singleClickDesktopItem = CDesktopItem::Empty;
  22. DesktopItemData g_desktopItemData;
  23. // Declaration of methods.
  24. static HWND FindShellWindow();
  25. static LRESULT CALLBACK GetMsgProc(int code,WPARAM wParam,LPARAM lParam);
  26. static VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
  27. EXTERN_C DESKTOPHOOK_API BOOL CreateDesktopEventCaptor(HWND hNotifierhWnd)
  28. {
  29. g_hDesktopWnd = FindShellWindow();
  30. if (NULL == g_hDesktopWnd)
  31. {
  32. OutputDebugString(L"Can not find desktop window handle");
  33. return FALSE;
  34. }
  35. g_hNotifierWnd = hNotifierhWnd;
  36. // Get desktop handle's thread id.
  37. DWORD targetThreadid = ::GetWindowThreadProcessId(g_hDesktopWnd, NULL);
  38. // Hook post message.
  39. g_hPostMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, &GetMsgProc, g_hModule, targetThreadid);
  40. if (g_hPostMsgHook != NULL)
  41. {
  42. return TRUE;
  43. }
  44. return FALSE;
  45. }
  46. EXTERN_C DESKTOPHOOK_API void CloseDesktopEventCaptor()
  47. {
  48. if (g_hPostMsgHook != NULL)
  49. {
  50. ::UnhookWindowsHookEx(g_hPostMsgHook);
  51. }
  52. }
  53. HWND FindShellWindow()
  54. {
  55. // Sometimes, we can't find the desktop window when we use this function, but we must
  56. // find it's handle, so we do a loop to find it, but at most we find for 10 times.
  57. UINT uFindCount = 0;
  58. HWND hSysListView32Wnd = NULL;
  59. while (NULL == hSysListView32Wnd && uFindCount < 10)
  60. {
  61. HWND hParentWnd = ::GetShellWindow();
  62. HWND hSHELLDLL_DefViewWnd = ::FindWindowEx(hParentWnd, NULL, L"SHELLDLL_DefView", NULL);
  63. hSysListView32Wnd = ::FindWindowEx(hSHELLDLL_DefViewWnd, NULL, L"SysListView32", L"FolderView");
  64. if (NULL == hSysListView32Wnd)
  65. {
  66. hParentWnd = ::FindWindowEx(NULL, NULL, L"WorkerW", L"");
  67. while((!hSHELLDLL_DefViewWnd) && hParentWnd)
  68. {
  69. hSHELLDLL_DefViewWnd = ::FindWindowEx(hParentWnd, NULL, L"SHELLDLL_DefView", NULL);
  70. hParentWnd = FindWindowEx(NULL, hParentWnd, L"WorkerW", L"");
  71. }
  72. hSysListView32Wnd = ::FindWindowEx(hSHELLDLL_DefViewWnd, 0, L"SysListView32", L"FolderView");
  73. }
  74. if (NULL == hSysListView32Wnd)
  75. {
  76. Sleep(1000);
  77. uFindCount++;
  78. }
  79. else
  80. {
  81. break;
  82. }
  83. }
  84. return hSysListView32Wnd;
  85. }
  86. // The message which is "Post" type can be hook in this hook procedure.
  87. LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
  88. {
  89. MSG* pMsg = (MSG*)lParam;
  90. if( NULL != pMsg && pMsg->hwnd != NULL  )
  91. {
  92. switch(pMsg->message)
  93. {
  94. case WM_MOUSEMOVE:
  95. {
  96. //OutputDebugStringW(L"Mouse Move");
  97. }
  98. break;
  99. case WM_LBUTTONUP:
  100. {
  101. //OutputDebugStringW(L"WM_LBUTTONUP");
  102. if (g_bDoubleClick)
  103. {
  104. OutputDebugString(L"g_bDoubleClick == TRUE");
  105. g_singleClickDesktopItem = CDesktopItem::Empty;
  106. g_bDoubleClick = FALSE;
  107. ::KillTimer(NULL, g_timerID);
  108. }
  109. else
  110. {
  111. OutputDebugString(L"g_bDoubleClick == FALSE");
  112. ::KillTimer(NULL, g_timerID);
  113. g_clickPt.x = pMsg->pt.x;
  114. g_clickPt.y = pMsg->pt.y;
  115. g_singleClickDesktopItem = pMsg;
  116. g_timerID = ::SetTimer(NULL, 1, ::GetDoubleClickTime(), TimerProc);
  117. }
  118. }
  119. break;
  120. case WM_LBUTTONDBLCLK:
  121. g_bDoubleClick = TRUE;
  122. break;
  123. }
  124. }
  125. return ::CallNextHookEx(g_hPostMsgHook, code, wParam, lParam);
  126. }
  127. VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
  128. {
  129. UNREFERENCED_PARAMETER(hwnd);
  130. UNREFERENCED_PARAMETER(uMsg);
  131. UNREFERENCED_PARAMETER(idEvent);
  132. UNREFERENCED_PARAMETER(dwTime);
  133. if (g_timerID == idEvent &&
  134. g_bDoubleClick == FALSE &&
  135. g_singleClickDesktopItem != CDesktopItem::Empty)
  136. {
  137. OutputDebugString(L"SendMessageTimeout Process begin");
  138. wstring strName = g_singleClickDesktopItem.GetItemName();
  139. //_tcscpy_s(g_szBuf, MAX_PATH, strName.c_str());
  140. //::PostMessage(g_hNotifierWnd, WM_DESKTOP_CLICKED_ITEM, NULL, (LPARAM)g_szBuf);
  141. ZeroMemory(&g_desktopItemData, sizeof(DesktopItemData));
  142. g_desktopItemData.point.x = g_clickPt.x;
  143. g_desktopItemData.point.y = g_clickPt.y;
  144. _tcscpy_s(g_desktopItemData.szName, MAX_PATH, strName.c_str());
  145. g_desktopItemData.nIndex = g_singleClickDesktopItem.GetItemIndex();
  146. COPYDATASTRUCT copyData;
  147. copyData.dwData = (UINT_PTR)WM_DESKTOP_CLICKED_ITEM;
  148. copyData.cbData = sizeof(DesktopItemData);
  149. copyData.lpData = &g_desktopItemData;
  150. DWORD_PTR rt = -1;
  151. SetActiveWindow(g_hNotifierWnd);
  152. SetForegroundWindow(g_hNotifierWnd);
  153. //SendMessageTimeout(g_hNotifierWnd, WM_COPYDATA, (WPARAM)g_hDesktopWnd, (LPARAM)©Data, SMTO_NORMAL, 1000, &rt);
  154. SendMessage(g_hNotifierWnd, WM_COPYDATA, (WPARAM)g_hDesktopWnd, (LPARAM)©Data);
  155. OutputDebugString(L"SendMessageTimeout Process end");
  156. }
  157. ::KillTimer(NULL, g_timerID);
  158. }

说明:在该实现中,CreateDesktopEventCaptor 函数与 CloseDesktopEventCaptor 函数被 DesktopCaptor2.exe 进程调用,此时,DesktopHook.dll 会被加载到 DesktopCaptor2.exe 所在进程,当使用 CreateDesktopEventCaptor 函数后,HOOK 过程函数 GetMsgProc 会被安装到 Explorer.exe 进程中去,此时,DesktopHook.dll 会被加载到 Explorer.exe 中去。当用鼠标单击桌面 Item 时,消息将被传递到 GetMsgProc 中去,然后,在这个函数中发送一个 WM_COPYDATA 消息给DesktopCaptor2 的窗口,实际上,这相当于是 Explorer.exe 进程发送的 WM_COPYDATA 消息到 DesktopCaptor2.exe 进程 。需要注意一点的是,在 GetMsgProc 中发送消息的时候,g_hNotifierWnd 必须要设置成为共享数据,因为它是DesktopCaptor2.exe 进程在调用DesktopHook.dll 的 CreateDesktopEventCaptor 函数的时候被设置的,要想在Explorer.exe进程中继续有效,需要将之设置为 DLL共享数据。

另外,这里还有另外两个问题:

  1. 如何找到 Desktop 窗口句柄? 通过Spy++,我们得到:

其中,蓝色部分就是我们的桌面窗口句柄。我们的 FindShellWindow 函数就是为了干这个事情,但是有时候,这个函数会失败,因此,我们最多循环10次去查找桌面窗口句柄。

  1. WH_GETMESSAGE 类型的消息 HOOK 只能钩住使用 PostMessage 方式发送的消息,这点很重要,否则,如果是以SendMessage发送的方式,则需要使用 WH_CALLWNDPROC
    或者 WH_CALLWNDPROCRET 的 HOOK 方式。
  1. 如何解析点击桌面的 Item 信息?

实际上,由于桌面窗口本身是一个 ListView 控件,通过ListView 的控件的API,我们便能够拿到相关的信息(当然这里只是一个简单的信息,深层次的信息还需要深掘)。

首先,通过下面的函数,能够拿到选中的 Item 的 ID,其中 hwnd 就是桌面窗口句柄。

UINT ListView_GetSelectedCount(

HWND hwnd

);

其次,通过下面的函数,传入选中的 Item 的 ID,我们就能拿到 Item 对应的文本。

void ListView_GetItemText(

HWND hwnd,

    int iItem,

    int iSubItem,

    LPTSTR pszText,

    int cchTextMax

);

在这里,我使用了一个 CDesktopItem 类来专门干这个事情。

具体请详见附录代码。

至此,简单的代码讲解就结束了。

下面说一下如何调试。

因为本例子是进程外的HOOK,因此调试起来有很多不方便的地方。调试的难处在于如何调试HOOK过程函数。先说一个简单的例子,可能大家经常会遇到这种情况:假如一个Solution下有ProjectA和 Project
B,它们都是exe,但涉及到相互发消息,有人就会打开2个Visual
Studio,同时进行调试,这样的确可以做到,但总感觉不太方便。实际上,在同一个Visual Studio中,是可以同时调试多个程序的。对于刚刚这种情况,只需要在每个Project A 和 ProjectB 上面分别右击->Debug->Start
new instance 即可。

然而,对于本例,这样做是不行的,因为 DesktopHook 是一个DLL工程,本身是无法进行独立调试的。因此,要想同时调试 DesktopCaptor2 工程和 DesktopHook 工程,需要按如下操作:

  1. 将DesktopHook工程设置为默认启动的工程。如图:

  1. 将 DesktopCaptor2 工程通过 右击->Debug->Start
    new instance 启动起来,点击 Start Desktop
    Captor 按钮启动HOOK,将DesktopHook .dll 注入到进程Explorer.exe中去。

  1. 将 DesktopHook  工程到附加(Attach)到Explorer.exe进程中去。如图:

点击 Tools->Attachto Process...

然后找到Explorer.exe,点击Attach按钮即可。

通过上面的这种方式,我们就能够很简单的在一个工程中,调试两个不同的进程的程序。这时,我们将断点打在DesktopHook 工程的函数
GetMsgProc中,并在DesktopCaptor2 工程 WM_COPYDATA 内部打上断点,发现点击桌面图标时,断点会走到GetMsgProc内部,当发送完消息后,就能走到DesktopCaptor2 工程 WM_COPYDATA 内部。。

另外,你调试的时候,要注意一下当前用户的权限以及Visual Studio的权限。如果当前用户是管理员组用户,而你的VisualStudio是以管理员启动进来的,那么DesktopCaptor2.exe将也是管理员权限,但此时,Explorer.exe却是普通权限,此时,在GetMsgProc内部发送WM_COPYDATA是会出现问题的,因为不能向高权限进程发送消息。

The End...

如何HOOK桌面窗口消息的更多相关文章

  1. Windows窗口消息大全(转)

    Windows窗口消息大全,全不全自己看 ////////////////////////////////////////////////////////////////////////// #inc ...

  2. [8]windows内核情景分析--窗口消息

    消息与钩子 众所周知,Windows系统是消息驱动的,现在我们就来看Windows的消息机制. 早期的Windows的窗口图形机制是在用户空间实现的,后来为了提高图形处理效率,将这部分移入内核空间,在 ...

  3. Windows窗口消息大全

    ////////////////////////////////////////////////////////////////////////// #include "AFXPRIV.H& ...

  4. Cocos2dx集成于windows桌面窗口程序的步骤

    2D游戏需要做编辑器,而编辑器总是希望可以复用游戏中的逻辑来运行场景试看效果. 对于cocos2dx开发的程序,这个需求可以描述为: 实现一种方法,在桌面窗口程序中的某个控件上显示cocos2dx的场 ...

  5. 2019-11-12-WPF-添加窗口消息钩子方法

    title author date CreateTime categories WPF 添加窗口消息钩子方法 lindexi 2019-11-12 18:46:53 +0800 2019-06-05 ...

  6. spy++捕获窗口消息

    打开spy++,窗口截图如下,点击窗口搜索按钮(红框标识) ,如果找不到对应的窗口,鼠标右键刷新即可. 鼠标左键点击窗口搜索图标,按住不放,拖到需要抓取消息的窗口上: spy++会自动在列表中高亮定位 ...

  7. 【转】Windows消息投递流程:一般窗口消息投递(WM_LBUTTONCLICK)

    原文网址:http://blog.csdn.net/hyhnoproblem/article/details/6182646 本例通过在单文档程序的视图中添加WM_LBUTTONCLICK消息处理函数 ...

  8. VCL控件组件大都应该重载TWinControl的虚函数WndProc来进行处理窗口消息的工作

    TWinControl的构造函数中会调用MakeObjectInstance并且传递MainWndProc作为窗口消息处理函数,而MainWndProc则会调用虚函数WndProc来处理窗口消息.留个 ...

  9. MFC窗口消息PostMessage和SendMessage

    以前这些消息用得比较少,但是今天碰到了个事儿,我看非用消息不可. 事情是这样的,我在线程中需要刷新对话框上面的内容,但是每每执行到UpdateData时就出现了断言错误. 查了相关资料,发现这个可能是 ...

随机推荐

  1. linux 7 添加永久路由方法

    linux 7 添加永久路由 用route命令添加 仅仅是当前状态下生效,一旦重启就会失效. 所以要在/etc/sysconfig/network-scripts/这个路径下添加一个文件route-{ ...

  2. vue实现curd功能

    一.实现效果 二.实现 (一)实现增加用户功能 Vuserlist组件中 <template> <div class="panel panel-default"& ...

  3. vue-cli搭建vue开发环境

    前置环境 npm install -g vue-cli vue list 已安装环境后: vue init webpack sell 建立项目名称sell----------------------- ...

  4. bzoj1001题解

    [解题思路] 显然,这题的答案是这个网格图的最小割.根据最大流-最小割定理,我们可以用网络流算法来求其最小割,时间复杂度最小为O(V2√E). 特殊的,这个网格图是一个平面图,于是可以根据平面图最小割 ...

  5. 关于windows下远程连接Linux服务器的方法(CentOs)

    1.服务器端安装VNC 1) 安装vncserver yum install -y tigervnc-server 2) 修改配置 vi /etc/sysconfig/vncservers   最后两 ...

  6. java web 在tomcat没有正常输出

    目录 文章背景 目录 问题介绍 问题解决 说明 参考文章 版本记录 文章背景 调试程序时候突然发现一些位置设置的日志输出没有了,最后总算是解决了! 目录 问题介绍 本地运行时候的环境如下: windo ...

  7. sqlalchemy session

    Cookie cookie是浏览器保存在用户电脑上的一小段文本,用来保存用户在网站上的必要的信息.Web页面或服务器告诉浏览器按照一定的规范存储这些信息,并且在以后的所有请求中,这些信息就会自动加在h ...

  8. (转)VS2010-MFC编程入门教程之目录和总结

     目前该教程可以到鸡啄米编程课堂去学习,阅读体验更好,更适合在线学习. 原文目录及链接: 一.VS2010/MFC编程入门教程之目录 第一部分:VS2010/MFC开发环境 VS2010/MFC编程入 ...

  9. CSS3:CSS3 背景

    ylbtech-CSS3:CSS3 背景 1.返回顶部 1. CSS3 背景 CSS3 背景 CSS3中包含几个新的背景属性,提供更大背景元素控制. 在本章您将了解以下背景属性: background ...

  10. MDK(KEIL) 两步解决 中文乱码 及 中文光标 半个半个跳的问题

    1. 如果已经用MDK(KEIL)的默认设置写了好多中文,那么先用notepad把文件一一打开然后转变编码格式为 utf-8 without ROM,如下: 2. 如果还没有开始编辑,或者已经用not ...