C
C#10mo ago
temp0

✅ Sharing executing Assembly in Roslyn script

Howdy, this could be a very specific use case, but if anyone has any insights here I would appreciate it: I am hosting am embedded .net runtime in a c++ app using nethost.h and loading my assembly and getting fn pointers with load_assembly_and_get_function_pointer. It's invoking everything fine. In the function I'm calling on the .net side I am using Roslyn to run .csx scripts. In that .net lib I define an interface INpcEvent and in the .csx I create a class that implements that interface and returns a new instance. I want to be able to get a reference to that new instance via this code: var scriptOptions = ScriptOptions.Default.WithReferences(Assembly.GetExecutingAssembly()); var state = await CSharpScript.RunAsync<GlobalType.INpcEvent>(text, scriptOptions, globals: global, globalsType: typeof(GlobalType)); And I get the error Exception thrown: 'System.ArgumentException' in System.Private.CoreLib.dll: 'Cannot bind to the target method because its signature is not compatible with that of the delegate type.' - seems to be that although they're the same types it's not the same assembly reference so it can't be cast as the return value. I see that Roslyn is loading that dll in runtime with the WithReferences option. Is there a way to use what I have defined in memory as the caller for types to sync those up?
7 Replies
temp0
temp0OP10mo ago
Did some debugging and they were indeed two different assemblies in memory although pointed to the same module on disk... Maybe this could be part of the embedded runtime from nethost ? Just to "get things work" i used a workaround with reflection in the assembly but I'm pretty sure it's duplicating it for every instance I call the script uncached which is a no go. Anyone have any insight into sharing an assembly across Roslyn scripts or an alternative for script compiling? var scriptOptions = ScriptOptions.Default.WithReferences(MetadataReference.CreateFromFile($"{directory}/DotNetTypes.dll")); var state = await CSharpScript.RunAsync<object>(text, scriptOptions); NpcMap[npcName] = state.ReturnValue; var instance = NpcMap[npcName]; var createNpcEvent = instance.GetType()?.BaseType?.Assembly.ExportedTypes.FirstOrDefault(f => f.FullName == "EqFactory")?.GetMethod("CreateNpcEvent"); if (createNpcEvent != null) { var npcEvent = createNpcEvent.Invoke(instance, [initArgs?.Zone, initArgs?.EntityList, npcEventArgs.Npc, npcEventArgs.Mob, message ?? ""]); var methodInfo = instance.GetType().GetMethod(MethodMap[id]); if (methodInfo != null) { methodInfo.Invoke(instance, [npcEvent]); } }
reflectronic
reflectronic10mo ago
so, the problem is that the default logic used by the script engine to load dependent assemblies for the script is not friendly for using dependencies from a non-Default AssemblyLoadContext load_assembly_and_get_function_pointer loads components into isolated AssemblyLoadContext instances. so, the assembly you load using it (the one which defines INpcEvent) is not in AssemblyLoadContext.Default you use CSharpScript.RunAsync, which loads the generated script assembly into a fresh ALC that it creates. (so, the assembly defining INpcEvent is not in it). when you run the script, it needs to load that assembly (since the script uses that type). because that assembly is not in the script ALC or AssemblyLoadContext.Default, it falls back to some assembly loading logic provided by the script engine the script engine remembers the Location of the assembly you provided in WithReferences and uses that to load the assembly defining INpcEvent into the script ALC. that's why you end up with a duplicate--the assembly is loaded both into the component ALC created by the unmanaged hosting layer and into the script ALC created by Roslyn. and that's why the error says that the signature is incompatible the easiest fix here is telling the script engine to reuse your already loaded assembly instead of re-loading it into the wrong ALC. you can do this by writing:
var loader = new InteractiveAssemblyLoader();
loader.RegisterDependency(Assembly.GetExecutingAssembly()); // the same assembly passed to WithReferences
var loader = new InteractiveAssemblyLoader();
loader.RegisterDependency(Assembly.GetExecutingAssembly()); // the same assembly passed to WithReferences
and passing loader to CSharpScript.Create. then delete your workaround @temp0
temp0
temp0OP10mo ago
@reflectronic Worked like a charm, thank you! Was trying to run through RunAsync the whole time, didn't see the Create method. Much appreciated! How do I mark this answer as complete?
reflectronic
reflectronic10mo ago
$close
MODiX
MODiX10mo ago
Use the /close command to mark a forum thread as answered
reflectronic
reflectronic10mo ago
:)
temp0
temp0OP10mo ago
@reflectronic Wasn't sure if I should open another thread for this question but you seem to know your way around roslyn/ALCs... It seems loading up script object and running Compile() will load a lot of necessary assemblies and incur about 50mb overhead in memory, is there any design pattern for reclaiming memory other than running in a separate process? I have tried a custom ALC with .Unload() at the end but I think it's moreso all the libs that roslyn loads in that take up that memory
Want results from more Discord servers?
Add your server