29. 光头的那几年

光头的那几年

很亲切

江边 田野

你的笑容 天边

夕阳无限

醉了 真醉了

你是我的从前

我们在这里再见

手还是

挽着手

只是从前

我欠你一个军礼

吃一个大白兔

是我想念你的守候

是我们想念你们的时候

其实我还在等你

在我们熟悉的街口

其实这不是我写的

翻翻旧照片

你青春依旧

你的 ——老牛

光头的这些年

是我

春风拂起的无眠

Posted in 那年夏天 | Tagged | Leave a comment

学习下 WTL 的 thunk-转载的,写的很好

由于 C++ 成员函数的调用机制问题,对C语言回调函数的 C++ 封装是件比较棘手的事。为了保持C++对象的独立性,理想情况是将回调函数设置到成员函数,而一般的回调函数格式通常是普通的C函数,尤其是 Windows API 中的。好在有些回调函数中留出了一个额外参数,这样便可以由这个通道将 this 指针传入。比如线程函数的定义为:

typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
    LPVOID lpThreadParameter
    );
typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;

这样,当我们实现线程类的时候,就可以:

class Thread
{
private:
    HANDLE m_hThread;

public:
    BOOL Create()
    {
        m_hThread = CreateThread(NULL, 0, StaticThreadProc, (LPVOID)this, 0, NULL);
        return m_hThread != NULL;
    }

private:
    DWORD WINAPI ThreadProc()
    {
        // TODO
        return 0;
    }

private:
    static DWORD WINAPI StaticThreadProc(LPVOID lpThreadParameter)
    {
        ((Thread *)lpThreadParameter)->ThreadProc();
    }
};

不过,这样,成员函数 ThreadProc() 便丧失了一个参数,这通常无伤大雅,任何原本需要从参数传入的信息都可以作为成员变量让 ThreadProc 来读写。如果一定有些什么是非从参数传入不可的,那也可以,一种做法,创建线程的时候传入一个包含 this 指针信息的结构。第二种做法,对该 class 作单例限制——如果现实情况允许的话。

所以,有额外参数的回调函数都好处理。不幸的是,Windows 的窗口回调函数没有这样一个额外参数:

typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

这使得对窗口的 C++ 封装变得困难。为了解决这个问题,一个很自然的想法是,维护一份全局的窗口句柄到窗口类的对应关系,如:

#include <map>

class Window
{
public:
    Window();
    ~Window();
    
public:
    BOOL Create();

protected:
    LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);

protected:
    HWND m_hWnd;

protected:
    static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    static std::map<HWND, Window *> m_sWindows;
};

在 Create 的时候,指定 StaticWndProc 为窗口回调函数,并将 hWnd 与 this 存入 m_sWindows:

BOOL Window::Create()
{
    LPCTSTR lpszClassName = _T(“ClassName”);
    HINSTANCE hInstance = GetModuleHandle(NULL);

    WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };
    wcex.lpfnWndProc   = StaticWndProc;
    wcex.hInstance     = hInstance;
    wcex.lpszClassName = lpszClassName;

    RegisterClassEx(&wcex);

    m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

    if (m_hWnd == NULL)
    {
        return FALSE;
    }

    m_sWindows.insert(std::make_pair(m_hWnd, this));

    ShowWindow(m_hWnd, SW_SHOW);
    UpdateWindow(m_hWnd);

    return TRUE;
}

在 StaticWindowProc 中,由 hWnd 找到 this,然后转发给成员函数:

LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    std::map<HWND, Window *>::iterator it = m_sWindows.find(hWnd);
    assert(it != m_sWindows.end() && it->second != NULL);

    return it->second->WndProc(message, wParam, lParam);
}

(m_sWindows 的多线程保护略过,下同)

据说 MFC 采用的就是类似的做法。缺点是,每次 StaticWndProc 都要从 m_sWindows 中去找 this。由于窗口类一般会保存窗口句柄,回调函数里的 hWnd 就没多大作用了,如果这个 hWnd 能够被用来存 this 指针就好了,那么就能写成这样:

LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    return ((Window *)hWnd)->WndProc(message, wParam, lParam);
}

这样看上去就爽多了。传说中 WTL 所采取的 thunk 技术就是这么干的。之前,只是听过这遥远的传说,今天,终于有机会走进这个传说去看一看。参考资料是一篇不知原始出处的文章《深入剖析WTL—WTL框架窗口分析》,以及部分 WTL 8.0 代码,还有其他乱七八糟的文章。

WTL 的思路是,每次在系统调用 WndProc 的时候,让它鬼使神差地先走到我们的另一处代码,让我们有机会修改堆栈中的 hWnd。这处代码可能是类似这样的:

__asm
{
    mov dword ptr [esp+4], pThis  ;调用 WndProc 时,堆栈结构为:RetAddr, hWnd, message, wParam, lParam, … 故 [esp+4]
    jmp WndProc
}

由于 pThis 和 WndProc 需要被事先修改(但又无法在编译前定好),所以我们需要运行的时候去修改这部分代码。先弄一个小程序探测下这两行语句的机器码:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    return 0;
}

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    MessageBox(NULL, NULL, NULL, MB_OK);

    __asm
    {
        mov dword ptr [esp+4], 1
        jmp WndProc
    }

    return 0;
}

最前面的 MessageBox 是为了等下调试的时候容易找到进入点。

然后使用 OllyDbg,在 MessageBoxW 上设置断点,执行到该函数返回:

image

这里我们看到,mov dword ptr [esp+4] 的机器码为 C7 44 24 04,后面紧接着的一个 DWORD 是 mov 的第二个操作数。jmp 的机器码是 e9,后面紧接着的一个 DWORD 是跳转的相对地址。其中 00061000h – 0006102Bh = FFFFFFD5h。

于是定义这样一个结构:

#pragma pack(push,1)
typedef struct _StdCallThunk
{
    DWORD   m_mov;          // = 0x042444C7
    DWORD   m_this;         // = this
    BYTE    m_jmp;          // = 0xe9
    DWORD   m_relproc;      // = relative distance
} StdCallThunk;
#pragma pack(pop)

这个结构可以作为窗口类的成员变量存在。我们的窗口类现在变成了这样子:

class Window
{
public:
    Window();
    ~Window();

public:
    BOOL Create();

protected:
    LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);

protected:
    HWND         m_hWnd;
    StdCallThunk m_thunk;

protected:
    static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
};

似乎少了点什么……创建窗口的时候,我们是不能直接把回调函数设到 StaticWndPorc 中去的,因为这个函数是希望被写成这样子的:

LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    return ((Window *)hWnd)->WndProc(message, wParam, lParam);
}

那么至少需要一个临时的回调函数,在这个函数里去设置新的回调函数(设到 m_thunk 上),再由 m_thunk 来调用 StaticWndProc,StaticWndProc 再去调用 WndProc,这样整个过程就通了。

但是,临时回调函数还是需要知道从 hWnd 到 this 的对应关系。可是现在我们不能照搬用刚才的 m_sWindows 了。因为窗口在创建过程中就会调用到回调函数,需要使用到 m_sWindows 里的 this,而窗口被成功创建之前,我们没法提前拿到 HWND 存入 m_sWindows。现在,换个方法,存当前线程 ID 与 this 的对应关系。这样,这个类变成了:

#include <map>

class Window
{
public:
    Window();
    ~Window();

public:
    BOOL Create();

protected:
    LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);

protected:
    HWND         m_hWnd;
    StdCallThunk m_thunk;

protected:
    static LRESULT CALLBACK TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

    static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    static std::map<DWORD, Window *> m_sWindows;
};

然后实现 Create 和 TempWndProc:

BOOL Window::Create()
{
    LPCTSTR lpszClassName = _T(“ClassName”);
    HINSTANCE hInstance = GetModuleHandle(NULL);

    WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };
    wcex.lpfnWndProc   = TempWndProc;
    wcex.hInstance     = hInstance;
    wcex.lpszClassName = lpszClassName;

    RegisterClassEx(&wcex);

    DWORD dwThreadId = GetCurrentThreadId();
    m_sWindows.insert(std::make_pair(dwThreadId, this));

    m_thunk.m_mov = 0x042444c7;
    m_thunk.m_jmp = 0xe9;

    m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

    if (m_hWnd == NULL)
    {
        return FALSE;
    }
    
    ShowWindow(m_hWnd, SW_SHOW);
    UpdateWindow(m_hWnd);

    return TRUE;
}

LRESULT CALLBACK Window::TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    std::map<DWORD, Window *>::iterator it = m_sWindows.find(GetCurrentThreadId());
    assert(it != m_sWindows.end() && it->second != NULL);

    Window *pThis = it->second;
    m_sWindows.erase(it);

    WNDPROC pWndProc = (WNDPROC)&pThis->m_thunk;

    pThis->m_thunk.m_this = (DWORD)pThis;
    pThis->m_thunk.m_relproc = (DWORD)&Window::StaticWndProc – ((DWORD)&pThis->m_thunk + sizeof(StdCallThunk));

    m_hWnd = hWnd;
    SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pWndProc);

    return pWndProc(hWnd, message, wParam, lParam);
}

差不多可以了,调试一下。结果,在 thunk 的第一行出错了。我原以为地址算错了神马的,尝试把 thunk.m_mov 改为 0x90909090,再运行,还是出错。于是傻掉了……过了好一会儿才意识到,可能是因为 thunk 在数据段,无法被执行。可是,很久很久以前偶滴一个敬爱的老师在 TC 中鼓捣程序运行时改变自身代码时,貌似无此问题啊。。。然后查呀查,原来是 Windows 在的数据执行保护搞的鬼。于是,需要用 VirtualAlloc 来申请一段有执行权限的内存。WTL 里面也是这么做的,不过它似乎维护了一块较大的可执行内存区作为 thunk 内存池,我们这里从简。最后,整个流程终于跑通了。最终代码清单如下:

#include <Windows.h>
#include <assert.h>
#include <map> 
#include <tchar.h>

#pragma pack(push,1)
typedef struct _StdCallThunk
{
    DWORD   m_mov;
    DWORD   m_this;
    BYTE    m_jmp;
    DWORD   m_relproc;

} StdCallThunk;
#pragma pack(pop)

class Window
{
public:
    Window();
    ~Window();

public:
    BOOL Create();

protected:
    LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);

protected:
    HWND          m_hWnd;
    StdCallThunk *m_pThunk;

protected:
    static LRESULT CALLBACK TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    static std::map<DWORD, Window *> m_sWindows;
};

std::map<DWORD, Window *> Window::m_sWindows;

Window::Window()
{

}

Window::~Window()
{
    VirtualFree(m_pThunk, sizeof(StdCallThunk), MEM_RELEASE);
}

BOOL Window::Create()
{
    LPCTSTR lpszClassName = _T(“ClassName”);
    HINSTANCE hInstance = GetModuleHandle(NULL);

    WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };
    wcex.lpfnWndProc   = TempWndProc;
    wcex.hInstance     = hInstance;
    wcex.lpszClassName = lpszClassName;
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    RegisterClassEx(&wcex);

    DWORD dwThreadId = GetCurrentThreadId();
    m_sWindows.insert(std::make_pair(dwThreadId, this));

    m_pThunk = (StdCallThunk *)VirtualAlloc(NULL, sizeof(StdCallThunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    m_pThunk->m_mov = 0x042444c7;
    m_pThunk->m_jmp = 0xe9;

    m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

    if (m_hWnd == NULL)
    {
        return FALSE;
    }
    
    ShowWindow(m_hWnd, SW_SHOW);
    UpdateWindow(m_hWnd);

    return TRUE;
}

LRESULT Window::WndProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_LBUTTONUP:
        MessageBox(m_hWnd, _T(“LButtonUp”), _T(“Message”), MB_OK | MB_ICONINFORMATION);
        break;
    case WM_RBUTTONUP:
        MessageBox(m_hWnd, _T(“RButtonUp”), _T(“Message”), MB_OK | MB_ICONINFORMATION);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        break;
    }

    return DefWindowProc(m_hWnd, message, wParam, lParam);
}

LRESULT CALLBACK Window::TempWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    std::map<DWORD, Window *>::iterator it = m_sWindows.find(GetCurrentThreadId());
    assert(it != m_sWindows.end() && it->second != NULL);

    Window *pThis = it->second;
    m_sWindows.erase(it);

    WNDPROC pWndProc = (WNDPROC)pThis->m_pThunk;

    pThis->m_pThunk->m_this = (DWORD)pThis;
    pThis->m_pThunk->m_relproc = (DWORD)&Window::StaticWndProc – ((DWORD)pThis->m_pThunk + sizeof(StdCallThunk));

    pThis->m_hWnd = hWnd;
    SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pWndProc);

    return pWndProc(hWnd, message, wParam, lParam);
}

LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    return ((Window *)hWnd)->WndProc(message, wParam, lParam);
}

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    Window wnd;
    wnd.Create();

    MSG msg;

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

刚才有一处,存 this 指针的时候,我很武断地把它与当前线程 ID 关联起来了,其实这正是 WTL 本身的做法。它用 CAtlWinModule::AddCreateWndData 存的 this,最终会把当前线程 ID 和 this 作关联。我是这么理解的吧,同一线程不可能同时有两处在调用 CreateWindow,所以这样取回来的 this 是可靠的。

好了,到此为止,边试验边记录的,不知道理解是否正确。欢迎指出不当之处,也欢迎提出相关的问题来考我,欢迎介绍有关此问题的新方法、新思路,等等,总之,请各位看官多指教哈。

Posted in 技术开发 | Leave a comment

28.关于一个梦

昨天,做了一个梦。

日本,某个大学,我被应邀参加一个会议。
可是,不知道为什么,学校里正在开展一个学生运动,简称学运,
很不幸的是,我也被日本警察带进了所里。
没有拷问,没有虐待。
我悄悄的溜走了,还不忘带走一件警察的衣服。
可是我不会日语。
夜晚。
某个小店的灯亮着,招牌是“定制中山装”,oh,肯定是中国人。
于是我走进店里。
“老板,我需要一个向导”
“你想要做什么?”
“我要回中国”
……
梦醒了,中国梦!
Posted in 那年夏天 | Tagged | 2 Comments

Unit Testing

http://artofunittesting.com/definition-of-a-unit-test/

 

A
good unit test is:

§  Able
to be fully automated

§  Has
full control over all the pieces running (Use mocks or stubs to achieve this
isolation when needed)

§  Can
be run in any order  if
part of many other tests

§  Runs
in memory (no
DB or File access, for example)

§  Consistently returns
the same result (You always run the same test, so no random numbers, for
example. save those for integration or range tests)

§  Runs fast

§  Tests
single
logical concept
 in the system

§  Readable

§  Maintainable

§  Trustworthy (when
you see its result, you don’t need to debug the code just to be sure)

A test is not a unit test if:

1.It talks to the database

2.It communicates across the network

3.It touches the file system

4.It can’t run at the same time as any of your other unit tests

5.You have to do special things to your
environment (such as editing config files) to run it.

 

2. Unit Test Best
practice

http://www.slideshare.net/nickokiss/unit-testing-best-practices?from_search=7

 

3. Testable Code

Based on lots of
practices, we found the testable code is fundamental for unit test, here is a
article from Google to use.

http://misko.hevery.com/attachments/Guide-Writing%20Testable%20Code.pdf

Posted in 技术开发 | Leave a comment

ATL::CRegKey trick

please pay attention on REGSAM.

HKEY m_hKey;

    REGSAM m_samWOW64;  

NND, default is HKEY_READ | HKEY_WRITE, so you understood.
Posted in 技术开发 | Tagged | 1 Comment