fix Issue 22323: Link error for virtual destructor of C++ class in DLL

The new test test/dshell/dll_cxx.d is based on test/dshell/dll.d, but
builds the DLL/SO from C++ instead of D.
This commit is contained in:
Tim Schendekehl 2021-10-11 18:18:54 +02:00 committed by The Dlang Bot
parent 454b526340
commit 1e6217535f
7 changed files with 281 additions and 13 deletions

View file

@ -293,7 +293,7 @@ jobs:
########################################
- name: Run C++ test suite
run: |
./dmd/test/run.d --environment runnable_cxx MODEL=64
./dmd/test/run.d --environment runnable_cxx dshell/dll_cxx.d MODEL=64
#if [ ${{ matrix.compiler }} == "g++" ]; then
# ./dmd/test/run.d clean
# ./dmd/test/run.d runnable_cxx MODEL=32

View file

@ -1079,17 +1079,16 @@ private DtorDeclaration buildWindowsCppDtor(AggregateDeclaration ad, DtorDeclara
auto ftype = new TypeFunction(ParameterList(params), Type.tvoidptr, LINK.cpp, dtor.storage_class);
auto func = new DtorDeclaration(dtor.loc, dtor.loc, dtor.storage_class, Id.cppdtor);
func.type = ftype;
if (dtor.fbody)
{
const loc = dtor.loc;
auto stmts = new Statements;
auto call = new CallExp(loc, dtor, null);
call.directcall = true;
stmts.push(new ExpStatement(loc, call));
stmts.push(new ReturnStatement(loc, new CastExp(loc, new ThisExp(loc), Type.tvoidptr)));
func.fbody = new CompoundStatement(loc, stmts);
func.generated = true;
}
// Always generate the function with body, because it is not exported from DLLs.
const loc = dtor.loc;
auto stmts = new Statements;
auto call = new CallExp(loc, dtor, null);
call.directcall = true;
stmts.push(new ExpStatement(loc, call));
stmts.push(new ReturnStatement(loc, new CastExp(loc, new ThisExp(loc), Type.tvoidptr)));
func.fbody = new CompoundStatement(loc, stmts);
func.generated = true;
auto sc2 = sc.push();
sc2.stc &= ~STC.static_; // not a static destructor

View file

@ -501,7 +501,7 @@ Symbol *toImport(Symbol *sym)
}
else
{
idlen = sprintf(id,(target.os == Target.OS.Windows && target.is64bit) ? "__imp_%s" : "_imp__%s",n);
idlen = sprintf(id,(target.os == Target.OS.Windows && target.is64bit) ? "__imp_%s" : (sym.Stype.Tmangle == mTYman_cpp) ? "_imp_%s" : "_imp__%s",n);
}
auto t = type_alloc(TYnptr | mTYconst);
t.Tnext = sym.Stype;

64
test/dshell/dll_cxx.d Normal file
View file

@ -0,0 +1,64 @@
import dshell;
import std.stdio;
int main()
{
// Only run this test, if CC has been set.
if (Vars.CC.empty)
return DISABLED;
version (Windows)
if (environment.get("C_RUNTIME", "") == "mingw")
return DISABLED;
version (FreeBSD)
if (Vars.MODEL == "32")
return DISABLED;
version (OSX)
Vars.set(`SOEXT`, `.dylib`);
Vars.set(`SRC`, `$EXTRA_FILES${SEP}dll_cxx`);
Vars.set(`EXE_NAME`, `$OUTPUT_BASE${SEP}testdll$EXE`);
Vars.set(`DLL`, `$OUTPUT_BASE${SEP}mydll$SOEXT`);
string[] dllCmd = [Vars.CC];
string mainExtra;
version (Windows)
{
Vars.set(`DLL_LIB`, `$OUTPUT_BASE${SEP}mydll.lib`);
if (Vars.MODEL == "32")
{
// CC should be dmc for win32.
dllCmd ~= [`-mn`, `-L/implib:` ~ Vars.DLL_LIB, `-WD`, `-o` ~ Vars.DLL, `kernel32.lib`, `user32.lib`];
mainExtra = `$DLL_LIB`;
}
else
{
// CC should be cl for win32mscoff.
dllCmd ~= [`/LD`, `/nologo`, `/Fe` ~ Vars.DLL];
mainExtra = `$DLL_LIB`;
}
}
else version(OSX)
{
dllCmd ~= [`-dynamiclib`, `-fPIC`, `-o`, Vars.DLL, `-lstdc++`];
mainExtra = `-fPIC -L-L$OUTPUT_BASE -L$DLL -L-lstdc++ -L--no-demangle`;
}
else
{
dllCmd ~= [`-shared`, `-fPIC`, `-o`, Vars.DLL];
mainExtra = `-fPIC -L-L$OUTPUT_BASE -L$DLL -L-lstdc++ -L--no-demangle`;
}
dllCmd ~= Vars.SRC ~ Vars.SEP ~ `mydll.cpp`;
// The arguments have to be passed as an array, because run would replace '/' with '\\' otherwise.
run(dllCmd);
run(`$DMD -m$MODEL -I$SRC -g -od=$OUTPUT_BASE -of=$EXE_NAME $SRC/testdll.d $SRC/cppnew.d ` ~ mainExtra);
run(`$EXE_NAME`, stdout, stderr, [`LD_LIBRARY_PATH`: Vars.OUTPUT_BASE]);
return 0;
}

View file

@ -0,0 +1,81 @@
module cppnew;
/* This module contains copies from core.stdcpp.new_.d, but with
* modifications for DMC. */
T* cpp_new(T, Args...)(auto ref Args args) if (!is(T == class))
{
import core.lifetime : emplace, forward;
T* mem = cast(T*)__cpp_new(T.sizeof);
return mem.emplace(forward!args);
}
T cpp_new(T, Args...)(auto ref Args args) if (is(T == class))
{
import core.lifetime : emplace, forward;
T mem = cast(T)__cpp_new(__traits(classInstanceSize, T));
return mem.emplace(forward!args);
}
void cpp_delete(T)(T* ptr) if (!is(T == class))
{
destroy!false(*ptr);
__cpp_delete(ptr);
}
void cpp_delete(T)(T instance) if (is(T == class))
{
destroy!false(instance);
__cpp_delete(cast(void*) instance);
}
/// Binding for ::operator new(std::size_t count)
pragma(mangle, __new_mangle)
extern(C++) void* __cpp_new(size_t count);
/// Binding for ::operator delete(void* ptr)
pragma(mangle, __delete_mangle)
extern(C++) void __cpp_delete(void* ptr);
// we have to hard-code the mangling for the global new/delete operators
version (CppRuntime_Microsoft)
{
version (D_LP64)
{
enum __new_mangle = "??2@YAPEAX_K@Z";
enum __delete_mangle = "??3@YAXPEAX@Z";
}
else
{
enum __new_mangle = "??2@YAPAXI@Z";
enum __delete_mangle = "??3@YAXPAX@Z";
}
}
else version (CppRuntime_DigitalMars)
{
version (D_LP64)
{
enum __new_mangle = "??2@YAPEAX_K@Z";
enum __delete_mangle = "??3@YAXPEAX@Z";
}
else
{
enum __new_mangle = "??2@YAPAXI@Z";
enum __delete_mangle = "??3@YAXPAX@Z";
}
}
else
{
version (D_LP64)
{
enum __new_mangle = "_Znwm";
enum __delete_mangle = "_ZdlPv";
}
else
{
enum __new_mangle = "_Znwj";
enum __delete_mangle = "_ZdlPv";
}
}

View file

@ -0,0 +1,51 @@
#ifdef _WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
class EXPORT C22323
{
public:
C22323();
virtual ~C22323();
static int ctorCount;
static int dtorCount;
};
int C22323::ctorCount;
int C22323::dtorCount;
C22323::C22323()
{
ctorCount++;
}
C22323::~C22323()
{
dtorCount++;
}
struct EXPORT S22323
{
public:
S22323(int dummy);
~S22323();
static int ctorCount;
static int dtorCount;
};
int S22323::ctorCount;
int S22323::dtorCount;
S22323::S22323(int dummy)
{
ctorCount++;
}
S22323::~S22323()
{
dtorCount++;
}

View file

@ -0,0 +1,73 @@
version(Windows)
enum EXPORT = "export ";
else
enum EXPORT = "";
// https://issues.dlang.org/show_bug.cgi?id=22323
extern(C++) class C22323
{
this();
~this();
mixin(EXPORT ~ q{static extern __gshared int ctorCount;});
mixin(EXPORT ~ q{static extern __gshared int dtorCount;});
}
extern(C++) struct S22323
{
this(int dummy);
~this();
mixin(EXPORT ~ q{static extern __gshared int ctorCount;});
mixin(EXPORT ~ q{static extern __gshared int dtorCount;});
}
void test22323()
{
import cppnew;
assert(C22323.ctorCount == 0);
assert(C22323.dtorCount == 0);
C22323 o = cpp_new!C22323;
assert(C22323.ctorCount == 1);
assert(C22323.dtorCount == 0);
cpp_delete(o);
assert(C22323.ctorCount == 1);
assert(C22323.dtorCount == 1);
o = new C22323;
assert(C22323.ctorCount == 2);
assert(C22323.dtorCount == 1);
o.destroy;
assert(C22323.ctorCount == 2);
assert(C22323.dtorCount == 2);
assert(S22323.ctorCount == 0);
assert(S22323.dtorCount == 0);
{
S22323 s = S22323(0);
assert(S22323.ctorCount == 1);
assert(S22323.dtorCount == 0);
}
assert(S22323.ctorCount == 1);
assert(S22323.dtorCount == 1);
S22323 *s = cpp_new!S22323(0);
assert(S22323.ctorCount == 2);
assert(S22323.dtorCount == 1);
cpp_delete(s);
assert(S22323.ctorCount == 2);
assert(S22323.dtorCount == 2);
s = new S22323(0);
assert(S22323.ctorCount == 3);
assert(S22323.dtorCount == 2);
(*s).destroy();
assert(S22323.ctorCount == 3);
assert(S22323.dtorCount == 3);
}
void main()
{
test22323();
}