Adding a TypeDefinition from another Assembly - c#

This one is driving me crazy.
AssemblyDefinition asm1 = AssemblyDefinition.ReadAssembly(example);
AssemblyDefinition asm2 = AssemblyDefinition.ReadAssembly(example2);
asm2.MainModule.Types.Add(asm1.MainModule.Types[0]);
Whenever I try to execute the above code I get this error 'Type already attached'
I decided to look this error at MonoCecil source and I found it throws this error because the Type's MainMoudle isn't asm2 MainModules. So I decided to Copy that Type to a new one.
TypeDefinition type2 = new TypeDefinition("", "type2", Mono.Cecil.TypeAttributes.Class);
foreach (MethodDefinition md in asm2.Methods )
{
type2.Methods.Add(md);
}
And then add this type to my assembly normally but this throws another error, 'Specified method is not supported.'.
Any thoughts why I am getting this error?
Edit: Just to add, the type I'm trying to add contains some methods which uses pointers. Might this be the problem? As far as I know mono supports that but not mixed mode.

I'm afraid there's no built in, easy way to do this.
When you read an assembly with Cecil, every piece of metadata is glued together by the Module the metadata is defined in. You can't simply take a method from a module, and add it into another one.
To achieve this, you need to clone the MethodDefinition into a MethodDefinition tied to the other module. Again, there's nothing built-in yet for this.
I suggest you have a look at IL-Repack, which is an open-source ILMerge clone. It does exactly that, it takes types from different modules, and clone them into another one.

Related

In-memory CSharpCompilation cannot resolve attributes

I am trying to run C# source generators in-memory using the following code snippet:
var syntaxTree = await SyntaxTreeFromRelativeFile("testdata/IMyInterface.cs");
var compilation = CSharpCompilation.Create("compilation", ImmutableArray.Create(syntaxTree), References);
var generator = new ProxyGenerator();
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGenerators(compilation);
References is set to the necessary sources to compile the code:
public static readonly ImmutableArray<MetadataReference> References = ImmutableArray.Create<MetadataReference>(
// System
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(GCSettings).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location),
// JetBrains
MetadataReference.CreateFromFile(typeof(UsedImplicitlyAttribute).Assembly.Location),
// some custom ones
);
While the generator runs just fine this way, it sadly doesn't have the desired affect, because the source generator relies on an attribute to know whether to generate source for the specified type. The exact source is something along the following:
[MyAttribute]
public interface IMyInterface { /* ... */ }
The source generator picks up the attribute correctly, but it gets resolved to an ExtendedErrorTypeSymbol with the result kind being NotAnAttributeType. However, the extended error type symbol also has a candidate symbol, which is the exact symbol I expect it to match.
This is surprising to me, because clearly the type is an attribute, and running the source generator as part of the normal compilation actually does generate all the right types. This seems to imply that there is something strange going on because of the in-memory nature of this run specifically.
As far as I can tell, my list of References covers everything that is needed to correctly realise that something is an attribute (mscorlib, System.Runtime, netstandard, and System.Core), though perhaps there is another MetadataReference missing?
I did find this GitHub issue which seems to describe a very similar, if not the same problem.
I'd love to know if I did something wrong here, if there are other references I am missing, or whether I am missing something completely else altogether.
By decompiling the source and stepping through how the attribute become an ExtendedErrorTypeSymbol, I found out that alongside the resultKind and candidate type, you could also find a DiagnosticBag. In this bag, the actual problem was shown:
error CS0012: The type 'Attribute' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
I was under the impression that my code added that correctly, but using this error I was luckily able to further my search, and ran into this Stackoverflow question. This seems to imply that for some reason (I am not 100% sure why), the following code will not actually add the right reference to System.Runtime:
MetadataReference.CreateFromFile(typeof(GCSettings).Assembly.Location)
Instead, I followed the example of the answer linked above, and changed the code to:
private static readonly string dotNetAssemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
public static readonly ImmutableArray<MetadataReference> References = ImmutableArray.Create<MetadataReference>(
// .NET assemblies are finicky and need to be loaded in a special way.
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "mscorlib.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.Core.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.Private.CoreLib.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.Runtime.dll")),
// more references, loaded as before
);
For some reason, this made all the difference. I had to also add the reference to System.Private.CoreLib.dll, but now that I knew where to find the diagnostics bag with additional information about what is actually wrong, this was an easy fix.

C# - Make a new class not part of System.Object

I have a huge code base and I recently made a change where I changed the type of a parameter from String to a custom class. On the next compile I got all the areas where the impact was, but areas where the input type was of type Object failed. for e.g.
String str = "32"
int i = Convert.ToInt32(str)
Now I have changed String to a new custom type lets say MyCustomClass I would now want following code to fail on next compile
MyCustomClass str = new MyCustomClass("32")
int i = Convert.ToInt32(str)
but it won't as Convert.ToInt32 also accepts type Object. Is there some way I can make a change in MyCustomClass that it's not considered Object anymore.
Please note: Convert.ToInt32 is only used for sample I have many more such functions, so please focus your suggestion/answer to question asked.
Override ToString() and IConvertible
You said in the comments that your intentions are to find places where your object, which had previously been treated as a string, and are now being treated as an object.
In these situations typically, the third-party code would call .ToString() on your object to get something which it can use.
So, Convert.ToInt32(str) is equivalent to Convert.ToInt32(str.ToString()).
If you implement ToString() and IConvertible to return whatever your old version of str looked like then it should continue to work in the same way as the old version.
Probably.
Sorry I know that is not the 100% perfect compile time answer you were looking for, but I think you also know very well that your MyCustomClass will always be considered object.
Possible compile time answer:
Write a tool which uses reflection to iterate over every class/struct/interface in every system/third-party DLL.
Output a load of CS files which contain all these same classes, but just throw NotImplementedException.
(T4 could help you do this)
Compile these classes into dummy.dll
Your .csproj now references only this one dummy.dll, instead of the real dlls.
Your project should compile fine against the dummy dll.
Look at your dummy.cs files and delete any use of object.
Re-compile... and suddenly you get a load of compile time errors showing you anywhere you are using an object.
Impliment an implicit cast from MyCustomClass to String.
public static implicit operator string(MyCustomClass str)
{
return "Legacy respresentation of str";
}
This allows the complier the choice of choosing ToInt32(Object) or ToInt32(String), and I bet it favours the later.
This way all your existing function calls will remain the same so you wont have to be concerned about third party implentation details.
(Sorry, I am not at a computer right now so I can`t test that my assumtion is correct. If you do test this, be sure to consider extension methods, as they can affect the conpilers desision making in unexpected ways)

Attaching an existing method to a dynamic assembly instead of generating IL for it

I am new to the Reflection.Emit API and have generated a bare minimum assembly with an entry point that returns an exit code based on a few simple I/O checks. The reason for creating an external assembly is that a piece of code needs to run outside the current process space and will be transient itself. It will eventually be deleted by the creating app.
Having said that, the code to be generated is quite complex and I would rather avoid creating a separate project for a transient assembly. I don't know IL enough to use the ILGenerator for this complex a method.
I have also read this SO question about using existing methods to generate IL and it does not seem possible short of parsing IL. I don't know of a way to take an existing method, detach it from the current context and add it to the new dynamic type. If such a way exists, that would be an acceptable answer to this post.
My final though is to have the generated assembly reference the generating assembly as a library. even though both assemblies are WinForms apps, is there a downside to referencing them as a library?
Finally solved it using Expression Trees instead of having to emit raw IL.
var queue = new Queue<Expression>();
var arguments = Expression.Parameter(typeof(string []), "args");
queue.Enqueue(Expression.Call(typeof(Console).GetMethod("WriteLine", new Type [] { })));
var block = Expression.Block(queue);
var lambda = Expression.Lambda<Func<string [], int>>(block, new ParameterExpression [] { arguments });
lambda.CompileToMethod(builderMethod);
// builderMethod is a MethodBuilder instance created earlier.
This is very powerful indeed and definitely suited for situations where micro-perf is not required using ILGenerator.

Adding a lambda expression has resulted in a strange error when attempting to compile

So currently there is a piece of code which looks like this...
string name = GetValues(sequenceOfCodes, 0, IDtoMatch, 1)[0];
I just updated the following line to be
string name = sequenceOfCodes
.Select(x => x[0])
.Where(x => x == IDtoMatch)
.FirstOrDefault();
Which should hopefully return the same thing.
sequenceOfCodes is a List<List<String>> and the IDtoMatch is also a string.
So hopefully this all seems fine.
However when I go to compile I get an odd error
The type 'System.Windows.Forms.ComboBox' is defined in an assembly
that is not referenced.
You must add a reference to assembly 'System.Windows.Forms, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089'
And when I take my newly added code away it compiles fine and runs... So why is it just because I have added a lambda expression does it think that I it needs a reference to System.Windows.Forms.ComboBox?
Just to state that this is a console application. Not a winforms application.
-----------UPDATE----------
Ok, So I have found that a one of the references does reference the System.Windows.Forms, which I is really disappointing as this is core code and should not have dependencies like this :(
However I still wonder why the error did not appear before until after I added my line of code.
To confirm, If I remove my code I can close VS down and restart and rebuild and all is fine.
If I add my line of code and close down and restart, etc. The error will reappear on rebuild.
Very strange error to me.
Thanks guys for all your help
You mention that one of the other projects does reference windows forms. My guess is that this project also declares some extension methods that are in scope (given your using directives), and which the compiler needs to explore for overload resolution - presumably of the Where, Select or FirstOrDefault methods; meaning: it can't decide that the best overload of these is the System.Linq.Enumerable one until it has compared it to the other candidates, and it can't do that without being able to understand the types used in the competing method signatures.
Or in other words: is there a Select, Where or FirstOrDefault custom extension method that mentions ComboBox ?

The as operator rejects the object even though the object appears to be of the correct type in the debugger

The following code throws an exception. If there is no easy answer or stuff to check, I'll try to produce something that reproduces the error (though I don't know where to upload it).
public static XMLobj Load(string FileName)
{
if (File.Exists(FileName) == false)
{
return null;
}
IRDnet.XMLobj def;
XmlSerializer xmlser = new XmlSerializer(typeof(IRDnet.XMLobj));
System.IO.Stream stream = File.OpenRead(FileName);
object o = xmlser.Deserialize(stream);
// o appears to be of correct type in the quick watch.
IRDnet.XMLobj def2 = o as IRDnet.XMLobj;
// def2 is "undefined" (as operator rejected o)
def = (IRDnet.XMLobj)o;
// Throws InvalidCastException with no InnerException.
stream.Close();
return def;
}
The strange thing is that "o" appears to be of correct type if I break just before the exception is thrown:
o {IRDnet.XMLobj} System.Object
And the object casts just fine in the quickwatch window. Values are easily inspected.
It is executed from the same project that it is part of. So, no loading contexts.
FYI: the static method is part of the XMLobj class if that's relevant.
Is there some other criteria for a successful cast that I'm not aware of? Any code that gets implicitly executed?
I've checked that reflector produces the equivalent code to make sure that nothing was lost in compiler optimization.
Any clues, people? I'm stumped. I even hoped that just writing this question would make me think twice on something completely obvious.
Chances are this is a versioning issue. That is, the deserialized XMLobj is a different version to the one you've compiled with. Check the fully qualified name of each type.
The .NET Serializer produces {Assembly}.Serializer.dll assemblies to speed up XML serialization/deserialization. Try to delete every Assembly and compile from scratch.
If the assemblies does not match exactly an InvalidCast Exception is throw.
EDIT: Look in your debugger output to see what assemblies have been loaded.
UPDATE:
string tmp = o.GetType().AssemblyQualifiedName;
string tmp2 = typeof(XMLobj).AssemblyQualifiedName;
produces:
tmp "IRDnet.XMLobj, IRDnet, Version=1.0.3600.18887, Culture=neutral, PublicKeyToken=null" string
tmp2 "IRDnet.XMLobj, IRDnet, Version=1.0.3601.27699, Culture=neutral, PublicKeyToken=null" string
So there is definitely a legitimate type mismatch. I highly appreciate this help. I was completely stuck. Now I have a lead. Now to find how the old type survived the rebuilding.
Maybe the XML file has something to say about it....
Or maybe it is the secretive Serialize-assembly somewhere that was mentioned....
I'm not stuck. I'm still digging, but I thought I'd inform of this new development. If anybody has more tips about interrogating a type about its declaration, then please chip in. Otherwise, thank you all!
ANSWER
The first thing to do if you ever get into this exception for seeming identical types, is to check that the two types are actually the same.
System.Type t1 = typeof(XMLobj);
System.Type t2 = o.GetType();
And then check them in the debugger. Take especially note of the "AssemblyQualifiedName". My two types turned out to be different versions of the same class.
Then do pay careful attention to the output window in Visual Studio as you are single stepping through. It will tell you when which assemblies are loaded. In my example, the culprit came in clear text in the debug output just as I stepped over the line
XmlSerializer xmlser = new XmlSerializer(typeof(IRDnet.XMLobj));
There, the debugger revealed that another, older, version of my assembly was loaded from a location that I wasn't aware of.
I neglected to tell the guys here that I debug using an external exe that loads my assembly. It turns out that the CLR, I assume on behalf of the XmlSerializer class constructor to resolve the "typeof", looks in the exe folder and its subfolders (which is what CLR does). And, lo and behold, deep down there there was a bastard of the original version of the IRDnet.dll file that I was fixing.
I still do not see how the two "typeof" statements produce different results: One results in the object type currently within context, whereas the other causes the CLR do go out looking for it in the execution folder. One possible theory is that the typeof statement is somehow lazily evalutated. I wouldn't know.

Categories