-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Refactor ComponentActivator #37784
Refactor ComponentActivator #37784
Changes from 3 commits
3b9480a
9d61485
3f51570
830cfe6
e4da064
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,9 @@ | |
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Reflection; | ||
using System.Runtime.InteropServices; | ||
using System.Runtime.Loader; | ||
|
||
namespace Internal.Runtime.InteropServices | ||
{ | ||
|
@@ -39,6 +39,31 @@ private static string MarshalToString(IntPtr arg, string argName) | |
return result; | ||
} | ||
|
||
private static (Type? delegateType, string? delegateTypeName) GetDelegateTypeArgs(IntPtr delegateTypeNative) | ||
{ | ||
// Determine the signature of the type. There are 3 possibilities: | ||
// * No delegate type was supplied - use the default (i.e. ComponentEntryPoint). | ||
// * A sentinel value was supplied - the function is marked UnmanagedCallersOnly. This means | ||
// a function pointer can be returned without creating a delegate. | ||
// * A delegate type was supplied - Load the type and create a delegate for that method. | ||
string? delegateTypeName = null; | ||
Type? delegateType = null; | ||
if (delegateTypeNative == IntPtr.Zero) | ||
{ | ||
delegateType = typeof(ComponentEntryPoint); | ||
} | ||
else if (delegateTypeNative == (IntPtr)(-1)) | ||
{ | ||
// Leave both null. | ||
} | ||
else | ||
{ | ||
delegateTypeName = MarshalToString(delegateTypeNative, nameof(delegateTypeNative)); | ||
} | ||
|
||
return (delegateType, delegateTypeName); | ||
} | ||
|
||
/// <summary> | ||
/// Native hosting entry point for creating a native delegate | ||
/// </summary> | ||
|
@@ -50,45 +75,19 @@ private static string MarshalToString(IntPtr arg, string argName) | |
/// <param name="functionHandle">Pointer where to store the function pointer result</param> | ||
[UnmanagedCallersOnly] | ||
public static unsafe int LoadAssemblyAndGetFunctionPointer(IntPtr assemblyPathNative, | ||
IntPtr typeNameNative, | ||
IntPtr methodNameNative, | ||
IntPtr delegateTypeNative, | ||
IntPtr reserved, | ||
IntPtr functionHandle) | ||
IntPtr typeNameNative, | ||
IntPtr methodNameNative, | ||
IntPtr delegateTypeNative, | ||
IntPtr reserved, | ||
IntPtr functionHandle) | ||
{ | ||
try | ||
{ | ||
// Load the assembly and create a resolver callback for types. | ||
// Validate all parameters first. | ||
string assemblyPath = MarshalToString(assemblyPathNative, nameof(assemblyPathNative)); | ||
IsolatedComponentLoadContext alc = GetIsolatedComponentLoadContext(assemblyPath); | ||
Func<AssemblyName, Assembly> resolver = name => alc.LoadFromAssemblyName(name); | ||
|
||
// Get the requested type. | ||
string typeName = MarshalToString(typeNameNative, nameof(typeNameNative)); | ||
Type type = Type.GetType(typeName, resolver, null, throwOnError: true)!; | ||
|
||
// Get the method name on the type. | ||
string methodName = MarshalToString(methodNameNative, nameof(methodNameNative)); | ||
|
||
// Determine the signature of the type. There are 3 possibilities: | ||
// * No delegate type was supplied - use the default (i.e. ComponentEntryPoint). | ||
// * A sentinel value was supplied - the function is marked UnmanagedCallersOnly. This means | ||
// a function pointer can be returned without creating a delegate. | ||
// * A delegate type was supplied - Load the type and create a delegate for that method. | ||
Type? delegateType; | ||
if (delegateTypeNative == IntPtr.Zero) | ||
{ | ||
delegateType = typeof(ComponentEntryPoint); | ||
} | ||
else if (delegateTypeNative == (IntPtr)(-1)) | ||
{ | ||
delegateType = null; | ||
} | ||
else | ||
{ | ||
string delegateTypeName = MarshalToString(delegateTypeNative, nameof(delegateTypeNative)); | ||
delegateType = Type.GetType(delegateTypeName, resolver, null, throwOnError: true)!; | ||
} | ||
(Type? delegateType, string? delegateTypeName) = GetDelegateTypeArgs(delegateTypeNative); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the logic is harder to follow now that this is split over 3 different functions. Was it really necessary to split it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. #37696 (comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can I do not see the point by doing little bit of upfront work in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is to validate the parameters before doing anything meaningful like creating an ALC. Can't the Marshal call throw an exception if given a bad pointer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, but it's not really a parameter validation - it will try to read from the pointer, if that throws an AV it will turn into an exception. I understand the goal here (to validate everything up-front), so I'm basically fine with this, but if @jkotas or others would prefer to not use this, I would also be OK if we duplicate the parameter validation (basically inline the method here as it was before, and then make a copy into the second one we're introducing) - or I would not mind if we simply postpone the marshal call to the point where we're going to actually get the delegate type. |
||
|
||
if (reserved != IntPtr.Zero) | ||
{ | ||
|
@@ -100,35 +99,11 @@ public static unsafe int LoadAssemblyAndGetFunctionPointer(IntPtr assemblyPathNa | |
throw new ArgumentNullException(nameof(functionHandle)); | ||
} | ||
|
||
IntPtr functionPtr; | ||
if (delegateType == null) | ||
{ | ||
// Match search semantics of the CreateDelegate() function below. | ||
BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; | ||
MethodInfo? methodInfo = type.GetMethod(methodName, bindingFlags); | ||
if (methodInfo == null) | ||
throw new MissingMethodException(typeName, methodName); | ||
// Set up the AssemblyLoadContext for this delegate. | ||
AssemblyLoadContext alc = InternalLoadAssembly(assemblyPath); | ||
|
||
// Verify the function is properly marked. | ||
if (null == methodInfo.GetCustomAttribute<UnmanagedCallersOnlyAttribute>()) | ||
throw new InvalidOperationException(SR.InvalidOperation_FunctionMissingUnmanagedCallersOnly); | ||
|
||
functionPtr = methodInfo.MethodHandle.GetFunctionPointer(); | ||
} | ||
else | ||
{ | ||
Delegate d = Delegate.CreateDelegate(delegateType, type, methodName)!; | ||
|
||
functionPtr = Marshal.GetFunctionPointerForDelegate(d); | ||
|
||
lock (s_delegates) | ||
{ | ||
// Keep a reference to the delegate to prevent it from being garbage collected | ||
s_delegates[functionPtr] = d; | ||
} | ||
} | ||
|
||
*(IntPtr*)functionHandle = functionPtr; | ||
// Create the function pointer. | ||
*(IntPtr*)functionHandle = InternalGetFunctionPointer(alc, typeName, methodName, delegateType, delegateTypeName); | ||
} | ||
catch (Exception e) | ||
{ | ||
|
@@ -153,5 +128,61 @@ private static IsolatedComponentLoadContext GetIsolatedComponentLoadContext(stri | |
|
||
return alc; | ||
} | ||
|
||
private static AssemblyLoadContext InternalLoadAssembly(string assemblyPath) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the point of wrapping GetIsolatedComponentLoadContext by another method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #37696 (comment). It's easy to remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would also prefer to not have this method (if we ever introduce a native hosting way to create an ALC, it would have to take the parameter as IntPtr anyway, so I don't really see the usefulness of this wrapper). |
||
{ | ||
|
||
IsolatedComponentLoadContext alc = GetIsolatedComponentLoadContext(assemblyPath); | ||
return alc; | ||
} | ||
|
||
private static IntPtr InternalGetFunctionPointer(AssemblyLoadContext alc, | ||
string typeName, | ||
string methodName, | ||
Type? delegateType, | ||
string? delegateTypeName) | ||
{ | ||
// Create a resolver callback for types. | ||
Func<AssemblyName, Assembly> resolver = name => alc.LoadFromAssemblyName(name); | ||
|
||
// Get the requested delegateType if a name was given. | ||
if (delegateTypeName != null) | ||
{ | ||
delegateType = Type.GetType(delegateTypeName, resolver, null, throwOnError: true)!; | ||
} | ||
|
||
// Get the requested type. | ||
Type type = Type.GetType(typeName, resolver, null, throwOnError: true)!; | ||
|
||
IntPtr functionPtr; | ||
if (delegateType == null) | ||
{ | ||
// Match search semantics of the CreateDelegate() function below. | ||
BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; | ||
MethodInfo? methodInfo = type.GetMethod(methodName, bindingFlags); | ||
if (methodInfo == null) | ||
throw new MissingMethodException(typeName, methodName); | ||
|
||
// Verify the function is properly marked. | ||
if (null == methodInfo.GetCustomAttribute<UnmanagedCallersOnlyAttribute>()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be changed to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want to make this change in this PR. The equivalent code is |
||
throw new InvalidOperationException(SR.InvalidOperation_FunctionMissingUnmanagedCallersOnly); | ||
|
||
functionPtr = methodInfo.MethodHandle.GetFunctionPointer(); | ||
} | ||
else | ||
{ | ||
Delegate d = Delegate.CreateDelegate(delegateType, type, methodName)!; | ||
|
||
functionPtr = Marshal.GetFunctionPointerForDelegate(d); | ||
|
||
lock (s_delegates) | ||
{ | ||
// Keep a reference to the delegate to prevent it from being garbage collected | ||
s_delegates[functionPtr] = d; | ||
} | ||
} | ||
|
||
return functionPtr; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two checks for null in MarshalToString is redundant. You can delete one of them while you are on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was probably added so the compiler knows we can't return null. Is adding '!' preferred over this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!
is preferred over adding unreachable code.