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

Why can't I copy assemblies like Example.Contracts.dll and CPlugin.Net.Attributes.dll to the plugin output directory? #27

Closed
MrDave1999 opened this issue Dec 6, 2023 · 1 comment
Labels
question Further information is requested

Comments

@MrDave1999
Copy link
Owner

This avoids copying the assemblies to the plugin output directory:

<ProjectReference Include="$(ProjectRootDir)/samples/Contracts/Example.Contracts.csproj">
<!-- This tells MSBuild not to copy Example.Contracts.dll to the plug-in output directory. -->
<Private>false</Private>
<!--
This setting has the same effect as <Private>false</Private> but works on package references
that the Example.Contracts project or one of its dependencies may include.
-->
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>

<ProjectReference Include="$(ProjectRootDir)/src/Attributes/CPlugin.Net.Attributes.csproj">
<!-- This tells MSBuild not to copy CPlugin.Net.Attributes.dll to the plug-in output directory. -->
<Private>false</Private>
</ProjectReference>

@MrDave1999 MrDave1999 added the question Further information is requested label Dec 6, 2023
@MrDave1999
Copy link
Owner Author

MrDave1999 commented Dec 6, 2023

1. What happens is that the host application already references these assemblies, so they will be copied to the host output directory.

See,

<ProjectReference Include="..\..\..\src\Core\CPlugin.Net.csproj" />
<ProjectReference Include="..\..\Contracts\Example.Contracts.csproj" />

<ProjectReference Include="..\..\..\src\Core\CPlugin.Net.csproj" />
<ProjectReference Include="..\..\Contracts\Example.Contracts.csproj" />

There is an indirect reference to CPlugin.Net.Attributes project because CPlugin.Net project already makes reference to it.

2. If you copy those assemblies to the plugin output directory as well, you will get unexpected behavior when running the host application.

What behavior?

FindSubtypesOf method will always return an empty enumerable and even if subtypes exist for a supertype.

Now the question is why does this happen?

When loading a plug-in, an instance of AssemblyLoadContext is created and in this context these assemblies are also loaded: Example.Contracts.dll and CPlugin.Net.Attributes.dll.

However, the host application is an assembly and so are its dependencies, so they are loaded in a default context (including assemblies as Example.Contracts.dll and CPlugin.Net.Attributes.dll).

Now let's look at this code:

private static IEnumerable<TSupertype> GetSubtypesOf<TSupertype>(IEnumerable<Assembly> assemblies)
where TSupertype : class
{
foreach (Assembly assembly in assemblies)
{
var pluginAttributes = assembly.GetCustomAttributes<PluginAttribute>();
foreach (PluginAttribute pluginAttribute in pluginAttributes)
{
Type implementationType = pluginAttribute.PluginType;
if (typeof(TSupertype).IsAssignableFrom(implementationType))
yield return (TSupertype)Activator.CreateInstance(implementationType);
}
}
}

This line is important:

var pluginAttributes = assembly.GetCustomAttributes<PluginAttribute>();

assembly contains an instance of type Assembly that represents the plug-in itself, which in fact was loaded in its own context.

GetCustomAttributes will always return an empty enumerable and this happens because the .NET runtime does not find the PluginAttribute type of the default context in the current assembly (the plugin as such) that is being evaluated.

That's right, the host application has the PluginAttribute type loaded in the default context but the plugin also has it in its custom context, so .NET runtime takes it as different types. This is not a bug, it is the behavior expected by the runtime, or at least I think so.

So, if you just load the assembly as CPlugin.Net.Attributes.dll in the default context and not in the custom context of your plugin, then there would be no conflict between the types, because the .NET runtime will only use the PluginAttribute type loaded in the default context.

3. The same problem occurs if the Example.Contracts.dll assembly is shared between the default and custom context.

This condition will always be false:

if (typeof(TSupertype).IsAssignableFrom(implementationType))
   yield return (TSupertype)Activator.CreateInstance(implementationType);

I will assume TSupertype is ICommand. This type is loaded both in the default context and in the custom context.
Now imagine that my plugin has a subtype called HelloCommand that implements ICommand.
That condition will be false because HelloCommand does not implement the ICommand that is loaded in the default context, but the one in the custom context. So the .NET runtime takes these two types as different, since they are loaded in different contexts.

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

No branches or pull requests

1 participant