枫叶棋语 发表于 3 天前

C++ 将类成员函数转为回调函数

本帖最后由 枫叶棋语 于 2026-1-23 23:03 编辑

苦于C++ 不像C#那么方便使用回调函数,迫于无奈是用thunk 技术实现将类成员函数转变为回调函数类型。
若是大家有更好的方法,也分享一下。

#pragma once
#include <windows.h>
#include <memory>
#include <cassert>

class MapleThunk {
public:
// 构造函数:初始化跳板代码
MapleThunk() {
    static constexpr BYTE kThunkCode[] = {
      0x48, 0x83, 0xEC, 0x28,             // sub rsp, 28h
      0x4C, 0x89, 0x4C, 0x24, 0x20,       // mov , r9
      0x4D, 0x8B, 0xC8,                   // mov r9, r8
      0x4C, 0x8B, 0xC2,                   // mov r8, rdx
      0x48, 0x8B, 0xD1,                   // mov rdx, rcx
      0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // mov rcx,
      0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // mov rax,
      0xFF, 0xD0,                         // call rax
      0x48, 0x83, 0xC4, 0x28,             // add rsp, 28h
      0xC3                              // ret
    };

    m_codeSize = sizeof(kThunkCode);
    AllocateExecutableMemory();

    // 复制原始指令模板
    memcpy(m_pThunkCode, kThunkCode, m_codeSize);

    // 定位关键偏移量
    LocateKeyOffsets();
}

~MapleThunk() {
    if (m_pThunkCode) {
      VirtualFree(m_pThunkCode, 0, MEM_RELEASE);
    }
}

template<typename T, typename Method>
void* SetThunkTarget(T* instance, Method method) {
    static_assert(std::is_member_function_pointer_v<Method>,
      "Method must be a member function pointer");

    *m_pThisPtr = reinterpret_cast<ULONG_PTR>(instance);

    // 使用 union 转换成员函数指针
    union {
      Method func;
      ULONG_PTR addr;
    } converter;
    converter.func = method;
    *m_pTargetPtr = converter.addr;

    FlushInstructionCache(GetCurrentProcess(), m_pThunkCode, m_codeSize);
    return m_pThunkCode;
}

// 禁用复制/移动
MapleThunk(const MapleThunk&) = delete;
MapleThunk& operator=(const MapleThunk&) = delete;

private:
BYTE* m_pThunkCode = nullptr;
size_t m_codeSize = 0;
ULONG_PTR* m_pThisPtr = nullptr;   // this指针位置
ULONG_PTR* m_pTargetPtr = nullptr; // 目标函数位置

void AllocateExecutableMemory() {
    m_pThunkCode = static_cast<BYTE*>(
      VirtualAlloc(nullptr, m_codeSize,
      MEM_COMMIT | MEM_RESERVE,
      PAGE_EXECUTE_READWRITE));

    if (!m_pThunkCode) {
      throw std::bad_alloc();
    }
}

void LocateKeyOffsets() {
    // 搜索特征码定位关键位置
    for (size_t i = 0; i < m_codeSize - 9; ++i) {
      if (*reinterpret_cast<WORD*>(m_pThunkCode + i) == 0xB948) { // mov rcx,
      m_pThisPtr = reinterpret_cast<ULONG_PTR*>(m_pThunkCode + i + 2);
      }
      else if (*reinterpret_cast<WORD*>(m_pThunkCode + i) == 0xB848) { // mov rax,
      m_pTargetPtr = reinterpret_cast<ULONG_PTR*>(m_pThunkCode + i + 2);
      }
    }

    assert(m_pThisPtr && m_pTargetPtr && "Failed to locate key offsets");
}
};

使用方法:比如将python的函数注册到CAD 命令
//py函数包装器
class PyCallWrapper {
private:
MapleThunk m_thunk; //将成员函数转变为命令回调函数
py::function m_pyfun;
public:
PyCallWrapper(py::function pyfunc) :m_pyfun(pyfunc) {}
//回调钩子,执行python函数
void invoke() {
    m_pyfun();
}
//转变为void(*)() 类型,才能进行命令注册
AcRxFunctionPtr GetCallback() {
    return reinterpret_cast<AcRxFunctionPtr>(m_thunk.SetThunkTarget(this, &PyCallWrapper::invoke));
}
};

static const wchar_t* GroupName = L"PyArxCmd";

void addCmd(py::function pyfun, int flags) {
auto wrapper = new PyCallWrapper(pyfun);//这里不能使用智能指针,也不可以用栈变量,只能使用new ,没有为什么
auto callback = wrapper->GetCallback();
std::wstring name = py::str(pyfun.attr("__name__")).cast<std::wstring>();
const wchar_t* fn = name.c_str();
auto es = acedRegCmds->addCommand(GroupName, fn, fn, flags, callback);
}
void removeCmd(const std::wstring& funname) {
acedRegCmds->removeCmd(GroupName, funname.c_str());
}
void clearCmds() {
acedRegCmds->removeGroup(GroupName);
}

//绑定输出到python 模块
void bind_PyCommandManager(py::module_* m) {
m->def("addCmd", &addCmd, py::arg("func"),
    py::arg("flags") = ACRX_CMD_TRANSPARENT,
    "Register Python function as ARX command")
    .def("removeCmd", &removeCmd, py::arg("name"), "Unregister ARX command")
    .def("clear", &clearCmds, "Remove all registered commands");
}



你有种再说一遍 发表于 3 天前

牛哇,居然还要区分可执行内存和资源内存.

枫叶棋语 发表于 3 天前

你有种再说一遍 发表于 2026-1-23 23:26
牛哇,居然还要区分可执行内存和资源内存.

这边卡了我好多天,确实上难度了

怡情 发表于 前天 10:39

看不懂,给大佬顶一下
页: [1]
查看完整版本: C++ 将类成员函数转为回调函数