Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic implementation of the ComWrappers #653

Merged
merged 58 commits into from
Apr 21, 2021

Conversation

kant2002
Copy link
Contributor

@kant2002 kant2002 commented Feb 7, 2021

This is attempt to resurrect dotnet/corert#8143
in order to improve status of #306

@kant2002
Copy link
Contributor Author

kant2002 commented Feb 9, 2021

@jkotas I have future questions.
My understanding how ComWrappers currently works in CoreCLR is following.
When CCW information stored in the lazy storage called SyncBlock which stored in GC information about managed object, SyncBlock in itself has InteropSyncBlockInfo where actual CCW pointer would be stored. That's quite a machinery which I have to replicate. Okay, don't scary too much of that. But is there a way how this can be implemented in C# at least as POC without touching too much stuff? Should I replicate all that machinery, or that can be done using some global dictionary for example?

Other option is to extend runtime.lib to include necessary C++ code by modifying src\coreclr\nativeaot\Runtime\CMakeLists.txt and start with including src\coreclr\vm\interoplibinterface.cpp and when what else compilation errors require me to add.

@jkotas
Copy link
Member

jkotas commented Feb 9, 2021

But is there a way how this can be implemented in C# at least as POC without touching too much stuff?

You can do that using ConditionalWeakTable, like it is done in #306 (comment).

ConditionalWeakTable and SyncBlocks are equivalent mechanisms. I think we would actually prefer ConditionalWeakTable as the permanent solution for this.

@kant2002
Copy link
Contributor Author

@jkotas I have what I would say very rough draft. I just trying understand do I move in proper direction or not?

@jkotas
Copy link
Member

jkotas commented Feb 12, 2021

Yes, I think you are going in the right direction.

I think some of the internal and private "called by the runtime" methods you have copied over will have different shape at the end or won't be needed at all.

I have 2 ideas. CCW seems to be rathe rcompilcated. But RCW require just having Dictionary<IntrPtr, object> to keep track of RCWs. Most likely this should be some weekly referenced object, which should release COM object when no longer referenced. In fnalizer probably.
@kant2002
Copy link
Contributor Author

@jkotas do I still move in proper direction? I definitely still have some not needed flags, and what I assume I should do if I move in right direction

  • Implement MOW AddRef/Release/QI
  • Proper cleanup not in finalizer. Seems to be I should cleanup MOW on Release.
  • Populate Target with GC handle of wrapped runtime object from which MOW constructed. GcHandle.InternalAlloc
  • Release GC handle when object no longer used by COM

Questions

  • I assume that ComInterfaceInstance is partly what is ManagedObjectWrapper. Should I use only one structure?
  • What's better way for memmove at that level?

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think you are still moving in the right direction. I have left a bunch of comments.

I assume that ComInterfaceInstance is partly what is ManagedObjectWrapper. Should I use only one structure?

Yes, they look the same. It would make sense to use only one structure.

What's better way for memmove at that level?

We prefer to use Span.CopyTo where posible instead of memmove.

@kant2002
Copy link
Contributor Author

@jkotas I think I more or less finish with COM creation.
What is not clear for me, how I should cleanup proxy objects created for COM.

@MichalStrehovsky
Copy link
Member

@MichalStrehovsky while Jan is busy with work or life

Jan is on vacation :).

It seems that Type.GUID do not preserved in any kind on interfaces, or ...?

RuntimeBlockedTypeInfo

This is what we discussed in #802. You can delete __BlockAllReflectionAttribute from the test to unblock yourself right now.

@kant2002
Copy link
Contributor Author

@jkotas is this comments all what you have? I'm done with comments. Hope you enjoy your vacation.

{
if (pUnk == null)
{
return IntPtr.Zero;
}

#if TARGET_WINDOWS
#pragma warning disable CA1416
Guid interfaceGuid = convertToType.GUID;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more inline with the NativeAOT design printiples for the GUID to be passed as an argument into this method by the toolchain instead of computing it at runtime via reflection.

@MichalStrehovsky Do you expect that there are going to be special measures required to make the interface reflectable going forward, or is this going to just work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using reflection to fetch the GUID here is also going to make the marshalling much slower compared to regular CLR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the design we will have to settle with is that types are always reflectable, so fetching a GUID should be reliable, but yes, this is not going to be very fast.

It shouldn't be too much of a problem to change this signature of this helper to take an actual GUID, and in the compiler:

  • Use .GetDecodedCustomAttributes to get the attributes on the type.
  • Parse the string into a GUID.
  • Emit IL to call the Guid(Int32, Int16, Int16, Byte, Byte, Byte, Byte, Byte, Byte, Byte, Byte) constructor with the parsed GUID values.

We can scope out types that don't have a GuidAttribute for now (runtime reflection would not compute the correct GUID for them either).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MichalStrehovsky I'm not sure that I properly understand how to jump from TypeDesc to EcmaType.
I plan to use following

CustomAttributeValue<TypeDesc>? customAttributeValue = ((EcmaType)this.ManagedParameterType.GetTypeDefinition()).GetDecodedCustomAttribute("", "GuidAttribute");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this.ManagedParameterType as EcmaType)?.GetDecodedCustomAttribute("System.Runtime.InteropServices", "GuidAttribute")

  • The attribute namespace has to be specified
  • GetTypeDefinition would be only needed for generic types. Built-in COM interop does not work for generic types so you can just skip that part.

@jkotas
Copy link
Member

jkotas commented Apr 17, 2021

is this comments all what you have?

Two comments left:

Copy link
Member

@MichalStrehovsky MichalStrehovsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, thank you!

{
public static int Main(string[] args)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if you put <CLRTestTargetUnsupported Condition="'$(TargetsWindows)' != 'true'">true</CLRTestTargetUnsupported> in the csproj, this test will be skipped. It's better than AOT compiling it only for the test to do nothing.

src/tests/nativeaot/SmokeTests/ComWrappers/ComWrappers.cs Outdated Show resolved Hide resolved
src/tests/nativeaot/SmokeTests/ComWrappers/ComWrappers.cs Outdated Show resolved Hide resolved
{
if (pUnk == null)
{
return IntPtr.Zero;
}

#if TARGET_WINDOWS
#pragma warning disable CA1416
Guid interfaceGuid = convertToType.GUID;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the design we will have to settle with is that types are always reflectable, so fetching a GUID should be reliable, but yes, this is not going to be very fast.

It shouldn't be too much of a problem to change this signature of this helper to take an actual GUID, and in the compiler:

  • Use .GetDecodedCustomAttributes to get the attributes on the type.
  • Parse the string into a GUID.
  • Emit IL to call the Guid(Int32, Int16, Int16, Byte, Byte, Byte, Byte, Byte, Byte, Byte, Byte) constructor with the parsed GUID values.

We can scope out types that don't have a GuidAttribute for now (runtime reflection would not compute the correct GUID for them either).

var int32Type = Context.GetWellKnownType(WellKnownType.Int32);
var int16Type = Context.GetWellKnownType(WellKnownType.Int16);
var byteType = Context.GetWellKnownType(WellKnownType.Byte);
guidType.GetParameterlessConstructor();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
guidType.GetParameterlessConstructor();

var int16Type = Context.GetWellKnownType(WellKnownType.Int16);
var byteType = Context.GetWellKnownType(WellKnownType.Byte);
guidType.GetParameterlessConstructor();
int param1 = int.Parse(parts[0], System.Globalization.NumberStyles.HexNumber);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be simpler and faster to do:

byte[] bytes = Guid.Parse(guidValue);
codeStream.EmitLdc(BinaryPrimitives.ReadInt32LittleEndian(bytes));
codeStream.EmitLdc(BinaryPrimitives.ReadInt16LittleEndian(bytes.Slice(4)));
codeStream.EmitLdc(BinaryPrimitives.ReadInt16LittleEndian(bytes.Slice(6)));
for (int i = 8; i < 16; i++)
    codeStream.EmitLdc(bytes[i]);

@kant2002
Copy link
Contributor Author

@jkotas ready for you

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for all you hard work on this!

@jkotas jkotas merged commit 4ab2b2b into dotnet:feature/NativeAOT Apr 21, 2021
@kant2002 kant2002 deleted the kant/comwrappers branch April 21, 2021 02:28
@kant2002
Copy link
Contributor Author

I do not believe my eyes that this is happens :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants