Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

VC 5.0, _declspec(dllimport) and virtual destructors

623 views
Skip to first unread message

Vadim Edorov

unread,
May 12, 1997, 3:00:00 AM5/12/97
to

If class A declared as _declspec(dllimport) and has
virtual desrtuctor :

class _declspec(dllimport) A {
public:
virtual ~A();
};

and instance of class A created via new operator :

A* p = new A() ;

VC 5.0 compiler generates symbol A::`local vftable' and
replaces vftable created by A constructor with this symbol.
A::`local vftable' contains pointers to import table entries which are
resides in module calling new operator.
If calling module is DLL and it dynamicaly unloaded while
instance of A still exists any call through A vftable cause Acces
Violation.
If class A is not dllimport, it's vftable points to it's own code and
everything okay.

Is there anyone who can answer me : Is it a bug or a feature ?
And what it for ?
And is there any switches (or something else) to make compiler not to do it
?

Thanks,
Vadim Egorov.


Jim Marshall

unread,
May 12, 1997, 3:00:00 AM5/12/97
to

I don't think I understand the question:

You export a class with a virtual DTOR. If you dynamicly unload the DLL
(FreeLibrary?) but still have an instance of the class in the EXE, the
EXE crashes?

If that is your question, then the behavior sounds correct. You have
unloaded the DLL so the code is no longer available. Now you try to
call the DTOR, it jumps to the last known address and begins to
execute. The DLL is not there so your now trying to execute memory you
don't own.
--
Why are builders afraid to have a 13th floor
but book publishers aren't afraid to have a Chapter 11?

Tim Ebben

unread,
May 12, 1997, 3:00:00 AM5/12/97
to

Vadim Edorov <ego...@1c.ru> wrote in article
<OUMInMr...@uppssnewspub04.moswest.msn.net>...

> If class A declared as _declspec(dllimport) and has
> virtual desrtuctor :
>
> class _declspec(dllimport) A {
> public:
> virtual ~A();
> };
>
> and instance of class A created via new operator :
>
> A* p = new A() ;
>
> VC 5.0 compiler generates symbol A::`local vftable' and
> replaces vftable created by A constructor with this symbol.

Just off the top of my head, don't you want
A* p = new A;
???

--
TIME
2470 Island Drive #304
Spring Park, MN 55384

Vadim Edorov

unread,
May 13, 1997, 3:00:00 AM5/13/97
to

Library DLL_A.DLL implements end exports class A with virtual DTOR.
Application App.EXE and Library DLL_B.DLL are linked with DLL_A.DLL ;
Application App.EXE calls LoadLibrary( "DLL_B.DLL")
DLL_B.DLL makes call A* p = new A() ; and sends pointer p to DLL_A.DLL
where it stored untill the end of execution.
Compliler generated instance of class A vftable in DLL_B.DLL code and
replaced vftable pointer of new object by pointer to local vftable.
And now we have pointer p with vftable pointed to DLL_B.DLL address space
.
( but class a implemenation is in the DLL_A.DLL code ).
App.EXE calls FreeLibrary(/* HMODULE of DLL_B.DLL */) .
And now vftable of p contains invalid pointers.


Jim Marshall wrote in article <3377A3...@msn.com.NoSpam>...

W. Tuchan

unread,
May 13, 1997, 3:00:00 AM5/13/97
to

I haven't tried this but I think if you declare a non-inline constructor
for you class you can force the vtable into DLL_A.DLL


Jim Marshall

unread,
May 13, 1997, 3:00:00 AM5/13/97
to

I think I have a better understanding of the problem. The next question
I have is this... What C Runtime are you using with App.EXE DLL_B.DLL
and DLL_A.DLL? For example are you using single threaded static link,
multithreaded static link or multithreaded using DLL?
--
I have no beliefs....
I believe I'm a walking contradiction....

Vadim Egorov

unread,
May 14, 1997, 3:00:00 AM5/14/97
to

I am using multithreaded DLL CRT, but compiler generates
'local vftable' independently of this switch
(and all other switches i tried to use ).
Jim Marshall wrote in article <3378F3...@msn.com.NoSpam>...

Jim Marshall

unread,
May 14, 1997, 3:00:00 AM5/14/97
to

Well I don't understand the concept of "local" I guess. The vftable is
local to what? In Win32 there is no such thing as local and non-local.
Everything sits in memory which belongs to the process (assuming
everything is using the DLL version of the CRT).

Phil Lucido

unread,
May 14, 1997, 3:00:00 AM5/14/97
to

(Warning: not official support, yada yadda yaddda)

Let me see if I can explain. First, the really quick workaround - just
turn off dllimport on the class. It's OK to leave dllexport on the DLL
side.

Consider the following source code:

=============== foo.h ===============
class DLLIMPEXP Foo {
public:
Foo();
virtual ~Foo();
};
============== exe.cpp ==============
#include <stdio.h>
#include <malloc.h>
#define DLLIMPEXP __declspec(dllimport)
#include "foo.h"

void *operator new(size_t sz) {
puts("In EXE's ::operator new");
return malloc(sz);
}

void operator delete(void *p) {
puts("In EXE's ::operator delete");
free(p);
}

void main(void) {
Foo* pFoo = new Foo;
delete pFoo;
}
============== dll.cpp ==============
#include <stdio.h>
#include <malloc.h>
#define DLLIMPEXP __declspec(dllexport)
#include "foo.h"

void *operator new(size_t sz) {
puts("In DLL's ::operator new");
return malloc(sz);
}

void operator delete(void *p) {
puts("In DLL's ::operator delete");
free(p);
}

Foo::Foo() {
puts("in DLL's Foo::Foo()");
}

Foo::~Foo() {
puts("in DLL's Foo::~Foo()");
}
============= repro.bat =============
del dll.dll dll.exp dll.lib exe.exe *.obj 2>nul
cl -LD -MLd dll.cpp
cl -MLd exe.cpp dll.lib
=====================================

In VC++ 4.2, running exe.exe will produce:
In EXE's ::operator new
in DLL's Foo::Foo()
in DLL's Foo::~Foo()
In DLL's ::operator delete
while in VC++ 5.0, it instead outputs
In EXE's ::operator new
in DLL's Foo::Foo()
in DLL's Foo::~Foo()
In EXE's ::operator delete

The difference is in which version of operator delete is called. Further,
the 4.2 version will pop up a CRT assertion when linked with the static
debug CRT lib, as in repro.bat.

In 4.2, "new Foo" in the EXE calls the EXE's version of ::operator new,
then calls Foo's ctor inside the DLL. There, in the DLL, the object's
vftable pointer would be set to a vftable within the DLL. Now, when you've
got a virtual dtor, the dtor slot in the vftable doesn't point to the real
dtor, instead it points to a compiler-generated helper, the deleting dtor,
which first calls the real dtor, then calls operator delete if necessary.
In 4.2, this del-dtor helper would be in the same context as the vftable,
and call operator delete in the same context, so all of these were on the
DLL side of the EXE/DLL boundary. Even though the "delete pFoo;" was in
the EXE, all the actual work was done by calling the del-dtor through the
vftable. If the EXE's operator new was not compatible with the DLL's
operator delete, as in this example, you end up with mismatched heap
allocation/deallocation. If you're lucky, you assert, if not, well, who
knows when the problem shows up.

To fix this bug, in 5.0, we changed things a bit. When you do "new Foo" on
a dllimport class with a virtual dtor, we call ::operator new, then the
ctor, the same as before. But when the ctor returns, we replace the
vftable pointer, pointing instead at a copy of the vftable, the "local
vftable," which has the dtor slot pointing at a local copy of the del-dtor
helper. This helper will call the dtor as normal, but then calls whatever
operator delete is present in the context of the new. That means we get
matching new/delete operators.

The local vftable makes it much safer to do new and delete on opposite
sides of a DLL boundary, which is the bug we were trying to fix. What I
didn't forsee was this problem with the local vftable and associated
executable code being yanked out from under you by a FreeLibrary.
Fortunately, it's pretty easy to avoid the local vftable rewrite. The
rewrite is only done if an entire class is marked __declspec(dllimport),
and the class has a virtual dtor. So you've got three choices:

1) Make the dtor non-virtual. Obviously not recommended, just included for
completeness.
2) Get rid of dllimport. You can still leave dllexport on the DLL side of
things, so you don't have to start using DEF files. All that's important
is that, on the EXE side, the class isn't dllimport. This has a slight
performance hit, in that calls to the non-virtual methods will go through
import thunks instead of being indirect calls through the import table. In
effect, you've got one extra jump. The penalty doesn't affect the virtual
methods, since those will be called through the vtable in the DLL.
3) Get rid of dllimport on the class, and instead mark the individual
methods dllimport. That way, you avoid the import thunk for non-virtual
methods.

...Phil (VC++ compiler developer)

--
The opinions expressed in this message are my own personal views and do not
reflect the official views of Microsoft Corporation. My responses are not
to be considered official technical support or advice. I do this on my on
time. If you need guaranteed response, contact Microsoft's official
technical support channels.

Jim Marshall <Jim_Ma...@msn.com> wrote in article
<3379E9...@msn.com>...

Vadim Egorov

unread,
May 15, 1997, 3:00:00 AM5/15/97
to

Thanks a lot!
The best explanation one can expect.
I suspected something of that sort but now things have took their
places.
The only thing I sorrow for is importing static class data
( such as CRuntimeClass stuctures).
By the case, is there any way to use imported data without
declaring them dllimport ang getting linker error : unresolved external ?

Regards,
Vadim Egorov.


Phil Lucido wrote in article <01bc60c3$4fd946e0$4862389d@philiplu2>...

Phil Lucido

unread,
May 15, 1997, 3:00:00 AM5/15/97
to

Vadim Egorov <ego...@1c.ru> wrote in article
<#wSXxKVY...@uppssnewspub05.moswest.msn.net>...

> Thanks a lot!
> The best explanation one can expect.
> I suspected something of that sort but now things have took their
> places.
> The only thing I sorrow for is importing static class data
> ( such as CRuntimeClass stuctures).

You can still do this, by marking the individual static item as dllimport,
e.g.

class Foo {
public:
static __declspec(dllimport) int sm_x;
__declspec(dllimport) Foo();
// ...
}

> By the case, is there any way to use imported data without
> declaring them dllimport ang getting linker error : unresolved external ?

Sorry, no. The compiler needs to know data is dllimport, otherwise it
doesn't know to generate code for the extra indirection required. That's
not necessary for functions since the IAT has a code thunk to do the
interaction for you when calling into a DLL. That's not something you can
do with data, though, just calls.

...Phil

> Regards,
> Vadim Egorov.


0 new messages