调用指针时c ++ typedef访问冲突

时间:2017-04-19 08:24:25

标签: c++

我有这个代码: (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)”我期望指向类+偏移的指针到方法调用。

1 个答案:

答案 0 :(得分:2)

指向成员的指针不是该类的偏移量。&#34;没有这样的事情。在一些情况下(例如指向具有简单继承层次结构的类中的虚拟成员函数的指针),其实现可以包含这样的偏移量(加上可能还有一些其他数据位)。 / p>

但是,对于非虚函数(如在您的示例中),它可能有一个指向其下方的函数的普通指针。非虚拟功能不存储在任何&#34;表&#34;用&#34;偏移&#34; (至少没有理由以这种方式存储它们),它们很可能被实现为具有错位名称和前置参数的普通沼泽标准函数。

指向成员的指针是C ++中有点棘手的部分,主要是因为没有明显的映射到实现概念,不同的编译器可以用不同的方式处理它们。相信void (Dummy::*)(int)void (Test::*)(int)是二进制兼容的是非常脆弱的。

通常,您不能指望指向Dummy::callMe的指针的二进制表示方式与指向Test成员函数的指针的二进制表示方式类似,因为它可能过分依赖于DummyTest的定义,以及编译器如何实现指向成员的指针。

最重要的是,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接口两侧的编译器和版本将始终相同),您可以使用指向成员函数的指针,前提是:

    1. 双方不会看到类的不同定义。如果其中一个人只有一个非定义的声明,那就没问题了。要符合100%C ++,类的名称应该相同。
    2. 在构建DLL及其客户端时使用/vmg