使用PIMPL成语删除DLL中的std :: vector时出错 [英] Error deleting std::vector in a DLL using the PIMPL idiom

查看:146
本文介绍了使用PIMPL成语删除DLL中的std :: vector时出错的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下代码:



在DLL1中:



在.h文件中:

  class MyClass 
{
public:
MyClass
private:
std :: string m_name;
};

class __declspec(dllexport)Foo
{
private:
struct Impl;
Impl * pimpl;
public:
Foo();
virtual〜Foo();
};

struct Foo :: Impl
{
std :: vector< MyClass> m_vec;
std :: vector< MyClass> & GetVector(){return m_vec; };
};

在.cpp文件中:

  Foo :: Foo():pimpl(new Impl)
{
}

Foo ::〜Foo
delete pimpl;
pimpl = NULL;
}



在DLL2中



in .h

  class Bar:public Foo 
{
public:
Bar();
virtual〜Bar();
};

在.cpp中:

  Bar :: Bar()
{
}

Bar ::〜Bar()
{
}

在DLL3中:

  externC__declspec(dllexport)Foo * MyFunc(Foo * param)
{
if(!param)
param = new Bar
return param;
}

在主应用程序中:

  void Abc :: my_func()
{
Foo * var = NULL;
//加载DLL3并获取函数的地址MyFunc
var = func(var);
delete var;
}

现在,我假设复制构造函数应该是私有的,感觉复制Foo和Bar对象。



现在的问题是:应该Bar也有复制构造函数和赋值运算符?
[/ EDIT]



请注意,MyClass未导出,并且没有析构函数。



这是一般你如何编写代码?



问题是我在Windows上崩溃(8.1 + MSVC 2010,如果重要)。
如果需要,我可以发布更多的代码,但现在只是想确保我不做明显的错误。



崩溃发生后,我步骤基本析构函数和堆栈跟踪说:


ntdll.dll!770873a6()[下面的框架可能不正确和/ b $ b丢失,没有为ntdll.dll加载符号] ntdll.dll!7704164f()

ntdll.dll!77010f01()KernelBase.dll!754a2844()

dll1。 dll!_CrtIsValidHeapPointer(const void * pUserData)Line
2036 C ++ dll1.dll!_free_dbg_nolock(void * pUserData,int nBlockUse)
行1322 + 0x9字节C ++ dll1.dll!_free_dbg(void * pUserData,int
nBlockUse)行1265 + 0xd字节C ++ dll1.dll!操作符delete(void
* pUserData)行54 + 0x10字节C ++ dll1.dll!Foo ::`vector deleting destructor' C ++


谢谢。



更新:



即使我把下面的代码放在

  externC__declspec(dllexport)Foo * MyFunc(Foo * param)
{
param = new Bar();
delete param;
return param;
}

程序在同一位置的删除参数操作中仍然崩溃。 / p>

看起来像在调用Foo的析构函数之后调用std :: vector的析构函数。



在调试器中仔细运行后,我发现崩溃发生在void operator delete(void * pUserData);内部。 pUserData指针的地址为param。



DLL1是用以下代码构建的:


C ++



/ ZI / nologo / W4 / WX- / Od / Oy- / DWIN32/ D_DEBUG/ D_LIB / D
_UNICODE/ DUNICODE/ Gm / EHsc / RTC1 / MTd / GS / fp:precise
/ Zc:wchar_t / Zc:forScope /Fp\"Debug\dll1.pch / FaDebug \/ FoDebug \
/Fd\"Debug\vc100.pdb/ Gd / analyze- / errorReport:queue



图书馆员
/OUT:\"C:\Users\Igor\OneDrive\Documents\dbhandler1\docview\Debug\dll1.lib
/ NOLOGO


DLL2由以下内容构建:

  C ++ 

/I\"..\dll1\/ Zi / nologo / W4 / WX- / Od / Oy- / DWIN32/ D_USRDLL/ DDLL_EXPORTS/ D _DEBUG/ D_CRT_SECURE_NO_DEPRECATE = 1/ D_CRT_NON_CONFORMING_SWPRINTFS = 1/ D_SCL_SECURE_NO_WARNINGS = 1/ D_UNICODE/ DMY_DLL_BUILDING/ D_WINDLL/ DUNICODE/ Gm- / EHsc / RTC1 / MTd / GS / fp:precise / Zc:wchar_t / Zc:forScope / GR /Fp\"vc_mswud\dll2\dll2.pch/ Favc_mswud\dll2\/ Fovc_mswud\dll2\\ \\/Fd\"vc_mswud\dll2.pdb/ Gd / analyze- / errorReport:queue

链接器

/OUT:\"..\myapp\vc_mswud \dll2.dll/ INCREMENTAL / NOLOGO /LIBPATH:\"..\docview\Debug\/ DLLdll1.libkernel32.libuser32.libgdi32.libcomdlg32。 libwinspool.libwinmm.libshell32.libshlwapi.libcomctl32.libole32.liboleaut32.libuuid.librpcrt4.libadvapi32。 libversion.libwsock32.libwininet.lib/ MANIFEST /ManifestFile:\"vc_mswud\dll2\dll2.dll.intermediate.manifest/ ALLOWISOLATION / MANIFESTUAC:level ='asInvoker'uiAccess = 'false'/ DEBUG /PDB:\"vc_mswud\dll2.pdb/PGD:\"C:\Users\Igor\OneDrive\Documents\myapp\dll2\vc_mswud\dll2.pgd / TLBID:1 / DYNAMICBASE / NXCOMPAT /IMPLIB:\"vc_mswud\dll2.lib/ MACHINE:X86 / ERRORREPORT:QUEUE


b $ b

有没有人看到我的库的构建方式有任何问题?

解决方案

问题是,在DLL3中分配了 Bar ,其中包含 Foo 的包含的实例。然而,你在主应用程序中通过 Foo * 删除它,它已经在DLL1中删除(如你的堆栈跟踪中所见)。



调试堆检查程序捕获了在一个模块中分配内存并在另一个模块中释放内存。






问题的详细说明



调用 new Foo(args ...)大致如下:

  pFoo = reinterpret_cast< Foo *>(:: operator new(sizeof )); 
pFoo-> Foo(args ...);
return pFoo;

在MS Visual Studio C ++对象模型中,调用 new Foo ,所以发生在你调用 new 语句。



code> delete pFoo 大致如下:

  pFoo->〜Foo ); 
:: operator delete(pFoo);

在MS Visual Studio C ++对象模型中,这两个操作都编译为 Foo ::`vector deletion destructor'()中,可以在 ://blogs.msdn.microsoft.com/oldnewthing/20040203-00/?p = 40763rel =nofollow>不匹配标量和向量新建和删除



除非你改变这种行为, :: operator new 将在 site new Foo 〜Foo 的结尾括号的站点上调用 :: operator delete / code>



我在这里没有详细的虚拟或矢量行为,但他们没有任何进一步的惊喜超出上述。



特定于类的重载 operator new :: operator new 来替换 :: operator delete 在上面,如果它们存在,这让你控制 :: operator new code> :: operator delete 被调用,甚至完全调用其他东西(eg一个池分配器)。这是您明确解决此问题的方式。



我从 MS Support Article 122675 MSVC ++ 5及更高版本应该不包括<$ c $的析构函数中的 :: operator delete 调用c> dllexport / dllimport 类使用虚拟析构函数,但我从来没有设法触发该行为,并发现它更可靠






要解决这个问题,请给予 Foo 特定于类的重载 operator new operator delete ,例如

  class __declspec(dllexport)Foo 
{
private:
struct Impl;
Impl * pimpl;
public:
static void * operator new(std :: size_t sz);
static void operator delete(void * ptr,std :: size_t sz)
Foo();
virtual〜Foo();
};

不要将实现放在标题中,否则会被内联,

  void * Foo :: operator new(std :: size_t sz)
{
return :: operator new(sz);
}

void Foo :: operator delete(void * ptr,std :: size_t sz)
{
return :: operator delete(ptr);
}

这只适用于 Foo 将导致在DLL1的上下文中分配和销毁 Foo Bar



如果你想在DLL2的上下文中分配和删除 Bar ,那么你也可以给它一个。虚拟析构函数将确保正确的操作符delete 将被调用,即使你 delete 例。你可能需要dllexport Bar ,因为内联者有时会在这里惊讶。



请参阅 MS支持文章122675 一些更多的细节,虽然你实际上反弹了相反的问题比






另一种选择:make Foo :: Foo protected和 Bar :: Bar private,并从DLL接口为它们公开静态工厂函数。然后 :: operator new 调用是在工厂函数中,而不是调用者的代码,这将把它放在与 :: operator删除,并获得与提供类特定 operator new operator delete ,以及工厂函数的所有其他优点和缺点(一旦你停止传递裸指针并开始使用 unique_ptr 或<$ c $



要执行此操作,您必须信任 Bar / code>不调用 new Foo ,或者您已将问题带回。所以这个是更加保护的约定,而类特定 operator new / operator delete 表示要求内存分配这种类型是以某种方式完成的。


I have the following code:

In DLL1:

in .h file:

class MyClass
{
public:
    MyClass();
private:
    std::string m_name;
};

class __declspec(dllexport) Foo
{
private:
    struct Impl;
    Impl *pimpl;
public:
    Foo();
    virtual ~Foo();
};

struct Foo::Impl
{
    std::vector<MyClass> m_vec;
    std::vector<MyClass> &GetVector() { return m_vec; };
};

in .cpp file:

Foo::Foo() : pimpl ( new Impl )
{
}

Foo::~Foo()
{
    delete pimpl;
    pimpl = NULL;
}

[EDIT]

In DLL2

in .h

class Bar : public Foo
{
public:
    Bar();
    virtual ~Bar();
};

in .cpp:

Bar::Bar()
{
}

Bar::~Bar()
{
}

In DLL3:

extern "C" __declspec(dllexport) Foo *MyFunc(Foo *param)
{
    if( !param )
        param = new Bar();
    return param;
}

In main application:

void Abc::my_func()
{
    Foo *var = NULL;
// loading the DLL3 and getting the address of the function MyFunc
    var = func( var );
    delete var;
}

Now, I presume that the copy constructor should be private as it does not make sense to copy both Foo and Bar objects.

Now the question I have is: should Bar also have copy constructor and assignment operator? [/EDIT]

Notice that MyClass is not exported and does not have a destructor.

Is this generally how you write the code?

The problem is that I have a crash on Windows (8.1 + MSVC 2010, if it matters). I can post more code if needed, but for now just want to make sure I don't do something obviously wrong.

The crash happens after I step out of the Base destructor and the stack trace says:

ntdll.dll!770873a6() [Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll] ntdll.dll!7704164f()
ntdll.dll!77010f01() KernelBase.dll!754a2844()
dll1.dll!_CrtIsValidHeapPointer(const void * pUserData) Line 2036 C++ dll1.dll!_free_dbg_nolock(void * pUserData, int nBlockUse) Line 1322 + 0x9 bytes C++ dll1.dll!_free_dbg(void * pUserData, int nBlockUse) Line 1265 + 0xd bytes C++ dll1.dll!operator delete(void * pUserData) Line 54 + 0x10 bytes C++ dll1.dll!Foo::`vector deleting destructor'() + 0x65 bytes C++

Thank you.

UPDATE:

Even if I put following code in the

extern "C" __declspec(dllexport) Foo *MyFunc(Foo *param)
{
    param = new Bar();
    delete param;
    return param;
}

The program is still crashing in the delete param operation in the same place.

It looks like the destructor of the std::vector is called later, after the destructor of Foo is called. Is it how it suppose to be?

UPDATE2:

After carefully running this under debugger, I see that the crash happens inside "void operator delete(void *pUserData);". The pUserData pointer has an address of "param".

The DLL1 is built with this:

C++

/ZI /nologo /W4 /WX- /Od /Oy- /D "WIN32" /D "_DEBUG" /D "_LIB" /D "_UNICODE" /D "UNICODE" /Gm /EHsc /RTC1 /MTd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Fp"Debug\dll1.pch" /Fa"Debug\" /Fo"Debug\" /Fd"Debug\vc100.pdb" /Gd /analyze- /errorReport:queue

Librarian /OUT:"C:\Users\Igor\OneDrive\Documents\dbhandler1\docview\Debug\dll1.lib" /NOLOGO

The DLL2 was built with:

C++

/I"..\dll1\" /Zi /nologo /W4 /WX- /Od /Oy- /D "WIN32" /D "_USRDLL" /D "DLL_EXPORTS" /D "_DEBUG" /D "_CRT_SECURE_NO_DEPRECATE=1" /D "_CRT_NON_CONFORMING_SWPRINTFS=1" /D "_SCL_SECURE_NO_WARNINGS=1" /D "_UNICODE" /D "MY_DLL_BUILDING" /D "_WINDLL" /D "UNICODE" /Gm- /EHsc /RTC1 /MTd /GS /fp:precise /Zc:wchar_t /Zc:forScope /GR /Fp"vc_mswud\dll2\dll2.pch" /Fa"vc_mswud\dll2\" /Fo"vc_mswud\dll2\" /Fd"vc_mswud\dll2.pdb" /Gd /analyze- /errorReport:queue 

Linker

/OUT:"..\myapp\vc_mswud\dll2.dll" /INCREMENTAL /NOLOGO /LIBPATH:"..\docview\Debug\" /DLL "dll1.lib" "kernel32.lib" "user32.lib" "gdi32.lib" "comdlg32.lib" "winspool.lib" "winmm.lib" "shell32.lib" "shlwapi.lib" "comctl32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "rpcrt4.lib" "advapi32.lib" "version.lib" "wsock32.lib" "wininet.lib" /MANIFEST /ManifestFile:"vc_mswud\dll2\dll2.dll.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"vc_mswud\dll2.pdb" /PGD:"C:\Users\Igor\OneDrive\Documents\myapp\dll2\vc_mswud\dll2.pgd" /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"vc_mswud\dll2.lib" /MACHINE:X86 /ERRORREPORT:QUEUE 

Does anybody see any issues with the way my libraries are built?

解决方案

The problem is that you've allocated Bar in DLL3, which includes the contained instance of Foo. However, you deleted it in the main application via the Foo*, which has done the deletion in DLL1 (as seen in your stack trace).

The debug heap checker has caught you allocating memory in one module and freeing it in another module.


Detailed explanation of the issue:

Calling new Foo(args...) does roughly the following:

pFoo = reinterpret_cast<Foo*>(::operator new(sizeof(Foo)));
pFoo->Foo(args...);
return pFoo;

In the MS Visual Studio C++ object model, this is inlined at the call of new Foo, so happens where you call the new statement.

Calling delete pFoo does roughly the following:

pFoo->~Foo();
::operator delete(pFoo);

In the MS Visual Studio C++ object model, both of these operations are compiled into ~Foo, in the Foo::`vector deleting destructor'(), which you can see in psuedocode at Mismatching scalar and vector new and delete.

So unless you change this behaviour, ::operator new will be called at the site of new Foo, and ::operator delete will be called at the site of the closing brace of ~Foo.

I haven't detailed virtual or vector behaviours here, but they don't carry any further surprises beyond the above.

Class-specific overloads of operator new and operator delete are used instead of ::operator new and ::operator delete in the above, if they exist, which lets you control where ::operator new and ::operator delete are called, or even to call something else entirely (e.g. a pool allocator). That's how you explicitly solve this issue.

I understood from MS Support Article 122675 that MSVC++ 5 and later is supposed to not include the ::operator delete call in the destructor of dllexport/dllimport classes with a virtual destructor, but I never managed to trigger that behaviour, and have found it much more reliable to be explicit about where my memory is allocated/deallocated for DLL-exported classes.


To fix this, give Foo class-specific overloads of operator new and operator delete, e.g.,

class __declspec(dllexport) Foo
{
private:
    struct Impl;
    Impl *pimpl;
public:
    static void* operator new(std::size_t sz);
    static void operator delete(void* ptr, std::size_t sz)
    Foo();
    virtual ~Foo();
};

Don't put the implementations in the header, or it'll be inlined, which defeats the point of the exercise.

void* Foo::operator new(std::size_t sz)
{
    return ::operator new(sz);
}

void Foo::operator delete(void* ptr, std::size_t sz)
{
    return ::operator delete(ptr);
}

Doing this only for Foo will cause both Foo and Bar to be allocated and destroyed in the context of DLL1.

If you'd rather Bar be allocated and deleted in the context of DLL2, then you can give it one as well. The virtual destructor will ensure that the right operator delete will be called even if you delete the base pointer as in your given example. You might have to dllexport Bar though, as the inliner can sometimes surprise you here.

See MS Support Article 122675 for some more details, although you've actually bounced off the opposite problem than the one they describe there.


Another option: make Foo::Foo protected, and Bar::Bar private, and expose static factory functions for them from your DLL interface. Then the ::operator new call is in the factory function rather than the caller's code, which will put it in the same DLL as the ::operator delete call, and you get the same effect as providing class-specific operator new and operator delete, as well as all the other advantages and disadvantages of factory functions (which are a great improvement once you stop passing raw pointers around and start using unique_ptr or shared_ptr depending on your requirements).

To do this, you have to trust the code in Bar to not call new Foo, or you've brought the problem back. So this one is more protection by convention, while class-specific operator new/operator delete expresses the requirement that memory allocation for that type be done in a certain way.

这篇关于使用PIMPL成语删除DLL中的std :: vector时出错的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆