福仔 发表于 2020-2-29 04:28

C++ x86 x64 类内类外子类化

本帖最后由 福仔 于 2020-2-29 13:08 编辑

# 留帖记录学习过程
要看懂源码需要了解一些汇编知识
不需要知道原理的, 直接按下面的调用例子来使用

```

#pragma once

// 命名空间内的不建议外部使用
// 使用前需要 #include<windows.h>
namespace __subclass
{
        typedef struct tagMAKEPROCDATA
        {
                BYTE data;                // 为了通用, 指令都写到这个数组里, 也不需要1字节对齐
        }MAKEPROCDATA, * LPMAKEPROCDATA;
}
typedef struct tagSUBCLASSSTRUCT : private __subclass::tagMAKEPROCDATA       
{
        // 私有继承
        HWND hWnd;                                // 子类化的窗口句柄
        WNDPROC oldProc;                // 旧窗口过程
        void* param;                        // 用户数据
}SUBCLASSSTRUCT, * LPSUBCLASSSTRUCT;

namespace __subclass
{
        // 由于VirtualAlloc() 申请的字节数是一页, 一般一页是4096个字节
        // 每次申请那么多, 只用几十个字节, 剩下的4000个字节都浪费了
        // 所以做个简单的内存池
        class simpleMempool
        {
        private:
                struct _list
                {
                        struct _list* next;
                };
                _list* ptr;
                void* root;
                bool init()
                {
                        if (ptr)return false;
                        // 8k 个字节足够了, 最多支持 8192/sizeof(SUBCLASSSTRUCT) 次子类化操作, 在不释放的前提下
                        size_t size = 0x2000;        // 4k对齐
#ifdef _M_X64
                        // x64需要自己指定申请的地址, 如果让系统自动分配, 有可能函数地址减去申请的地址会大于4个字节
                        INT_PTR base = 0x100000000;
                        for (INT_PTR i = 0; i < 50; i++)
                        {
                                ptr = (_list*)VirtualAlloc((LPVOID)base, size,
                                        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
                                if (ptr)break;
                                base += 0x100000000;
                        }
#else
                        // x86没那么多讲究, 让系统自己分配地址
                        ptr = (_list*)VirtualAlloc(0, size,
                                MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
#endif
                        if (!ptr)return false;

                        root = ptr;        // 首地址, 首节点
                        LPBYTE p = (LPBYTE)ptr;
                        size_t len = size / sizeof(SUBCLASSSTRUCT) - 1;
                        for (size_t i = 0; i < len; i++)
                        {
                                // 用一个单向链表记录可用内存地址
                                // 每个地址记录的大小为 SUBCLASSSTRUCT 结构大小
                                p += sizeof(SUBCLASSSTRUCT);
                                ptr->next = (_list*)p;
                                ptr = (_list*)p;        // 指向下一个节点
                        }
                        ptr->next = 0;
                        ptr = (_list*)root;
                        return true;
                }
        public:
                simpleMempool() :ptr(0), root(0) { ; }
                ~simpleMempool() { VirtualFree(root, 0, MEM_RELEASE); ptr = 0; root = 0; }
               
                // 申请失败则抛出int类型异常
                // 异常值 1=初始化失败, 2=空间不足,需要释放一些内存
                LPMAKEPROCDATA alloc()
                {
                        size_t size = sizeof(SUBCLASSSTRUCT);
                        if (!ptr)init();
                        if (!ptr)throw int(1);
                        void* p = ptr;                // 每次申请都从当前节点开始取, 释放则还原到首节点
                        if (ptr->next == 0)throw int(2);        // 没有内存了, 抛出异常

                        ptr = ptr->next;        // 当前节点指向下一块内存
                        return (LPMAKEPROCDATA)p;
                }
                bool free(LPMAKEPROCDATA& p)
                {
                        if (!ptr || !p)return false;
                        // 释放就简单的加入链表中
                        memset(p, 0, sizeof(SUBCLASSSTRUCT));
                        ((_list*)p)->next = ptr;        // 放到链表头部
                        ptr = (_list*)p;
                        p = 0;
                        return true;
                }
        };
        static simpleMempool mempool;
        inline LPMAKEPROCDATA subclassAlloc()
        {
                LPMAKEPROCDATA pData = 0;
                try
                {
                        pData = mempool.alloc();
                }
                catch (int e)
                {
                        switch (e)
                        {
                        case 1:
                                MessageBoxW(0, L"调用SubClassWindow() 失败\n失败原因为:VirtualAlloc() 申请内存失败", L"错误", MB_OK);
                                break;
                        case 2:
                                MessageBoxW(0, L"调用SubClassWindow() 失败\n失败原因为:为SubClassWindow() 提供的内存不足", L"错误", MB_OK);
                                break;
                        default:
                                MessageBoxW(0, L"调用SubClassWindow() 失败\n失败原因为:其他未知错误", L"错误", MB_OK);
                                break;
                        }
                        return 0;
                }
                return pData;
        }
}

typedef LRESULT(__stdcall* SUBWNDPROC)(LPSUBCLASSSTRUCT, UINT, WPARAM, LPARAM);


// 返回值强转成 WNDPROC 就可以在注册类时赋值, 窗口过程的第一个参数就是这个返回值
// proc = 子类化回调函数
// param = 用户数据
inline LPSUBCLASSSTRUCT MakeProc(SUBWNDPROC proc, void* param)
{
        __subclass::LPMAKEPROCDATA pData = __subclass::subclassAlloc();
        if (!pData)return 0;
#ifdef _M_X64
        // 这里执行的指令有45个字节, 函数地址 - 指令地址 - 指令字节数
        const INT_PTR pFun = (INT_PTR)proc - INT_PTR(pData) - 45;

        const unsigned char bin[] = {
                0x50,                                                                // push rax        保存原始rax寄存器
                0x48, 0xB8,0,0,0,0,0,0,0,0,                        // mov rax, pData(+3) 把地址放入到eax
                0x50,                                                                // push rax        保存地址
                0x48, 0x8B, 0x40, 0x50,                                // mov rax, 把地址偏移0x50的值存到rax, 指向hWnd
                0x48, 0xA9, 00, 00, 00, 00,                        // test rax, 0       判断rax是否为0
                0x58,                                                                // pop rax        把地址取出来放到rax
                0x75, 0x04,                                                        // jnz 跳转4个字节
                0x48, 0x89, 0x48, 0x50,                                // mov ,rcx 把原来的第一个参数存到地址+0x50的位置
                0x58,                                                                // pop rax 还原原始rax数据
                0x48, 0xb9, 0,0,0,0,0,0,0,0,                // mov rcx, pData(+32)
                0xe9, 0,0,0,0                                                // jmp pFun(+41)
        };
        memcpy(pData->data, bin, sizeof(bin));
        memcpy(pData->data + 3, &pData, 8);
        memcpy(pData->data + 32, &pData, 8);
        memcpy(pData->data + 41, &pFun, 4);

#else
        // 这里执行的指令有39个字节
        const int pFun = (int)proc - int(pData) - 39;
        const unsigned char bin[] = {
                0x50 ,                                                                // push eax
                0xB8, 0,0,0,0,                                                // mov eax, pData(+2)
                0x50,                                                                // push eax
                0x8B, 0x40, 0x50,                                        // mov eax,
                0xA9, 0x00, 0x00, 0x00, 0x00,                // test eax,0
                0x58,                                                                // pop eax
                0x75, 0x08,                                                        // jne 4个字节
                0xFF, 0x74, 0x24, 0x08,                                // push
                0x8F, 0x40, 0x50,                                        // pop
                0x58,                                                                // pop eax
                0xc7, 0x44, 0x24, 0x04,        0,0,0,0,        // mov , pData(+30)
                0xe9, 0,0,0,0                                                // jmp pFun(+35)
        };
        memcpy(pData->data, bin, sizeof(bin));
        memcpy(pData->data + 2, &pData, 4);
        memcpy(pData->data + 30, &pData, 4);
        memcpy(pData->data + 35, &pFun, 4);

#endif

        FlushInstructionCache(GetCurrentProcess(), pData, sizeof(SUBCLASSSTRUCT));
        LPSUBCLASSSTRUCT ptr = (LPSUBCLASSSTRUCT)pData;
        ptr->param = param;
        // 返回的这个回调函数可以用作子类化的回调函数
        // 子类化第一个参数是 SUBCLASSSTRUCT 结构指针
        return ptr;
}

// 返回值强转成 WNDPROC 就可以在注册类时赋值, 窗口过程的第一个参数就是这个返回值
// pThis = 回调函数所在的类指针
// proc = 子类化回调函数, 可以用 类方法, &类名::方法名 来获取
// param = 用户数据
template<typename T>
inline LPSUBCLASSSTRUCT MakeProc(T* pThis, LRESULT(__stdcall T::* fnCallback)(LPSUBCLASSSTRUCT, UINT, WPARAM, LPARAM), void* param)
{
        const INT_PTR proc = (INT_PTR) * ((void**)&fnCallback);
        __subclass::LPMAKEPROCDATA pData = __subclass::subclassAlloc();
        if (!pData || !proc)return 0;
#ifdef _M_X64
        // 这里执行的指令有67个字节
        const INT_PTR pFun = proc - (INT_PTR)pData - 67;
        const unsigned char bin[] = {
                0x50,                                                                // push rax        保存原始rax寄存器
                0x48, 0xB8,0,0,0,0,0,0,0,0,                        // mov rax, pData(+3) 把地址放入到eax
                0x50,                                                                // push rax        保存地址
                0x48, 0x8B, 0x40, 0x50,                                // mov rax, 把地址偏移0x50的值存到rax, 指向hWnd
                0x48, 0xA9, 00, 00, 00, 00,                        // test rax, 0       判断rax是否为0
                0x58,                                                                // pop rax        把地址取出来放到rax
                0x75, 0x04,                                                        // jnz 跳转4个字节
                0x48, 0x89, 0x48, 0x50,                                // mov ,rcx 把原来的第一个参数存到地址+0x50的位置
                0x58,                                                                // pop rax 还原原始rax数据
                0x41, 0x51,                                                        // push r9
                0x8f, 0x44, 0x24, 0x28,                                // pop
                0x4d, 0x8b, 0xc8,                                        // mov r9, r8
                0x4c, 0x8b, 0xc2,                                        // mov r8, rdx
                0x48, 0xba,        0,0,0,0,0,0,0,0,                // mov rdx, pData(+44)
                0x48, 0xb9, 0,0,0,0,0,0,0,0,                // mov rcx, pThis(+54)
                0xe9, 0,0,0,0                                                // jmp pFun(+63)
        };
        memcpy(pData->data, bin, sizeof(bin));
        memcpy(pData->data + 3, &pData, 8);
        memcpy(pData->data + 44, &pData, 8);
        memcpy(pData->data + 54, &pThis, 8);
        memcpy(pData->data + 63, &pFun, 4);

#else
        // 这里执行的指令有50个字节
        const INT_PTR pFun = proc - (INT_PTR)pData - 50;
        char as[] = {
        };
        const unsigned char bin[] = {
                0x50 ,                                                                // push eax
                0xB8, 0,0,0,0,                                                // mov eax, pData(+2)
                0x50,                                                                // push eax
                0x8B, 0x40, 0x50,                                        // mov eax,
                0xA9, 0x00, 0x00, 0x00, 0x00,                // test eax,0
                0x58,                                                                // pop eax
                0x75, 0x08,                                                        // jne 4个字节
                0xFF, 0x74, 0x24, 0x08,                                // push
                0x8F, 0x40, 0x50,                                        // pop
                0x58,                                                                // pop eax
                0xff, 0x34, 0x24,                                        // push esp
                0xc7, 0x44, 0x24, 0x04,        0,0,0,0,        // mov , pThis(+33)
                0xc7, 0x44, 0x24, 0x08,        0,0,0,0,        // mov , pData(+41)
                0xe9, 0,0,0,0                                                // jmp pFun(+46)
        };
        memcpy(pData->data, bin, sizeof(bin));
        memcpy(pData->data + 2, &pData, 4);
        memcpy(pData->data + 32, &pThis, 4);
        memcpy(pData->data + 41, &pData, 4);
        memcpy(pData->data + 46, &pFun, 4);

#endif
        FlushInstructionCache(GetCurrentProcess(), pData, sizeof(SUBCLASSSTRUCT));
        LPSUBCLASSSTRUCT ptr = (LPSUBCLASSSTRUCT)pData;
        ptr->param = param;
        // 返回的这个回调函数可以用作子类化的回调函数
        // 子类化第一个参数是 SUBCLASSSTRUCT 结构指针
        return ptr;
}


// 当不需要使用时必须要调用FreeSubClass()进行释放, 传入的数据为子类化的第一个参数
// 此函数调用 SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (WNDPROC)MakeProc()) 来进行子类化
// 如果只需要获取子类化的函数, 请调用 MakeProc()
// hWnd = 要子类化的窗口句柄
// proc = 子类化回调函数
// param = 用户数据
inline bool __stdcall SubClassWindow(HWND hWnd, SUBWNDPROC proc, void* param)
{
        //if (!IsWindow(hWnd))return false;
        LPSUBCLASSSTRUCT ptr = MakeProc(proc, param);
        if (!ptr)return false;
        ptr->hWnd = hWnd;
        ptr->oldProc = (WNDPROC)SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (LONG_PTR)ptr);
        return true;
}

// 当不需要使用时必须要调用FreeSubClass()进行释放, 传入的数据为子类化的第一个参数
// 此函数调用 SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (WNDPROC)MakeProc()) 来进行子类化
// 如果只需要获取子类化的函数, 请调用 MakeProc()
// hWnd = 要子类化的窗口句柄
// pThis = 回调函数所在的类指针
// proc = 子类化回调函数, 可以用 类方法, &类名::方法名 来获取
// param = 用户数据
template<typename T>
inline bool __stdcall SubClassWindow( HWND hWnd, T* pThis, LRESULT(__stdcall T::* proc)(LPSUBCLASSSTRUCT, UINT, WPARAM, LPARAM), void* param)
{
        if (!IsWindow(hWnd)) return false;
        LPSUBCLASSSTRUCT ptr = MakeProc(pThis, proc, param);
        if (!ptr)return false;
        ptr->hWnd = hWnd;
        ptr->oldProc = (WNDPROC)SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (LONG_PTR)ptr);
        // 返回的这个回调函数可以用作子类化的回调函数
        // 子类化第一个参数是 SUBCLASSSTRUCT 结构指针
        return true;
}


// 释放申请的内存空间, 调用后会把传入参数置0, 参数是子类化里第一个参数的值
// 调用此函数前必须先调用 SetWindowLongW(hWnd, GWLP_WNDPROC, oldProc) 还原子类化
inline bool __stdcall FreeSubClass(__subclass::LPMAKEPROCDATA& proc)
{
        return __subclass::mempool.free(proc);
}
```

# 下面这里是调用例子
例子演示了类内和类外的调用

```
class test
{
public:
      LRESULT WINAPI testProc(LPSUBCLASSSTRUCT pData, UINT message, WPARAM wParam, LPARAM lParam)
      {
                // 跟正常的子类化差不多, 只不过第一个参数不是窗口句柄, 而是一个结构指针
                return CallWindowProcW(pData->oldProc, pData->hWnd, message, wParam, lParam);
      }
      LRESULT WINAPI RegisterClassWndProc(LPSUBCLASSSTRUCT pData, UINT message, WPARAM wParam, LPARAM lParam)
      {
                // 跟正常的子类化差不多, 只不过第一个参数不是窗口句柄, 而是一个结构指针
                return DefWindowProcW(pData->hWnd, message, wParam, lParam);
      }
};

LRESULT CALLBACK testProc(LPSUBCLASSSTRUCT pData, UINT message, WPARAM wParam, LPARAM lParam)
{
      // 跟正常的子类化差不多, 只不过第一个参数不是窗口句柄, 而是一个结构指针
      return CallWindowProcW(pData->oldProc, pData->hWnd, message, wParam, lParam);
}

LRESULT CALLBACK RegisterClassWndProc(LPSUBCLASSSTRUCT pData, UINT message, WPARAM wParam, LPARAM lParam)
{
      // 跟正常的子类化差不多, 只不过第一个参数不是窗口句柄, 而是一个结构指针
      return DefWindowProcW(pData->hWnd, message, wParam, lParam);
}

void test1(HWND hWnd)
{
      
      test a;
      SubClassWindow(hWnd, &a, &test::testProc, 0);
      SubClassWindow(hWnd, testProc, 0);

      WNDCLASSEX wcex;
      wcex.lpfnWndProc = (WNDPROC)MakeProc(RegisterClassWndProc, 0);                              // 可以这样赋值
      wcex.lpfnWndProc = (WNDPROC)MakeProc(&a, &test::RegisterClassWndProc, 0);      // 也可以这样赋值

}
```

福仔 发表于 2020-2-29 12:56

本帖最后由 福仔 于 2020-2-29 12:59 编辑

JuncoJet 发表于 2020-2-29 12:48

子类化是对现有窗口可以直接用的
超类化的话需要重新注册类和创建窗口来接管消息,不到万不得已没人用

```

LRESULT CALLBACK RegisterClassWndProc(LPSUBCLASSSTRUCT pData, UINT message, WPARAM wParam, LPARAM lParam)
{
        // 收到所有消息都转到 pData->oldProc 这个函数去处理
        // pData->oldProc 这个函数是 button 类的默认处理函数
        return CallWindowProcW(pData->oldProc, pData->hWnd, message, wParam, lParam);
}
void test(HINSTANCE hInst)
{
        WNDCLASSEXW wcex = { 0 };
        wcex.cbSize = sizeof(WNDCLASSEXW);
        GetClassInfoExW(hInst, L"button", &wcex);        // 获取"button"类的结构
        wcex.lpszClassName = L"新类名";
        LPSUBCLASSSTRUCT newProc = MakeProc(RegisterClassWndProc, 0);
        newProc->oldProc = wcex.lpfnWndProc;                // 保存"button"类的消息过程函数
        wcex.lpfnWndProc = (WNDPROC)newProc;                // 创建类名为"新类名"时的消息过程函数
        RegisterClassExW(&wcex);                                        // 注册类

        // 调用 CreateWindowExW 后, 这个控件的所有消息都在 RegisterClassWndProc 这个函数里
        // RegisterClassWndProc 这个函数把消息都转到 button类原来的消息处理函数
        CreateWindowExW(0, L"新类名", L"拥有button类的所有特征", 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
```

用帖子里的源码写了个简单的超类化例子

超类化可以收到 WM_NCCREATE, WM_CREATE 消息
子类化的话是收不到这两个消息的, 我感觉这个不算不常用啊
如果需要在控件创建前处理一些事情的话, 子类化实现不了, 超类化可以实现
因为子类化是针对窗口, 需要子类化的时候表示窗口已经创建成功了
超类化是基于类, 窗口还没创建时就已经处理了

福仔 发表于 2020-2-29 12:29

JuncoJet 发表于 2020-2-29 10:57
VB子类化我会= =# 超类化不会

vb我不会, 不过超类化应该都差不多吧,
一般的步骤:
1.先获取你要超类化的类结构 GetClassInfoEx
2.保存获取的结构中原始的fnWndProc, 并设置新的fnWndProc
3.修改这个结构的类名
4.用这个结构注册类名
5.以后创建控件时就用新注册的这个类名去创建
6.在自己的窗口消息处理回调中不要使用DefWindowProc 来返回, 应该用 CallWindowProc(保存的fnWndProc) 来返回

大概的意思就是 新创建的控件有指定控件的属性
比如我创建了一个自己注册类名的控件
我把所有消息都让button类去处理, 那我创建的这个控件就有button类的所有特征
其他同理, 把消息callWindowproc给哪个类就有那个类的所有特征
所以需要GetClassinfoEx来获取指定类的类结构, 并保存他的消息过程函数地址

tlf 发表于 2020-2-29 07:50

imcuer 发表于 2020-2-29 08:52

代码很优美

plattl 发表于 2020-2-29 09:05

代码这个确实不懂。

Ambition_ning 发表于 2020-2-29 10:14

技术贴,感谢分享!

Wapj_Wolf 发表于 2020-2-29 10:30

干货,谢谢分享!

JuncoJet 发表于 2020-2-29 10:57

VB子类化我会= =# 超类化不会

福仔 发表于 2020-2-29 12:35

imcuer 发表于 2020-2-29 08:52
代码很优美

瞎说什么大实话, 害我心里暗暗开心....

JuncoJet 发表于 2020-2-29 12:48

福仔 发表于 2020-2-29 12:29
vb我不会, 不过超类化应该都差不多吧,
一般的步骤:
1.先获取你要超类化的类结构 GetClassInfoEx


子类化是对现有窗口可以直接用的
超类化的话需要重新注册类和创建窗口来接管消息,不到万不得已没人用
页: [1] 2
查看完整版本: C++ x86 x64 类内类外子类化