Skip to content

Commit a642691

Browse files
author
Fabio Anderegg
authored
Call destructor on copied arguments when calling C# method from C++ (MS ABIs only) (#1685)
* on MS abi call destructor on copy-by-value arguments after call to c# function * add tests for destructor call on call by value from c++ to c# * copy-by-value destructor call using Dispose() instead of Internal.dtor to handle destructors in base class
1 parent ea5c095 commit a642691

File tree

5 files changed

+166
-3
lines changed

5 files changed

+166
-3
lines changed

src/Generator/Generators/CSharp/CSharpSources.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,7 +1918,7 @@ private void GenerateVTableManagedCall(Method method)
19181918
var hasReturn = !isVoid && !isSetter;
19191919

19201920
if (hasReturn)
1921-
Write(isPrimitive && !isSetter ? "return " : $"var {Helpers.ReturnIdentifier} = ");
1921+
Write($"var {Helpers.ReturnIdentifier} = ");
19221922

19231923
Write($"{Helpers.TargetIdentifier}.");
19241924
string marshalsCode = string.Join(", ", marshals);
@@ -1933,8 +1933,37 @@ private void GenerateVTableManagedCall(Method method)
19331933
Write($" = {marshalsCode}");
19341934
}
19351935
WriteLine(";");
1936-
if (isPrimitive && !isSetter)
1936+
1937+
// on Microsoft ABIs, the destructor on copy-by-value parameters is
1938+
// called by the called function, not the caller, so we are generating
1939+
// code to do that for classes that have a non-trivial destructor.
1940+
if (Context.ParserOptions.IsMicrosoftAbi)
1941+
{
1942+
for (int i = 0; i < method.Parameters.Count; i++)
1943+
{
1944+
var param = method.Parameters[i];
1945+
if (param.Ignore)
1946+
continue;
1947+
1948+
if (param.Kind == ParameterKind.IndirectReturnType)
1949+
continue;
1950+
1951+
var paramType = param.Type.GetFinalPointee();
1952+
1953+
if (param.IsIndirect &&
1954+
paramType.TryGetClass(out Class paramClass) && !(paramClass is ClassTemplateSpecialization) &&
1955+
paramClass.HasNonTrivialDestructor)
1956+
{
1957+
WriteLine($"{Generator.GeneratedIdentifier("result")}{i}.Dispose(false, true);");
1958+
}
1959+
}
1960+
}
1961+
1962+
if (hasReturn && isPrimitive && !isSetter)
1963+
{
1964+
WriteLine($"return { Helpers.ReturnIdentifier};");
19371965
return;
1966+
}
19381967

19391968
if (hasReturn)
19401969
{

src/Generator/Generators/CodeGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1292,7 +1292,7 @@ public static class Helpers
12921292
public static readonly string InstanceField = Generator.GeneratedIdentifier("instance");
12931293
public static readonly string InstanceIdentifier = Generator.GeneratedIdentifier("Instance");
12941294
public static readonly string PrimaryBaseOffsetIdentifier = Generator.GeneratedIdentifier("PrimaryBaseOffset");
1295-
public static readonly string ReturnIdentifier = Generator.GeneratedIdentifier("ret");
1295+
public static readonly string ReturnIdentifier = "___ret";
12961296
public static readonly string DummyIdentifier = Generator.GeneratedIdentifier("dummy");
12971297
public static readonly string TargetIdentifier = Generator.GeneratedIdentifier("target");
12981298
public static readonly string SlotIdentifier = Generator.GeneratedIdentifier("slot");

tests/CSharp/CSharp.Tests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,4 +1925,56 @@ public void TestPatialRefSupport()
19251925
Assert.That(myclass, Is.Not.SameAs(backup));
19261926
}
19271927

1928+
1929+
class CallByValueInterfaceImpl : CallByValueInterface
1930+
{
1931+
public override void CallByValue(RuleOfThreeTester value)
1932+
{
1933+
}
1934+
public override void CallByReference(RuleOfThreeTester value)
1935+
{
1936+
}
1937+
public override void CallByPointer(RuleOfThreeTester value)
1938+
{
1939+
}
1940+
}
1941+
1942+
[Test]
1943+
public void TestCallByValueCppToCSharpValue()
1944+
{
1945+
RuleOfThreeTester.Reset();
1946+
CallByValueInterface @interface = new CallByValueInterfaceImpl();
1947+
CSharp.CSharp.CallCallByValueInterfaceValue(@interface);
1948+
1949+
Assert.That(RuleOfThreeTester.ConstructorCalls, Is.EqualTo(1));
1950+
Assert.That(RuleOfThreeTester.DestructorCalls, Is.EqualTo(2));
1951+
Assert.That(RuleOfThreeTester.CopyConstructorCalls, Is.EqualTo(1));
1952+
Assert.That(RuleOfThreeTester.CopyAssignmentCalls, Is.EqualTo(0));
1953+
}
1954+
1955+
[Test]
1956+
public void TestCallByValueCppToCSharpReference()
1957+
{
1958+
RuleOfThreeTester.Reset();
1959+
CallByValueInterface @interface = new CallByValueInterfaceImpl();
1960+
CSharp.CSharp.CallCallByValueInterfaceReference(@interface);
1961+
1962+
Assert.That(RuleOfThreeTester.ConstructorCalls, Is.EqualTo(1));
1963+
Assert.That(RuleOfThreeTester.DestructorCalls, Is.EqualTo(1));
1964+
Assert.That(RuleOfThreeTester.CopyConstructorCalls, Is.EqualTo(0));
1965+
Assert.That(RuleOfThreeTester.CopyAssignmentCalls, Is.EqualTo(0));
1966+
}
1967+
1968+
[Test]
1969+
public void TestCallByValueCppToCSharpPointer()
1970+
{
1971+
RuleOfThreeTester.Reset();
1972+
CallByValueInterface @interface = new CallByValueInterfaceImpl();
1973+
CSharp.CSharp.CallCallByValueInterfacePointer(@interface);
1974+
1975+
Assert.That(RuleOfThreeTester.ConstructorCalls, Is.EqualTo(1));
1976+
Assert.That(RuleOfThreeTester.DestructorCalls, Is.EqualTo(1));
1977+
Assert.That(RuleOfThreeTester.CopyConstructorCalls, Is.EqualTo(0));
1978+
Assert.That(RuleOfThreeTester.CopyAssignmentCalls, Is.EqualTo(0));
1979+
}
19281980
}

tests/CSharp/CSharp.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,3 +1691,59 @@ DLL_API int TestFunctionToInstanceMethodStruct(FTIStruct* bb, FTIStruct defaultV
16911691
DLL_API int TestFunctionToInstanceMethodRefStruct(FTIStruct* bb, FTIStruct& defaultValue) { return defaultValue.a; }
16921692
DLL_API int TestFunctionToInstanceMethodConstStruct(FTIStruct* bb, const FTIStruct defaultValue) { return defaultValue.a; }
16931693
DLL_API int TestFunctionToInstanceMethodConstRefStruct(FTIStruct* bb, const FTIStruct& defaultValue) { return defaultValue.a; }
1694+
1695+
int RuleOfThreeTester::constructorCalls = 0;
1696+
int RuleOfThreeTester::destructorCalls = 0;
1697+
int RuleOfThreeTester::copyConstructorCalls = 0;
1698+
int RuleOfThreeTester::copyAssignmentCalls = 0;
1699+
1700+
void RuleOfThreeTester::reset()
1701+
{
1702+
constructorCalls = 0;
1703+
destructorCalls = 0;
1704+
copyConstructorCalls = 0;
1705+
copyAssignmentCalls = 0;
1706+
}
1707+
1708+
RuleOfThreeTester::RuleOfThreeTester()
1709+
{
1710+
a = 0;
1711+
constructorCalls++;
1712+
}
1713+
1714+
RuleOfThreeTester::RuleOfThreeTester(const RuleOfThreeTester& other)
1715+
{
1716+
a = other.a;
1717+
copyConstructorCalls++;
1718+
}
1719+
1720+
RuleOfThreeTester::~RuleOfThreeTester()
1721+
{
1722+
destructorCalls++;
1723+
}
1724+
1725+
RuleOfThreeTester& RuleOfThreeTester::operator=(const RuleOfThreeTester& other)
1726+
{
1727+
a = other.a;
1728+
copyAssignmentCalls++;
1729+
return *this;
1730+
}
1731+
1732+
// test if generated code correctly calls constructors and destructors when going from C++ to C#
1733+
void CallCallByValueInterfaceValue(CallByValueInterface* interface)
1734+
{
1735+
RuleOfThreeTester value;
1736+
interface->CallByValue(value);
1737+
}
1738+
1739+
void CallCallByValueInterfaceReference(CallByValueInterface* interface)
1740+
{
1741+
RuleOfThreeTester value;
1742+
interface->CallByReference(value);
1743+
}
1744+
1745+
void CallCallByValueInterfacePointer(CallByValueInterface* interface)
1746+
{
1747+
RuleOfThreeTester value;
1748+
interface->CallByPointer(&value);
1749+
}

tests/CSharp/CSharp.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,3 +1551,29 @@ DLL_API inline ClassWithIntValue* CreateCore(CS_IN_OUT ClassWithIntValue*& pClas
15511551
pClass->value = 20;
15521552
return nullptr;
15531553
}
1554+
1555+
1556+
struct DLL_API RuleOfThreeTester {
1557+
int a;
1558+
static int constructorCalls;
1559+
static int destructorCalls;
1560+
static int copyConstructorCalls;
1561+
static int copyAssignmentCalls;
1562+
1563+
static void reset();
1564+
1565+
RuleOfThreeTester();
1566+
~RuleOfThreeTester();
1567+
RuleOfThreeTester(const RuleOfThreeTester& other);
1568+
RuleOfThreeTester& operator=(const RuleOfThreeTester& other);
1569+
};
1570+
1571+
struct DLL_API CallByValueInterface {
1572+
virtual void CallByValue(RuleOfThreeTester value) = 0;
1573+
virtual void CallByReference(RuleOfThreeTester& value) = 0;
1574+
virtual void CallByPointer(RuleOfThreeTester* value) = 0;
1575+
};
1576+
1577+
void DLL_API CallCallByValueInterfaceValue(CallByValueInterface*);
1578+
void DLL_API CallCallByValueInterfaceReference(CallByValueInterface*);
1579+
void DLL_API CallCallByValueInterfacePointer(CallByValueInterface*);

0 commit comments

Comments
 (0)