我有这个代码: (EXE)
#include <Windows.h>
#pragma comment(lib, "user32.lib")
class Dummy;
typedef void(Dummy::*Referece)(int i);
typedef void(*InitCall)(void*, Referece);
class Dummy
{
public:
Dummy(){}
void callMe(int val)
{
MessageBoxA(0, "ok", "ok", 0);
}
};
int main()
{
Dummy* obj = new Dummy();
HMODULE ha= LoadLibraryA("aa.dll");
InitCall val = (InitCall)GetProcAddress(ha, "Init");
val(obj, &Dummy::callMe);
}
和我的dll: (.h)中
#pragma once
#define DLL_EXPORT __declspec(dllexport)
class Test;
typedef void (Test::*Reference)(int a);
#ifdef __cplusplus
extern "C"
{
#endif
void DLL_EXPORT Init(Test* Object, Reference reference);
#ifdef __cplusplus
}
#endif
(CPP)
#include "your.h"
void DLL_EXPORT Init(Test * Object, Reference reference)
{
(Object->*reference)(1);
}
我复制了系统,应该是这样的,因为我无法改变一方的代码。 为什么我会违反访问权限?调用“val(obj,ref)”我期望指向类+偏移的指针到方法调用。
答案 0 :(得分:2)
指向成员的指针不是该类的偏移量。&#34;没有这样的事情。在一些情况下(例如指向具有简单继承层次结构的类中的虚拟成员函数的指针),其实现可以包含这样的偏移量(加上可能还有一些其他数据位)。 / p>
但是,对于非虚函数(如在您的示例中),它可能有一个指向其下方的函数的普通指针。非虚拟功能不存储在任何&#34;表&#34;用&#34;偏移&#34; (至少没有理由以这种方式存储它们),它们很可能被实现为具有错位名称和前置参数的普通沼泽标准函数。
指向成员的指针是C ++中有点棘手的部分,主要是因为没有明显的映射到实现概念,不同的编译器可以用不同的方式处理它们。相信void (Dummy::*)(int)
和void (Test::*)(int)
是二进制兼容的是非常脆弱的。
通常,您不能指望指向Dummy::callMe
的指针的二进制表示方式与指向Test
成员函数的指针的二进制表示方式类似,因为它可能过分依赖于Dummy
和Test
的定义,以及编译器如何实现指向成员的指针。
最重要的是,Visual Studio的编译器默认处理指向成员的指针的方式是不符合的(因此,从大多数角度看,已经破坏)。这个默认处理是为了正确地形成指向类成员的指针,编译器需要看到类的定义。原因是指向成员的指针的最一般实现是相当大的(我相信4个本地单词),因为它必须考虑虚拟继承等。没有虚拟的单基类的最常见情况可以适合本地单词。
因此,如果您想要可靠地使用完全标准的 C ++构造,比如接受指向其定义在站点上不可见的类成员的指针,则必须使用编译标志{{3 }}。有了这个,将始终使用最一般的表示。
默认行为/vmb
根据A::*
的定义优化A
的二进制表示(包括大小!)。因此,无法使用此行为创建类似你的typedef。
关于您的选择:
如果你必须通过C风格的接口,强制在调用回调的一侧使用C风格的函数作为回调,并在注册端创建一个包装器C风格的函数。像这样:
class Dummy
{
void callMe(int) {}
};
extern "C" void fw_Dummy_callMe(void *self, int i)
{ static_cast<Dummy*>(self)->callMe(i); }
加
#ifdef __cplusplus
extern "C"
{
#endif
void DLL_EXPORT Init(void* Object, void (*reference)(void*, int));
#ifdef __cplusplus
}
#endif
如果您可以在界面中使用C ++(也就是说,DLL接口两侧的编译器和版本将始终相同),您可以使用指向成员函数的指针,前提是:
/vmg
。