-
-
Notifications
You must be signed in to change notification settings - Fork 646
Description
In the "spirit" of #524 and #646 and probably others…
Under .NET, Is there a "correct" way to use DefaultReflectionImporter
and have the output assembly reference System.Runtime
and not System.Private.CoreLib
?
Code of interest:
delegateDef.BaseType = module.ImportReference (typeof (MulticastDelegate));
Example: cecil-import-reflection.zip
Consider the cecil-import-reflection
example:
% dotnet build
% ikdasm -assemblyref bin/Debug/net7.0/cecil-import-reflection.dll | grep Name=
Name=System.Runtime
Name=System.Runtime.Loader
Name=Mono.Cecil
Name=System.Console
Note that the default dotnet build
output references System.Runtime
. System.Private.CoreLib
doesn't make an appearance.
Let's run the example, which uses Cecil to read an input assembly, add a new delegate type to the assembly, and write it out:
% dotnet run bin/Debug/net7.0/cecil-import-reflection.dll out.dll
% ikdasm -assemblyref out.dll | grep Name=
Name=System.Runtime
Name=System.Runtime.Loader
Name=Mono.Cecil
Name=System.Console
Name=cecil-import-reflection
Name=System.Private.CoreLib
Note that System.Private.CoreLib
now exists as an assembly reference. (Also note that cecil-import-reflection
is an assembly reference! See "Question 2", below.)
Question 1: Is there a way to not have System.Private.CoreLib
added as an assembly reference? I tried playing around with a DefaultReflectionImporter
subclass and had no luck with that, for reasons I wasn't able to understand.
What I did have luck with was:
- Write the assembly to a stream/disk.
- Read (1) via
AssemblyDefinition.ReadAssembly()
- Modify
AssemblyDefinition.MainModule.AssemblyReferences
andAssemblyDefinition.MainModule.MemberReferences
so that.Scope
usesSystem.Runtime
.
Something like:
static void WriteKludge (AssemblyDefinition assemblyDef, string path, bool keepIntermediate)
{
var c = new MemoryStream ();
assemblyDef.Write (c);
c.Position = 0;
if (keepIntermediate) {
using var intermediate = File.Create (path + ".cecil");
c.WriteTo (intermediate);
c.Position = 0;
}
var rp = new ReaderParameters {
InMemory = true,
ReadSymbols = false,
ReadWrite = true,
};
var newAsm = AssemblyDefinition.ReadAssembly (c, rp);
var module = newAsm.MainModule;
var systemRuntimeRef = module.AssemblyReferences.FirstOrDefault (r => r.Name == "System.Runtime");
var privateCorelibRef = module.AssemblyReferences.FirstOrDefault (r => r.Name == "System.Private.CoreLib");
if (systemRuntimeRef == null && privateCorelibRef != null) {
throw new NotSupportedException ("Don't support assemblies which only reference System.Private.CoreLib and not System.Runtime.");
}
var selfRef = module.AssemblyReferences.FirstOrDefault (r => r.Name == newAsm.Name.Name);
foreach (var member in module.GetMemberReferences ()) {
if (member.DeclaringType.Scope == privateCorelibRef) {
member.DeclaringType.Scope = systemRuntimeRef;
continue;
}
if (member.DeclaringType.Scope == selfRef) {
member.DeclaringType.Scope = null;
continue;
}
}
foreach (var type in module.GetTypeReferences ()) {
if (type.Scope == privateCorelibRef) {
type.Scope = systemRuntimeRef;
continue;
}
if (type.Scope == selfRef) {
type.Scope = null;
continue;
}
}
module.AssemblyReferences.Remove (privateCorelibRef);
if (selfRef != null) {
module.AssemblyReferences.Remove (selfRef);
}
newAsm.Write (path);
}
Is this what I should be doing?
(Aside: I found that the member references & assembly references would change before vs. after AssemblyDefinition.Write()
, which implied to me that the collections are "incomplete" until everything is serialized at .Write()
.)
Question 2: The example also uses Reflection to load a type from the assembly and use that with Cecil. In this particular case, it's creating something equivalent to:
delegate void MyDelegate(MyType type);
where MyType
is resolved from the assembly being modified.
The result of this is that out.dll
now references cecil-import-reflection
, i.e. "itself":
% dotnet run bin/Debug/net7.0/cecil-import-reflection.dll cecil-import-reflection.dll
% ikdasm -assemblyref out.dll | grep Name=
Name=System.Runtime
Name=System.Runtime.Loader
Name=Mono.Cecil
Name=System.Console
Name=cecil-import-reflection
Name=System.Private.CoreLib
Note Name=cecil-import-reflection
!
This is mostly "just weird", and WriteKludge()
checks for this situation and removes the "self reference".