The question in short: How can I debug the code generated during a debugging session on the generating program? (see code below)
I am facing the following issue: I would like to debug into dynamically generated/compiled code from the application that generates it. I provided an oversimplified example to clarify it. This example doesn't need debugging! My real app generates many more lines and code that really justify debugging, believe me :-) I would like to know if there is a way to debug or put a breakpoint at HelloWorld
. Stepping into the InvokeMethod call doesn't work. Maybe a solution involves code modification at the call sites to the generated assembly.
I had a look at many questions already (Debug dynamically loaded assembly in Visual Studio .NET for example) but none was helpful in solving the problem (if solvable at all?)
I took code from http://www.csharpfriends.com/Articles/getArticle.aspx?articleID=118 as a base and fixed the obsoleted calls. Beside this I generated the assembly on-the-fly in memory and the calls are working well. I generated explicitly an assembly with Debug information, what gives me hope: why would there be the option if debugging is not possible?
using System;
using System.Text;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
namespace DynamicAssembly
{
class CreateCompileExecute
{
[STAThread]
static void Main(string[] args)
{
// Creates a text file to store the new class
StringBuilder builder = new StringBuilder();
builder.AppendLine("using System;");
builder.AppendLine("namespace CSharpFriendsRocks");
builder.AppendLine("{");
builder.AppendLine("class CSharpFriends");
builder.AppendLine("{");
builder.AppendLine("public CSharpFriends() {" +
" Console.WriteLine(\"The CSharpFriends type is constructed\"); }");
builder.AppendLine("public void HelloWorld() {" +
" Console.WriteLine(\"Hello World - CSharpFriends.Com Rocks.\"); }");
builder.AppendLine("}");
builder.AppendLine("}");
// Create the C# compiler
CSharpCodeProvider csCompiler = new CSharpCodeProvider();
// input params for the compiler
CompilerParameters compilerParams = new CompilerParameters();
compilerParams.OutputAssembly = "CSharpFriends.dll";
compilerParams.GenerateInMemory = true;
compilerParams.IncludeDebugInformation = true;
compilerParams.ReferencedAssemblies.Add("system.dll");
compilerParams.GenerateExecutable = false; // generate the DLL
// Run the compiler and build the assembly
CompilerResults results = csCompiler.CompileAssemblyFromSource(
compilerParams, builder.ToString());
// Load the generated assembly into the ApplicationDomain
Assembly asm = results.CompiledAssembly;
Type t = asm.GetType("CSharpFriendsRocks.CSharpFriends");
// BindingFlags enumeration specifies flags that control binding and
// the way in which the search for members and types is conducted by reflection.
// The following specifies the Access Control of the bound type
BindingFlags bflags = BindingFlags.DeclaredOnly | BindingFlags.Public
| BindingFlags.NonPublic | BindingFlags.Instance;
// Construct an instance of the type and invoke the member method
Object obj = t.InvokeMember("HelloWorld", bflags |
BindingFlags.CreateInstance, null, null, null);
// Call the method
t.InvokeMember("HelloWorld", bflags | BindingFlags.InvokeMethod,
null, obj, null);
}
}
}
I finally found a way to workaround it after discovering that my question was a duplicate of How to debug/break in codedom compiled code, which was not obvious for me to find. bbmud gives a very good hint in there to get the debugger working correctly, but doesn't tell how to get into the code. I add a reference to some assembly containing an interface that I want to implement in the scripts:
Now when I consider
CSharpFriends
being an implementation ofIPlugin
, I can get the interface by casting theobj
above:Then debugging calls to interface methods or properties is as easy as usual! The trick of adding
inside the script code also works well but it needs change to the script. As the code inside the application always needs to know what kind of methods are inside the script according to some mechanism (reflexion with attributes or interfaces), using an interface known by both is a very acceptable solution for me.
I hope it helps somebody else.
Visual Studio 2010 handles this gently in the debugger. I was surprised of it after I upgraded. I hope it helps.