Skip to content

Commit 3d67609

Browse files
Copilotjtschuster
andcommitted
Add behavioral tests for NativeMarshalling on COM interfaces
Added tests to ComInterfaceGenerator.Tests that verify: 1. Getting the same COM interface twice returns unique instances when using NativeMarshallingAttribute with UniqueComInterfaceMarshaller 2. COM interface methods returning the same interface type return new managed instances, not cached ones Created IUniqueMarshalling interface with NativeMarshallingAttribute, native exports, and comprehensive tests. Co-authored-by: jtschuster <[email protected]>
1 parent 5cd3516 commit 3d67609

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
using System.Runtime.InteropServices.Marshalling;
7+
using SharedTypes.ComInterfaces;
8+
using Xunit;
9+
10+
namespace ComInterfaceGenerator.Tests
11+
{
12+
public unsafe partial class NativeMarshallingAttributeTests
13+
{
14+
[LibraryImport(NativeExportsNE.NativeExportsNE_Binary, EntryPoint = "new_unique_marshalling")]
15+
internal static partial IUniqueMarshalling NewUniqueMarshalling();
16+
17+
[Fact]
18+
public void GetSameComInterfaceTwiceReturnsUniqueInstances()
19+
{
20+
// When using NativeMarshalling with UniqueComInterfaceMarshaller,
21+
// getting the same COM interface twice should return different managed instances
22+
var obj1 = NewUniqueMarshalling();
23+
var obj2 = NewUniqueMarshalling();
24+
25+
Assert.NotSame(obj1, obj2);
26+
27+
// Verify they work independently
28+
obj1.SetValue(42);
29+
obj2.SetValue(100);
30+
31+
Assert.Equal(42, obj1.GetValue());
32+
Assert.Equal(100, obj2.GetValue());
33+
}
34+
35+
[Fact]
36+
public void MethodReturningComInterfaceReturnsUniqueInstance()
37+
{
38+
// When a COM interface method returns the same interface type,
39+
// it should return a new managed instance, not the cached one
40+
var obj = NewUniqueMarshalling();
41+
obj.SetValue(42);
42+
43+
var returnedObj = obj.GetThis();
44+
45+
// Should be a different managed object
46+
Assert.NotSame(obj, returnedObj);
47+
48+
// But should refer to the same underlying COM object
49+
Assert.Equal(42, returnedObj.GetValue());
50+
51+
// Modifying through one should affect the other
52+
returnedObj.SetValue(100);
53+
Assert.Equal(100, obj.GetValue());
54+
}
55+
}
56+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
using System.Runtime.InteropServices.Marshalling;
7+
8+
namespace SharedTypes.ComInterfaces
9+
{
10+
[GeneratedComInterface]
11+
[Guid(IID)]
12+
[NativeMarshalling(typeof(UniqueComInterfaceMarshaller<IUniqueMarshalling>))]
13+
internal partial interface IUniqueMarshalling
14+
{
15+
int GetValue();
16+
void SetValue(int x);
17+
IUniqueMarshalling GetThis();
18+
19+
public const string IID = "E11D5F3E-DD57-4E7E-A78C-F5F8B8E0A1F4";
20+
}
21+
22+
[GeneratedComClass]
23+
internal partial class UniqueMarshalling : IUniqueMarshalling
24+
{
25+
int _data = 0;
26+
public int GetValue() => _data;
27+
public void SetValue(int x) => _data = x;
28+
public IUniqueMarshalling GetThis() => this;
29+
}
30+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
8+
using SharedTypes.ComInterfaces;
9+
using static System.Runtime.InteropServices.ComWrappers;
10+
11+
namespace NativeExports.ComInterfaceGenerator
12+
{
13+
public static unsafe class UniqueMarshalling
14+
{
15+
// Call from another assembly to get a ptr to make an RCW
16+
[UnmanagedCallersOnly(EntryPoint = "new_unique_marshalling")]
17+
public static void* CreateComObject()
18+
{
19+
MyComWrapper cw = new();
20+
var myObject = new ImplementingObject();
21+
nint ptr = cw.GetOrCreateComInterfaceForObject(myObject, CreateComInterfaceFlags.None);
22+
23+
return (void*)ptr;
24+
}
25+
26+
class MyComWrapper : ComWrappers
27+
{
28+
static void* _s_comInterface1VTable = null;
29+
static void* s_comInterface1VTable
30+
{
31+
get
32+
{
33+
if (MyComWrapper._s_comInterface1VTable != null)
34+
return _s_comInterface1VTable;
35+
void** vtable = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(UniqueMarshalling), sizeof(void*) * 6);
36+
GetIUnknownImpl(out var fpQueryInterface, out var fpAddReference, out var fpRelease);
37+
vtable[0] = (void*)fpQueryInterface;
38+
vtable[1] = (void*)fpAddReference;
39+
vtable[2] = (void*)fpRelease;
40+
vtable[3] = (delegate* unmanaged<void*, int*, int>)&ImplementingObject.ABI.GetValue;
41+
vtable[4] = (delegate* unmanaged<void*, int, int>)&ImplementingObject.ABI.SetValue;
42+
vtable[5] = (delegate* unmanaged<void*, void**, int>)&ImplementingObject.ABI.GetThis;
43+
_s_comInterface1VTable = vtable;
44+
return _s_comInterface1VTable;
45+
}
46+
}
47+
protected override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
48+
{
49+
if (obj is ImplementingObject)
50+
{
51+
ComInterfaceEntry* comInterfaceEntry = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ImplementingObject), sizeof(ComInterfaceEntry));
52+
comInterfaceEntry->IID = new Guid(IUniqueMarshalling.IID);
53+
comInterfaceEntry->Vtable = (nint)s_comInterface1VTable;
54+
count = 1;
55+
return comInterfaceEntry;
56+
}
57+
count = 0;
58+
return null;
59+
}
60+
61+
protected override object? CreateObject(nint externalComObject, CreateObjectFlags flags) => throw new NotImplementedException();
62+
protected override void ReleaseObjects(IEnumerable objects) => throw new NotImplementedException();
63+
}
64+
65+
class ImplementingObject : IUniqueMarshalling
66+
{
67+
int _data = 0;
68+
69+
int IUniqueMarshalling.GetValue()
70+
{
71+
return _data;
72+
}
73+
void IUniqueMarshalling.SetValue(int x)
74+
{
75+
_data = x;
76+
}
77+
IUniqueMarshalling IUniqueMarshalling.GetThis()
78+
{
79+
return this;
80+
}
81+
82+
// Provides function pointers in the COM format to use in COM VTables
83+
public static class ABI
84+
{
85+
86+
[UnmanagedCallersOnly]
87+
public static int GetValue(void* @this, int* value)
88+
{
89+
try
90+
{
91+
*value = ComInterfaceDispatch.GetInstance<IUniqueMarshalling>((ComInterfaceDispatch*)@this).GetValue();
92+
return 0;
93+
}
94+
catch (Exception e)
95+
{
96+
return e.HResult;
97+
}
98+
}
99+
100+
[UnmanagedCallersOnly]
101+
public static int SetValue(void* @this, int newValue)
102+
{
103+
try
104+
{
105+
ComInterfaceDispatch.GetInstance<IUniqueMarshalling>((ComInterfaceDispatch*)@this).SetValue(newValue);
106+
return 0;
107+
}
108+
catch (Exception e)
109+
{
110+
return e.HResult;
111+
}
112+
}
113+
114+
[UnmanagedCallersOnly]
115+
public static int GetThis(void* @this, void** value)
116+
{
117+
try
118+
{
119+
IUniqueMarshalling result = ComInterfaceDispatch.GetInstance<IUniqueMarshalling>((ComInterfaceDispatch*)@this).GetThis();
120+
MyComWrapper cw = new();
121+
*value = (void*)cw.GetOrCreateComInterfaceForObject(result, CreateComInterfaceFlags.None);
122+
return 0;
123+
}
124+
catch (Exception e)
125+
{
126+
return e.HResult;
127+
}
128+
}
129+
}
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)