2017-04-19 92 views
1

我有这样的代码: (EXE)C++的typedef访问冲突,同时呼吁指针

#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

Maybe'LoadLibraryA'返回NULL或'GetProcAddress'返回NULL? – VTT

+0

您是否使用Visual Studio? – Angew

+0

是的,我在Visual Studio上,没有loadlibraryA正确加载所有。通过调试,我可以达到“(对象 - > *引用)(1);”但呼吁导致崩溃。 – Ixiodor

回答

2

甲指针构件是不是一个“偏移到类”。没有这样的事情。在一些案件(诸如指针到虚拟成员函数的一类用一个简单的继承层次结构),它的实现可以由这样的偏移量(加上可能的几个其它数据的比特)的。

然而,对于非虚拟功能(如在你的例子),它可能具有一个普通的指针在它下面的功能。非虚函数不存储在任何“表”与“补偿”(至少有任何理由来存储他们的方式),他们是最有可能是正常的沼泽标准功能实现了错位的名字和一个前置的prameter 。

指针到构件是C++的稍微复杂的部分,这主要是由于这样的事实,没有明显的映射到一个实现的概念和不同的编译器可以以不同的方式处理它们。相信void (Dummy::*)(int)void (Test::*)(int)是二进制兼容的,非常脆弱。

一般情况下,你不能指望一个指针的Dummy::callMe二进制表示以任何方式类似于指针的Test成员函数的,因为它会过于依赖的DummyTest的定义,以及编译器如何实现指向成员的指针。

最糟糕的是,在Visual Studio的编译器的默认处理指向成员的方式是不符合要求的(因此,从大多数角度,打破)。这个默认的处理是为了正确地形成一个指向类成员的指针,编译器需要看到该类的定义。原因是成员指针的最普通的实现是相当大的(我相信4个本地词),因为它必须考虑虚拟继承等。没有虚拟的单个基类最常见的情况可以适合本地词。

因此,如果要可靠地使用完美标准 C++结构,如接受一个指向类,其定义的成员不是在现场看到,你必须使用编译标志/vmg。有了这个,最常用的表示法将始终被使用。

默认行为/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++,类的名称应该是相同的。您可以在构建DLL及其客户端时使用/vmg
+0

我该如何创建一个好的方法指针?我需要注册一个回调。 – Ixiodor

+2

如果您打算使用C风格的界面,则使用C风格类型:指向void类的实例和指向常规函数的指针。 – VTT

+0

我接受你的答案,因为解决了我的POC代码问题,但实际上(我正在使用虚幻引擎并为其制作外部dll)不能使用标志构建。 – Ixiodor