留帖记录学习过程
要看懂源码需要了解一些汇编知识
不需要知道原理的, 直接按下面的调用例子来使用
subclass.h
#pragma once
// 命名空间内的不建议外部使用
// 使用前需要 #include<windows.h>
namespace __subclass
{
typedef struct tagMAKEPROCDATA
{
BYTE data[80]; // 为了通用, 指令都写到这个数组里, 也不需要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