随笔-90  评论-947  文章-0  trackbacks-0

由于 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 on 2010-10-24 16:44 溪流 阅读(6515) 评论(40)  编辑 收藏 引用 所属分类: C++Windows

评论:
# re: 学习下 WTL 的 thunk 2010-10-24 17:43 | OwnWaterloo
virtualalloc不是这样用的啊, 同学……
virtualalloc的粒度很大(保留按64k, 提交按页)。
也就是说, 如果直接用virtualalloc, 每个window instance, 要占用64k地址空间, 并使用至少4k内存。


ATL不同版本处理这个问题好像采取的策略不同。
有你提到的使用virtualalloc分配, 然后再划分。
还有直接使用HeapCreate创建一个分配可执行内存的win32 heap。


这些技术很炫, 研究起来也很有成就感。
玩具代码当然可以随便用, 但成熟代码尽可能选取成熟的技术。

比如全局table。它的效率问题真的无法忍受吗? 或者全局带来的其他问题(多线程相关)很要紧吗?

如果窗口类是自己注册的, 可以用cbExtra。
如果不是, 而且GWL_USERDATA是开放的(对话框和MDI是保留给自己用的), 那可以用GWL_USERDATA。

还可以用Set/GetWindowProp。

如果thunk机器相关的缺陷不要紧, 而且上述的这些成熟的方案都不合适, 也不一定非要自己写内存池, HeapCreate在windows上总是可用的。  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 18:00 | 溪流
@OwnWaterloo
啊。。我真的没打算写这方面轮子~~~只是想顺着 WTL 的做法自己跑一遍,以了解仅仅看别人文章所看不出的细节。。。VirtualAlloc确实没用过哈,于是刚才胡乱搞了一通,只求拿到内存,惭愧~  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 18:02 | 溪流
@OwnWaterloo
嗯对了,你觉得WTL本身算成熟技术吗?  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 18:08 | OwnWaterloo
@溪流
没什么好惭愧的呀……
实验、 玩具代码这很正常。 为了突出重点(机器代码的构造)而非内存管理。
造轮子也是很好的练习。

只是, 轮子在投入正规使用前, 一定要谨慎。
玩具代码中忽略的非重点, 恰恰可能占了很多代码, 繁琐、 无趣、 bug丛生……
如果真有恒心把这些问题处理好, 再和现有的轮子客观的比较权衡之后, 再考虑使用。


因为看过许多把玩具代码 —— 而且是一些相当没有技术含量, 仅仅是满足个人审美观而已 —— 的代码放入实际工程中, 还很自鸣得意的……
惨不忍睹啊……
  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 18:13 | OwnWaterloo
@溪流
WTL我不熟, 深入研究的也就是和thunk相关的东西……

给人的感觉是模板使用过度。
比如, 居然用一个模板参数去传递两个窗口风格参数。
感觉是C++的混沌年代, 大家都还不知道如何合理使用模板的情况下, 产生的东西……
所以也不愿意多看……

想研究gui的话, cppblog有个 cexer, 看过很多gui的框架, 可以向他咨询。  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 21:33 | 陈梓瀚(vczh)
@溪流
好快……其实我是看错了,所以把留言删掉了你竟然在这几秒钟内……  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 21:34 | 溪流
@陈梓瀚(vczh)
哈哈,碰巧在刷~  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 21:36 | 陈梓瀚(vczh)
一般来说,如果你自己想用满足各种要求的函数对象,就去用std::function哈。当然这个实现起来比较麻烦,假设支持10个参数,你就要特化0-10个参数,而且返回void和其他的各一份,一共22个类,还得考虑写程序或者用宏搞代码生成……然后如果你想实现bind和curry,那就更多类了……

幸好std已经有function了,哇哈哈  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 21:38 | 溪流
@陈梓瀚(vczh)
function 现在 std 了?我土了。。  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 21:53 | OwnWaterloo
@陈梓瀚(vczh)
这问题用bind解决不了。

bind生成的对象, 需要template配合。
比如: for_each(begin, end, bind( ... ) );
for_each是一个函数模板。

而WndProc必须是一个函数指针。
lpWndProc = bind( ... ); 是不行的。
  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 21:57 | 溪流
@OwnWaterloo
刚才逛了下cexer的博客,开场白太好太强大了,尽看开场白去了哈  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 22:02 | 溪流
@OwnWaterloo
我以为你说 WTL 模板使用过度神马的可能会引人打架,怎么这么久都没有人来打酱油呢~ ^_^  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-24 22:10 | OwnWaterloo
@溪流
因为用的人少啊  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 09:26 | waiting4you
打酱油的来了~~希望没打搅几位,呵呵.
我只是有一点小疑问:这种方法在64位系统下可以工作吗?StdCallThunk里放在代码块是否x64系统兼容?
WTL应该可以编译为64位程序,不知道WTL的源码中有没有针对x64的编译开关  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 09:33 | OwnWaterloo
@waiting4you
有。
Windows支持的体系结构里, ATL的stdthunk都有实现。
编译switch也不用自己操心, 都写好了的。
  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 10:13 | waiting4you
@OwnWaterloo
谢谢,看来这种实现的可移植性不太好,了解一下它的实现可以开阔视野.只是自己应用这种技术来编码就得好好考虑一下了.像ATL/WTL这样有强大的微软或开源社区(偶不确定这个thunk部分应该归哪个"部门"管,开源的WTL基于商业的ATL~!@#$)支持的代码来说,这不是问题.但是我们自己写就...  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 10:29 | 空明流转
WTL的高手还没发话没得打架的。
我没觉得WTL模板用的多。。。  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 10:42 | 空明流转
顺便说一下,那个什么所谓的Thunk,也就是一个JIT的雏形。。。  回复  更多评论
  
# re: 学习下 WTL 的 thunk[未登录] 2010-10-25 12:34 | dd
请问下:(DWORD)&Window::StaticWndProc - ((DWORD)pThis->m_pThunk + sizeof(StdCallThunk));这里面的(DWORD)m_pThunk + sizeof(StdCallThunk)是正确的吗?sizeof(StdCallThunk)得到的是这个结构体的字节数 13 byte,然后把字节数的单位变成DWORD加上,即 sizeof(DWORD)*13...  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 13:03 | 溪流
@dd
(DWORD)(pThis->m_pThunk),一个很原始很普通的数字,再加上 sizeof(StdCallThunk),也就是加上13。没有sizeof(DWORD)*13。嗯。  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 22:36 | 路过
boost::function即可  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 23:01 | 路过
@OwnWaterloo

class Thread
{
LONG ThreadProc(LPVOID lParam)
{
boost::function<void> *pFunc = (boost::function<void> *) lParam;
(*pFunc)();
delete pFunc;
}

template<typename Callable>
void Run(Callable callable)
{
boost::function<void> *pFunc = new boost::function<void>;
*pFunc = callable;
::CreateThread(NULL, 0, StaticThreadProc, (LPVOID) pFunc, 0, NULL);
}
}

int _tmain(int argc, _TCHAR* argv[])
{
Task task;
Thread thread;

thread.Run(boost::bind(&Task::DoSomething, &task, 1, 2, ... n));
}
}  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 23:09 | OwnWaterloo
@路过
喔唷! 好华丽, 好耀眼哦!
class, thread, callable, run什么的, 我看不懂也!

我就问一句, 还望赐教赐教啊~

如果CreateThread使用的线程函数的签名是这个样子:
unsigned threadproc(void); // 注意, 是void, 而不是void*

你用上面那些华丽的东西给我看看?
全局变量什么的也丑的, 也不许用哦!


WndProc就是这样一个场景 —— 回调的签名没有void* context。
您, 明白了吗?  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 23:24 | 路过
@OwnWaterloo

>> 如果CreateThread使用的线程函数的签名是这个样子:
那是如果,这样的线程api可用吗?windows,linux都没有

>> WndProc就是这样一个场景 —— 回调的签名没有void* context。
用GWLP_USERDATA不行?

如果设计者真的设计了这种用户无法扩展的api,那您是对的
  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-25 23:27 | 路过
@OwnWaterloo

多嘴说句,没有如果;

如果真有“如果“,我也不会回复你  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-26 00:10 | OwnWaterloo
@路过
作者讨论的是“如何开启一个线程”吗?
不, 作者讨论的是如何给WndProc塞入一个context。

哪谁在讨论ThreadProc?
是你, 将讨论从WndProc转移到ThreadProc。

我怕你不懂WndProc和ThreadProc究竟有什么重大区别,
以及, 也许你不熟悉WndProc,
所以想就近从你熟悉的ThreadProc中给你一点启发 —— 就是移除ThreadProc的context参数。
设想一下ThreadProc如果没有void*参数, 编程会是个什么样子, api有多难扩展。
以及, 问题的关键究竟是什么?

你是不明白啊, 还是不明白啊, 还是不明白?


多说一句, GWL_USERDATA我在上面已经提到过, 是谁回帖不看贴的?
再多提醒一句, GWL_USERDATA并不是你想象的那么简单。
究竟是什么, 请继续看我在楼上的回复。
  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-26 19:09 | 路过
@OwnWaterloo

>> 哪谁在讨论ThreadProc?
请看楼主发的第一个例程,

我用threadproc来举例一种灵活的方法而已

照这种方法法不能推导出wndproc的用法?需要在这上面挑刺?
你要挑刺我那例程语法也有问题,你可以继续

>>设想一下ThreadProc如果没有void*参数, 编程会是个什么样子, api有多难扩展。

还是“如果”,有意义?

>>多说一句, GWL_USERDATA我在上面已经提到过, 是谁回帖不看贴的?
我知道你提过,既然你知道GWL_USERDATA,那我提的方法就是可行的;但你没发现,所以告诉你



最后,请你搞清楚一点,

我之所以回复你,是有人说可以用bind,你说不行;我告诉怎么做可以行,明白不?都不知道你喷什么
  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-26 19:15 | OwnWaterloo
@路过
lz的标题是 WTL thunk。
thunk你懂吗? thunk解决的是华丽的语法, 还是缺少的context参数?
  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-26 19:20 | 溪流
@路过
@OwnWaterloo

啊。。。熄火熄火~~~最开始提出bind可能原意并非完全针对我在顶楼说的情形,路过同学的例子确实也指出了一个用bind拐个弯设置到原始C函数指针上去的方法,不过用不用bind都没法解决WndProc少一个参数的硬伤……lz是来打酱油的,请无视~  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-26 19:27 | OwnWaterloo
>>照这种方法法不能推导出wndproc的用法?
>>还是“如果”,有意义?
 
你有点起码的逻辑分析能力吗?
说threadproc是帮助你理解这两者的不同, 既然不同, 那就是不可导出的。
既然好心被当作驴肝肺, 那别什么"如果"了, 你自己动手导出wndproc试试。
 
然后, 摸着自己的良心说: wndproc缺少的参数是靠什么传入的
是bind吗? 是function吗? 是华丽的语法吗?
 
还是我在前面提到的那些东西: table, cbExtra, GWL_USERDATA, Set/GetProp, thunk?
 
  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-26 19:30 | OwnWaterloo
@路过
最烦这种瞅住机会就迫不及待的秀一下自己似是而非的理解, 离题万里
浮躁
  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2010-10-26 19:35 | OwnWaterloo

@溪流
>> 路过同学的例子确实也指出了一个用bind拐个弯设置到原始C函数指针上去的方法

bind生成的可调用体 —— 一个instance —— 依然需要通过某种方式传递到callback中
而现在的问题就是如何将这个context传递进去
 
所以我一直说, bind根本就不是解决这个问题的方法, 它本身需要这个问题解决之后才能使用
  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2011-03-17 23:11 | 溪流
@OwnWaterloo
又看了一些人对回调函数的理解,觉得你在这个问题上如此激烈的反应是必要的,太多人认为function之类的玩意儿就能解决回调函数少参数的问题了,甚至没意识到这个问题是什么问题。。。  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2011-03-17 23:24 | OwnWaterloo
@溪流
嘿, 多谢理解~  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2011-03-17 23:51 | 溪流
@OwnWaterloo
http://topic.csdn.net/u/20110221/16/86608158-ac06-4a45-b3bb-7f53ba3008fc.html,好多自以为是的人呐,还有人说“去看chrome源码吧”  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2013-04-27 17:37 | 溪流
@OwnWaterloo
最近有点想给TimerProc成员化的欲望~  回复  更多评论
  
# re: 学习下 WTL 的 thunk[未登录] 2015-08-26 11:05 | Mark
是我看过分析Thunk技术最清晰的文章  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2015-10-25 00:46 | WXX
这个做法还是有个全局的static std::map<DWORD, Window *> m_sWindows
感觉没有比mfc那种查找的做法好多少啊  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2015-10-26 00:27 | 溪流
@WXX
请看完全文吧  回复  更多评论
  
# re: 学习下 WTL 的 thunk 2023-05-15 16:39 | 溪流
@Mark
谢谢~!  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理