浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第22章 DLL注入和API拦截(1)

Posted on 2016-02-04 00:20  浅墨浓香  阅读(1970)  评论(0编辑  收藏  举报

22.1 注入的一个例子(跨进程子类化窗口)

 

  ①子类化窗口可以改变窗口的行为,让发往该窗口的消息重新发到我们指定的过程来处理。但这种行为只能在本进程中(如A),对于从一个进程(如B)去子类化另一个进程(如A)时,会遇到无法跨越进程地址空间的边界问题。

  ②上图,进程B试图调用SetWindowLongPtr将进程A中的hWnd窗口过程,重新指定为MySubClassProc来处理(注意,MySubClassProc在进程B的地址空间中),而不是hWnd窗口的标准窗口处理过程。但因该行为是跨进程的,如果被允许的话,当系统要执行进程A的hWnd窗口过程时,就会试图调用进程A地址空间中另一个地址(该地址的值与进程B中的地址相同),原因是不同进程的地址空间是相互隔离的,所以会出现内存访问违规的错误。因此,为了避免这种错误,微软不允许SetWindowLongPrt修改别的进程的窗口过程

  ③要实现跨进程子类化窗口,须用到“DLL注入”技术,将DLL注入到进程的地址空间中。

22.2 使用注册表来注入DLL

(1)注册表项的使用

 

  ①AppInit_Dlls键可能会包含一个或一组Dll的文件名(用空格或逗号分隔,第一个Dll的文件名可包含路径,但其他Dll包含的路径会被忽略)。为了让系统使用这个注册表项,还须将LoadAppInit_Dlls的值设为1。

  ②因为路径可能会被忽略,所以最好将自己的Dll放到Windows的系统目录中,这样就不必指定路径了。

(2)User32.dll的映射

  ①当User32.dll被映射到一个新的进程时,该进程会收到DLL_PROCESS_ATTACH通知。User32.dll对它进行处理的时候,会取得上述注册表键的值,并调用LoadLibrary来载入这个字符串中指定的每个DLL。

  ②当系统载入每个DLL时,会调用它们的DllMain函数,并将参数fdwReason设为DLL_PROCESS_ATTACH,这样每个DLL就能够对自己进行初始化。

  ③由于被注入的DLL是在进程生命期的早期被载入,所以调用Kernel32.dll中的函数是没问题的,但其他DLL中的函数可能会导致问题。

(3)注册表注入DLL的缺点

  ①基于GUI的应用程序都使用了User32.dll,但大多数基于CUI的应用程序不会使用它。因此想要将DLL注入到编译器或链接器,这种方法是行不通的。

  ②DLL会被映射进每个基于GUI的应用程序中,导致这些进程的线程都在运行我们的代码,如果我们的代码进入无限循环或错误地访问了内存,那么会影响到“容器”进程(即其他基于GUI的所有进程)的行为和健壮性

  ③我们的DLL被映射到每个基于GUI应用程序中,其生命期会直到应用程序终止(因为进程终止时,才会发送DLL_PROCESS_DETACH通知给User32.dll,在这里会卸载到我们的DLL)。而理想的情况下,DLL应注入到我们需要的进程中,并且映射的时间越短越好。

22.3 使用Windows钩子来注入DLL

22.3.1 钩子的类型

(1)按作用范围

  ①局部钩子:仅钩挂属于自身进程的事件

  ②远程钩子:可钩挂自身进程,还可以钩挂其他进程中发生的事件。又分为基于线程和系统范围的。

(2)按监视消息的类型分类

钩子名称

监视消息的类型和时机

WH_CALLWNDPROC

每当调用SendMessage函数时,函数将消息发送给目标窗口前首先调用钩子函数

WH_CALLWNDPROCRET

每当调用SendMessage函数时,函数将消息发送给目标窗口过程后再调用钩子函数

WH_GETMESSAGE

每当调用GetMessage或PeekMessage函数时,函数从程序的消息队列中获取一个消息后调用钩子函数

WH_KEYBOARD

每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是WM_KEYUP或WM_KEYDOWN消息时,则调用钩子函数

WH_MOUSE

每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是鼠标消息,则调用钩子函数

WH_HARDWARE

每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是非鼠标和键盘消息时,则调用钩子函数

WH_MSGFILTER

当用户对对话框、菜单和滚动条有所操作时,系统在发送对应的消息之前调用钩子函数,这种钩子只能是局部的

WH_SYSMSGFILTER

同WH_MSGFILTER,不过是系统范围的

WH_SHELL

当Windows shell程序准备接收一些通知事件前调用钩子函数,如shell被激活或重绘等。外壳应用程序是不接受WH_SHELL消息的,要使应用程序能够接收WH_SHELL,必须调用SystemParametersInfo函数注册该消息。WM_SHELL共有5种情况:

①TaskBar需要重绘某个按钮时

②当系统地要显示关于Taskbar的一个程序的最小化形式

③当目前的键盘布局状态改变。

④当按Ctrl+Esc去执行TaskManager(或相等级别的程序)时。

⑤只要有个top-level、unowned窗口被创建、起作用或被摧毁

WH_DEBUG

用来给其他钩子函数除错

WH_CBT

当基于计算机的训练(CBT)事件发生时调用钩子函数

WH_JOURNALRECORD

日志记录钩子,用来记录发送给系统消息队列的所有消息

WH_JOURNALPLAYBACK

日志回放钩子,用来回放日志记录钩子记录的系统事件

WH_FOREGROUNDIDLE

系统空闲钩子,当系统空闲的时候调用钩子函数,这样就可以在这里安排一些低优先级的任务

22.3.2 钩子程序的结构:

(1)主要的3个功能模块

模块

作用

①主程序

用来实现界面或者其他功能

②钩子回调函数

用来接收系统发过来的消息

①对于局部钩子,这些模块可在同一个EXE文件中

②对于远程钩子这个回调函数必须在一个DLL中

③钩子的安装和卸载模块

调用SetWindowsHookEx和UnhookWindowsHookEx函数。

①这部分虽然没有要求,但一般也放在DLL中,因为钩子创建以后会得到一个钩子句柄,这个句柄要在钩子回调函数中,以及卸载钩子的时候用到。

②如果把这部分代码放在主程序中的话,还需要创建一个函数将它传回给动态链接库,所以不如直接放在库中

(2)钩子的安装:SetWindowsHookEx

参数

说明

int idHook

要安装的钩子的类型

HOOKPROC lpfn

钩子回调函数(在我们的地址空间或某个DLL)

HINSTANCE hInstDll

标识一个DLL,这个DLL包含了钩子回调函数。

如果是局部钩子这里须为NULL。

DWORD dwThreadId

给哪个线程安装钩子。如果为0,表示给系统中所有的GUI线程安装钩子。

(3)举例说明钩子的工作过程——以WM_GETMESSAGE为例(进程A监视进程B的窗口消息,设钩子回调函数为GetMsgProc)

  ①当进程B的一个线程准备GetMessage一条窗口消息时。系统会检查该线程是否安装了WH_GETMESSAGE钩子。

  ②系统检查GetMsgProc所在的DLL是否己被映射进程B的地址空中。

  ③如果DLL尚未被映射,那么系统会强制将该DLL映射到进程B的地址空间中,并将进程B中该DLL的锁计数器递增

  ④由于安装钩子时参数hInstDll和回调函数的地址都是进程A地址空间中的值。当被映射到进程B时,系统会检查该DLL被映射的基地址是否与进程A中的位置相同。

    Ⅰ如果相同,那么在两个进程的地址空间中,GetMsgProc函数位于相同的位置,在这情况下,系统可以直接在进程B(课本这里写A,是否是错误?)的地址空间中调用GetMsgProc。

    Ⅱ如果不同,则得通过GetMsgProcB = hInstDllB + (GetMsgProcA - hInstDllA)公式计算GetMsgProc在进程B中的虚拟地址。

  ⑤系统在进程B中递增该DLL的锁计数器,这是因为调用GetMsgProc函数时须先锁定,以防止该DLL被别的线程卸载掉。当GetMsgProc返回后,系统递减该DLL在进程B中的锁计数器。

(4)钩子的卸载:BOOL UnhookWindowsHookEx(HHOOK hHook)

  ①当一个线程调用UnhookWindowsHookEx时,系统会遍历其内部维护的一个进程列表,该列表记录着那些己经注入过该DLL的进程。

  ②并将这些进程中该DLL的锁计数递减(注意每个进程都有各自对该DLL的锁计数器)。当某个进程的锁计数器减到0时,系统会自动从该进程的地址空间中撤消对该DLL的映射。

22.3.3 钩子链

(1)钩子链

  ①每一个钩子都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向由应用程序定义的、后面由钩子调用的回调函数,也就是该钩子的各个处理子程。

  ②多个程序可以同时安装同一种钩子(如WH_GETMESSAGE),这些钩子组成一个钩子链,最近加入的钩子放在链表的头部,Windows负责为每种钩子维护一个钩子链。

  ③当一个事件发生的时候,Windows调用最后安装的钩子,然后由这个钩子的回调函数发起调用下一个钩子的动作,Windows收到这个动作后,再从链表中取出下一个钩子的地址并将调用传递下去。

(2)CallNextHookEx函数:调用下一个钩子回调函数的函数。

【Desktop Item Position Saver(DIPS)工具】——用来保存/恢复桌面图标的位置

(1)可能遇到的几个问题

  ①获得桌面的ListView控件窗口句柄的方法

  A、XP下

  //桌面的ListView控件是ProgMan窗口的孙子窗口

  hWndLV = GetFirstChild(GetFirstChild(FindWindow(TEXT("ProgMan"),NULL)));

  B、Win8下

HWND GetDestopListViewHandle(){
    HWND hWnd = NULL,hWndItem;
    HWND hWndParent = FindWindowEx(0,0,TEXT("WorkerW"),NULL);
    while (hWndParent && (!hWnd)){

        hWndItem = FindWindowEx(hWndParent, 0, TEXT("SHELLDLL_DefView"), NULL);
        if (hWndItem){
            hWnd = FindWindowEx(hWndItem, 0, TEXT("SysListView32"), TEXT("FolderView"));
            break;
        }
        hWndParent = FindWindowEx(0, hWndParent, TEXT("WorkerW"), NULL);
    }
    return hWnd;
}

  ②获取创建窗口的线程ID:dwThreadID = GetWindowThreadProcessID(hwnd,NULL)

  ③为Windows资源管理器创建一个“隐藏窗口”用于与DIPS.exe通信(利用窗口消息)

  ④卸载钩子时,须先销毁“隐藏窗口”这个对话框,再清除钩子。否则当对话框收到一下条消息时会导致Windows资源管理器的线程引发访问违规。

(2)实例源码

【动态链接库】

/************************************************************************
Module:  DIPSLib.h
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/
#pragma  once
#include <windows.h>

#if !defined(DIPSLIBAPI)
#define DIPSLIBAPI __declspec(dllimport)
#endif

//////////////////////////////////////////////////////////////////////////

//导出的外部函数原型
DIPSLIBAPI  BOOL WINAPI  SetDIPSHook(DWORD dwThreadId);


//////////////////////////////////////////////////////////////////////////

//DIPSLib.cpp

/************************************************************************
Module:  DIPSLib.cpp
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/

#include "../../CommonFiles/CmnHdr.h"


#define DIPSLIBAPI __declspec(dllexport)
#include "DIPSLib.h"
#include "resource.h"

//////////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
//这个函数会强制调试器被调用
void ForceDebugBreak(){
    __try{ DebugBreak();}
    __except (UnhandledExceptionFilter(GetExceptionInformation())){}
}
#else
#define ForceDebugBreak()
#endif

//////////////////////////////////////////////////////////////////////////
//前置声明
LRESULT  WINAPI GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam);

INT_PTR  WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

//////////////////////////////////////////////////////////////////////////
//通知编译器将g_hHook变量放在共享数据段中,以便让不同进程共享
#pragma data_seg("Shared")
HHOOK  g_hHook = NULL;
DWORD  g_dwThreadIdDIPS = 0; //安装钩子的线程(这里是DIPS.exe中一个线程)
#pragma data_seg()

#pragma comment(linker,"/section:Shared,rws") //可读可读、可共享

//////////////////////////////////////////////////////////////////////////

HINSTANCE g_hInstDll = NULL;

//////////////////////////////////////////////////////////////////////////
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, PVOID fImpLoad){
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        g_hInstDll = hInstDll;
        break;

    case DLL_THREAD_ATTACH:
        break;

    case DLL_THREAD_DETACH:
        break;

    case DLL_PROCESS_DETACH:
        break;
    }
    return (TRUE);
}

//////////////////////////////////////////////////////////////////////////
BOOL WINAPI SetDIPSHook(DWORD dwThreadId){
    BOOL bOk = FALSE;

    if (dwThreadId != 0){
        chASSERT(g_hHook == NULL);//断言钩子还未被安装

        //保存我们的线程ID,以便在GetMsgProc函数里可以通过客户程序(DIPS.exe)与
        //服务程序(Windows资源管理器)间进行通信
        g_dwThreadIdDIPS = GetCurrentThreadId();

        //为指定的线程安装WH_GETMESSAGE钩子
        g_hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hInstDll, dwThreadId);

        bOk = (g_hHook != NULL);
        if (bOk){
            //钩子安装成功,发送一条消息到Windows资源管理器的消息队列里,这时资源管理器
            //会检测到一条消息,并调用GetMessage,于是钩子函数就开始工作。
            bOk = PostThreadMessage(dwThreadId, WM_NULL, 0, 0);
        }

    } else{
        //当传入dwThreadId为0时,表示要卸载钩子
        chASSERT(g_hHook != NULL);//断言钩子己被安装,
        bOk = UnhookWindowsHookEx(g_hHook);
        g_hHook = NULL;
    }

    return (bOk);
}

//////////////////////////////////////////////////////////////////////////
LRESULT WINAPI GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam){
    static BOOL bFirstTime = TRUE;

    if (bFirstTime){
        //Dll刚被注入
        bFirstTime = FALSE;

        //当调试被注入DLL的进程时,要取消注释
        //ForceDebugBreak();

        //创建DIPS服务窗口,以处理客户请求
        CreateDialog(g_hInstDll, MAKEINTRESOURCE(IDD_DIPS), NULL, Dlg_Proc);

        //通知DIPS应用程序服务窗口己创建完毕,可以接收请求了
        PostThreadMessage(g_dwThreadIdDIPS, WM_NULL, 0, 0);
    }
    return (CallNextHookEx(g_hHook, nCode, wParam, lParam));
}

//////////////////////////////////////////////////////////////////////////
void Dlg_OnClose(HWND hWnd){
    DestroyWindow(hWnd);
}

//////////////////////////////////////////////////////////////////////////
static const TCHAR g_szRegSubKey[] = 
        TEXT("Software\\Wintellect\\Desktop Item Position Saver");

//////////////////////////////////////////////////////////////////////////
void SaveListViewItemPositions(HWND hWndLV){
    int nMaxItems = ListView_GetItemCount(hWndLV);

    //删除先前的旧信息
    LONG lRet = RegDeleteKey(HKEY_CURRENT_USER, g_szRegSubKey);

    //创建新的注册表项
    HKEY hKey;
    lRet = RegCreateKeyEx(HKEY_CURRENT_USER, g_szRegSubKey, 0, NULL, REG_OPTION_NON_VOLATILE,
                          KEY_SET_VALUE, NULL, &hKey, NULL);

    chASSERT(lRet == ERROR_SUCCESS);

    for (int nItem = 0; nItem < nMaxItems;nItem++){

        //获取ListItem项的名称及位置
        TCHAR szName[MAX_PATH];
        ListView_GetItemText(hWndLV, nItem, 0, szName, _countof(szName));

        POINT pt;
        ListView_GetItemPosition(hWndLV, nItem, &pt);

        //保存名称及位置
        lRet = RegSetValueEx(hKey, szName, 0, REG_BINARY, (PBYTE)&pt, sizeof(pt));

        chASSERT(lRet == ERROR_SUCCESS);
    }

    RegCloseKey(hKey);
}

//////////////////////////////////////////////////////////////////////////
void RestoreListViewItemPosition(HWND hWndLV){
    HKEY hKey;
    LONG lRet = RegOpenKeyEx(HKEY_CURRENT_USER, g_szRegSubKey, 0, KEY_QUERY_VALUE, &hKey);

    if (lRet != ERROR_SUCCESS)
        return;
        
    //如果ListView打开了自动排列属性,则暂时关闭
    DWORD dwStyle = GetWindowStyle(hWndLV);
    if (dwStyle & LVS_AUTOARRANGE)
        SetWindowLong(hWndLV, GWL_STYLE, dwStyle & ~LVS_AUTOARRANGE);

    lRet = NO_ERROR;
    for (int nIndex = 0; lRet != ERROR_NO_MORE_ITEMS; nIndex++){
        TCHAR szName[MAX_PATH];
        DWORD cbValueName = _countof(szName);//必须要在循环体内重新设置该值,因为每次会被RegEnumValue改变,用来接受下一个szName  

        POINT pt;
        DWORD cbData = sizeof(pt), nItem;//必须要在循环体内重新设置cbData,因为每次会被RegEnumValue改变

        //从注册表中读取名称及位置信息
        DWORD dwType;

        lRet = RegEnumValue(hKey, nIndex, szName, &cbValueName, NULL, &dwType, (PBYTE)&pt, &cbData);

        if (lRet == ERROR_NO_MORE_ITEMS)
            continue;

        if ((dwType == REG_BINARY) && (cbData = sizeof(pt))){
            //查找ListView控件相应的项目
            LV_FINDINFO  lvfi;
            lvfi.flags = LVFI_STRING;
            lvfi.psz = szName;
            nItem = ListView_FindItem(hWndLV, -1, &lvfi);
            if (nItem != -1){
                ListView_SetItemPosition(hWndLV, nItem, pt.x, pt.y);
            }
        }
    }
    //将自动排列属性恢复为原来的值
    SetWindowLong(hWndLV, GWL_STYLE, dwStyle);
    RegCloseKey(hKey);
}

//////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    switch (uMsg)
    {
        chHANDLE_DLGMSG(hWnd, WM_CLOSE, Dlg_OnClose); //隐藏服务窗口
    case WM_APP:
         //当调试被注入DLL的进程时,要取消注释
         //ForceDebugBreak();

        //lParam:表示保存或恢复操作。wParam:桌面ListView的句柄
        if (lParam)
            SaveListViewItemPositions((HWND)wParam);
        else
            RestoreListViewItemPosition((HWND)wParam);
        break;
    }
    return (FALSE);
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 22_DipsLib.rc 使用
//
#define IDD_DIPS                        101

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//rc文件

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_DIPS DIALOG DISCARDABLE  0, 0, 132, 13
STYLE WS_CAPTION
CAPTION "Wintellect DIPS"
FONT 8, "MS Shell Dlg"
BEGIN
END

/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_DIALOG1, DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 302
        TOPMARGIN, 7
        BOTTOMMARGIN, 169
    END
END
#endif    // APSTUDIO_INVOKED

#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

【DIPS工具源码】

/************************************************************************
Module: DIPS.cpp
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/

#include "../../CommonFiles/CmnHdr.h"
#include <tchar.h>
#include "resource.h"
#include "../22_DipsLib/DIPSLib.h"

#pragma  comment(lib,"../../Debug/22_DIPSLib.lib")
//////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam){
    chSETDLGICONS(hWnd, IDI_DIPS);
    return (TRUE);
}

//////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotity){
    switch (id)
    {
    case IDC_SAVE:
    case IDC_RESTORE:
    case IDCANCEL:
        EndDialog(hwnd, id);
        break;
    }
}

//////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    switch (uMsg){
        chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);
        chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);
    }

    return (FALSE);
}

HWND GetDestopListViewHandle(){
    HWND hWnd = NULL,hWndItem;
    HWND hWndParent = FindWindowEx(0,0,TEXT("WorkerW"),NULL);
    while (hWndParent && (!hWnd)){

        hWndItem = FindWindowEx(hWndParent, 0, TEXT("SHELLDLL_DefView"), NULL);
        if (hWndItem){
            hWnd = FindWindowEx(hWndItem, 0, TEXT("SysListView32"), TEXT("FolderView"));
            break;
        }
        hWndParent = FindWindowEx(0, hWndParent, TEXT("WorkerW"), NULL);
    }
    return hWnd;
}

//////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int){
    //将命令行转为大写字符串
    CharUpperBuff(pszCmdLine, 1);
    TCHAR cWhatToDo = pszCmdLine[0];

    if ((cWhatToDo !=TEXT('S')) && (cWhatToDo != TEXT('R'))){
        cWhatToDo = 0; //无效的命令行参数
    }

    //如果是从GUI界面启动本程序,而不是命令行启动
    if (cWhatToDo == 0){
        //无命令行参数时,显示对话框以让用户相应的选择
        switch (DialogBox(hInstExe, MAKEINTRESOURCE(IDD_DIPS), NULL, Dlg_Proc)){
        case IDC_SAVE:
            cWhatToDo = TEXT('S');
            break;
        case IDC_RESTORE:
            cWhatToDo = TEXT('R');
            break;
        }
    }

    //表示用户什么也不做
    if (cWhatToDo == 0){
        return (0);
    }

    //获取桌面listView的窗口句柄,他是
    
    HWND hWndLV = GetDestopListViewHandle();
    chASSERT(IsWindow(hWndLV));

    //安装钩子到Explorer进程的地址空间。当安装完成后,Explorer里会创建一个“隐藏”
    //的窗口,我们可以向这个窗口发送消息,以便告诉他我们要想做的事情
    chASSERT(SetDIPSHook(GetWindowThreadProcessId(hWndLV, NULL)));

    //阻塞等待DIPS服务器窗口创建完毕,当创建完毕后,这里GetMessage会收到通知,线程被唤醒
    MSG msg;
    GetMessage(&msg, NULL, 0, 0);//阻塞等待

    //查找服务端这个被隐藏的窗口
    HWND hWndDIPS = FindWindow(NULL, TEXT("Wintellect DIPS"));

    chASSERT(IsWindow(hWndDIPS)); //断言窗口是存在的

    //告诉服务端的窗口进行保存或恢复桌面图标操作
    BOOL bSave = (cWhatToDo == TEXT('S'));
    SendMessage(hWndDIPS, WM_APP, (WPARAM)hWndLV, bSave);

    //销毁隐形窗口,使用SendMessage可以保证在函数返回后,窗口己这被销毁
    SendMessage(hWndDIPS, WM_CLOSE, 0, 0);

    chASSERT(!IsWindow(hWndDIPS)); //断言窗口己被销毁

    //卸载钩子
    SetDIPSHook(0);
    return (0);
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 22_DIPS.rc 使用
//
#define IDC_RESTORE                     3
#define IDI_DIPS                        101
#define IDD_DIPS                        102
#define IDC_SAVE                        1000

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//rc文件

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_DIPS                ICON                    "DIPS.ico"

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_DIPS DIALOGEX 0, 0, 208, 95
STYLE DS_SETFONT | DS_3DLOOK | DS_CENTER | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU
CAPTION "桌面图标位置保存工具"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
    CTEXT           "DIPS: 桌面图标位置保存工具\n版权所有 (c) 2016 by 浅墨浓香.", IDC_STATIC, 4, 4, 200, 20, SS_SUNKEN, WS_EX_DLGMODALFRAME | WS_EX_STATICEDGE
    LTEXT           "这个工具可以保存或恢复桌面图标的位置.\n\n保存位置: DIPS S\n恢复位置: DIPS R", IDC_STATIC, 4, 32, 200, 36
    DEFPUSHBUTTON   "&保存", IDC_SAVE, 4, 76, 50, 14
    PUSHBUTTON      "&恢复", IDC_RESTORE, 79, 76, 50, 14
    PUSHBUTTON      "取消", IDCANCEL, 154, 76, 50, 14
END

/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_DIALOG1, DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 302
        TOPMARGIN, 7
        BOTTOMMARGIN, 169
    END
END
#endif    // APSTUDIO_INVOKED

#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED