|
I have a project with large client base deploying .NET 3.5 WPF project on their machines, since there are some rules that keep changing we are considering to save the rules in database(which will be synced regularly with host app) and then the assembly will be built in runtime on client's machine using either COdeDOMProvider.CompileAssemblyFromSource() or CsharpCodeProvider.CompileAssemblyFromSource() on start of the application and then invoke the required methods
What are the limitations, security concerns of building the assembly on client's machines?
Are there any differences in using CodeDOmProvider vs CSharpCodeProvider
|
|
|
|
|
Hi.
About your limitation and security concerns question, you have for sure to consider you're distributing source code to your clients - never a good idea. Maybe better to opt for distributing updated already-compiled assemblies to your clients, for example by using Click-Once deployment/update. I would prefer this option also because it allows you to pre-test binaries (instead of source), eventually obfuscate your MSIL, avoid the compilation step on the client (one point of failure less...).
About the code provider, I would use certainly CSharpCodeProvider in targeting C# code generation.
Regards, AV
|
|
|
|
|
I found this article very intriguing. I knew that it could be done, but I am glad that you wrote and article showing how.
Shane
|
|
|
|
|
|
Hi,
Can you please let me know how I can use this code into a dll generating app?
I mean I'll take input a string, encrypt it using my encryption function and store the encrypted string in a dll.
I want the dll (in a specific location) as output [and nothing else] so that I can use this dll in future.
Appreciate your help.
RG
|
|
|
|
|
I am writing a fairly simple app as a school project, a Ebook Maker and was wondering if using CODEDom would be the correct method to create the standalone 'Ebooks'. The Maker app is written in C# and takes some files of the users choosing to be compiled into a executable, which would act as a viewer, to then be distributed. Would I be able to use codedom to form these executable files and would it be possible for them to execute on a system without .NET?
|
|
|
|
|
Absolutely not.
A .NET executable actually contains MSIL code that needs to be run inside the CLR environment.
Before writing a line of C# code, I suggest you to read carefully an introductory book on the .NET Framework architecture. You simply cannot develop on .NET without this basic concepts in your mind.
AV
|
|
|
|
|
There are third party apps that do allow you to compile dotnet code to native executable code, by statically linking in the dot net methods that were used.
Shane
|
|
|
|
|
Nice article, I have a question irrelevant to dynamic code generation though. Let's say we have a module like Code1.cs that contains a class within, we got another module like Code2.cs that uses the class within Code1.cs. How can we find out the sequence of compilation of these two modules programmatically?
Thanks,
Arash
Arash Sabet
Computer Engineer
E-mail: arash.afifi@gmail.com
|
|
|
|
|
Well, to be honest I think the only way is to parse in some way each source code to find out where it "uses" the classes defined elsewhere. This is easy to say, not so easy to do...
In fact, if you had two assemblies, each with its own metadata, it was simple to understand external references, by inspecting their metadata via Reflection. But in this case you just have source codes.
Be aware of the fact that, if (as I understand) each source is targeted to be compiled in a different assembly, you not only will need to compile and produce Code1.dll before Code2.ddl, but you will also need to add a reference to Code1.dll while programmatically compiling Code2.dll.
|
|
|
|
|
think u first!
today i made a test ,but i find the same code didn't work the same.
when i do it in a winform ,it's ok.
but when i do it in a webForm,it gives message like "can't find "customer.dll"
the code:
public Assembly CreateAssembly(string strRealSourceCode)
{
//Create an instance whichever code provider that is needed
CodeDomProvider codeProvider = null;
codeProvider = new CSharpCodeProvider();
//create the language specific code compiler
ICodeCompiler compiler = codeProvider.CreateCompiler();
//add compiler parameters
CompilerParameters compilerParams = new CompilerParameters();
compilerParams.CompilerOptions = "/target:library"; // you can add /optimize
compilerParams.GenerateExecutable = false;
compilerParams.GenerateInMemory = true;
compilerParams.IncludeDebugInformation = false;
// add some basic references
compilerParams.ReferencedAssemblies.Add( "mscorlib.dll");
compilerParams.ReferencedAssemblies.Add( "System.dll");
compilerParams.ReferencedAssemblies.Add( "System.Data.dll" );
compilerParams.ReferencedAssemblies.Add( "System.Drawing.dll" );
compilerParams.ReferencedAssemblies.Add( "System.Xml.dll" );
compilerParams.ReferencedAssemblies.Add( "customer.dll" );
//actually compile the code
CompilerResults results = compiler.CompileAssemblyFromSource(
compilerParams,
strRealSourceCode );
string e = "" ;
//Do we have any compiler errors
if (results.Errors.Count > 0)
{
foreach (CompilerError error in results.Errors)
{
e = e + error.ErrorText;
}
throw new Exception(e);
}
//get a hold of the actual assembly that was generated
Assembly generatedAssembly = results.CompiledAssembly;
return generatedAssembly;
}
-- modified at 6:49 Friday 8th December, 2006 ms.ReferencedAssemblies.Add( "System.Data.dll" );
compilerParams.ReferencedAssemblies.Add( "System.Drawing.dll" );
compilerParams.ReferencedAssemblies.Add( "System.Xml.dll" );
compilerParams.ReferencedAssemblies.Add( "CC.dll" );
compilerParams.ReferencedAssemblies.Add( "System.Data1.dll" );
compilerParams.ReferencedAssemblies.Add( "PDT.Persist.dll" );
//actually compile the code
CompilerResults results = compiler.CompileAssemblyFromSource(
compilerParams,
strRealSourceCode );
string e = "" ;
//Do we have any compiler errors
if (results.Errors.Count > 0)
{
foreach (CompilerError error in results.Errors)
{
e = e + error.ErrorText;
}
throw new Exception(e);
}
//get a hold of the actual assembly that was generated
Assembly generatedAssembly = results.CompiledAssembly;
return generatedAssembly;
}
|
|
|
|
|
Not easy to reply to you looking at this code: I can't see "customer.dll " in it...
Where customer.dll is located? In the bin folder, in the GAC?
AV
|
|
|
|
|
thank you for your answer
in webform the "customer.dll" is in bin folder.
but in winform it's in "bin\Debug".
and in my test ,i do add the code "ReferencedAssemblies.Add("customer.dll");";
|
|
|
|
|
I can't actually test your code (basically bacause the post is not complete). But I quickly tried to use mine in a WebForm application, and with minor modifications it worked.
Try it by yourself:
- create a WebForm VB.NET app (I used VS.NET 2003)
- in a brand new WebForm1, put this in the Form_Load in the code-behind:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim Expression As String = "3+7*4"
Response.Write("Result: " & ParseExpr.ProcessExpression(Expression))
End Sub
- in the same code-behind, just after "End Class", paste this:
Public Class ParseExpr
Public Shared Function ProcessExpression(ByVal _
Expression As String) As Single
Dim VBcp As New VBCodeProvider
Dim cg As ICodeGenerator = VBcp.CreateGenerator()
Dim AssemblySourceCode As New StringBuilder
Dim AssemblyFile As String = "MyClass.dll"
' Generating the assembly source file
Dim tw As TextWriter
tw = New StringWriter(AssemblySourceCode)
GenerateEvalMethod(tw, Expression, cg)
tw.Close()
' Compiling the source file to an assembly DLL (IL code)
Dim cc As ICodeCompiler = VBcp.CreateCompiler()
Dim cpar As New CompilerParameters
cpar.ReferencedAssemblies.Add("System.dll")
cpar.GenerateExecutable = False ' result is a .DLL
cpar.GenerateInMemory = True
Dim compRes As CompilerResults
compRes = cc.CompileAssemblyFromSource(cpar, AssemblySourceCode.ToString())
' Invoking a method of the newly-created assembly
Dim ass As [Assembly]
ass = compRes.CompiledAssembly
Dim ty As Type = ass.GetType("MyNamespace.MyClass")
Dim mi As MethodInfo = ty.GetMethod("MyMethod")
Dim Result As Single = CType(mi.Invoke(Nothing, Nothing), Single)
Return Result
End Function
Private Shared Sub GenerateEvalMethod(ByVal tw As _
TextWriter, ByVal Expression As String, _
ByVal CodeGen As ICodeGenerator)
'> Namespace MyNamespace
Dim ns As New CodeNamespace("MyNamespace")
'> Public Class MyClass
Dim ty As New CodeTypeDeclaration("MyClass")
ty.IsClass = True
ty.TypeAttributes = TypeAttributes.Public
ns.Types.Add(ty)
Dim mm As New CodeMemberMethod
'> ' Expression:...
mm.Comments.Add(New _
CodeCommentStatement([String].Format("Expression:{0}", _
Expression)))
'> Public Shared Function MyMethod() As Single
mm.Name = "MyMethod"
mm.ReturnType = New CodeTypeReference("System.Single")
mm.Attributes = MemberAttributes.Public Or MemberAttributes.Static
'> Return ...
mm.Statements.Add(New _
CodeMethodReturnStatement(New CodeSnippetExpression(Expression)))
ty.Members.Add(mm)
CodeGen.GenerateCodeFromNamespace(ns, tw, Nothing)
End Sub
End Class
- in the same code-behind, add on top:
Imports System
Imports System.Collections
Imports System.CodeDom
Imports System.CodeDom.Compiler
Imports System.IO
Imports System.Reflection
Imports Microsoft.CSharp
Imports Microsoft.VisualBasic
Imports System.Text
- then run the web application, and you'll obtain a result of 31 (=3+7*4).
The modifications I did on the original code:
- I moved the ParseExpr.Main code conceptually in the Form_Load
- I made Public the ParseExpr.ProcessExpression method to be accessed externally
- I avoided the use of a physical source file (ASP.NET has troubles in accessing file system directly without proper permissions), putting the source code only in memory (I modified AssemblySourceFile in AssemblySourceCode and CompileAssemblyFromFile in CompileAssemblyFromSource)
- I forced the assembly generation in memory (avoiding the physical .DLL at all and putting GenerateInMemory=True).
This my experimentation proofs that WebForm applications are not "allergic" to "on-the-fly code generation". But, anyway, please consider it just as a proof of concept: it's really far from being a perfect slice of code....!
Hope this helps you.
AV
|
|
|
|
|
thank you sir.
it's very kind of for give me those replys.
i didn't i think made my question clearly.
what i wanna konow is can i call a method of a class in another dll in a webapplication.
for example, i replace "3+7*4" with "SomeMethods.Add(3+4) +5"
in this case :SomeMethods is a class in another dll "customer.dll".
SomeMethods defined as fellow:
public class SomeMethods()
{
public SomeMethods(){}
public static int Add(int a,int b)
{
return a + b ;
}
}
Best regards!
wlbkeats
|
|
|
|
|
As described in note #1 in the "Some notes" paragraph of the article, you can call any method you want, being sure:
1. to add a reference to the assembly DLL that implements that method;
2. to supply the full namespacename.classname.methodname.
Being the code "on-the-fly-generated", I'm quite convinced (but not sure) you have to put the assembly DLL you're referencing in the GAC (so, you'll have to provide for it a strong name and use GACutil).
AV
|
|
|
|
|
your idea is very good, and would it possible support adding parameter variebles ?
I mean if the expression is "a * 5 + b", not just "1 * 5 + 6), since in the real world, we wish to let the user to enter the expression with variebles.
If yes, would you please show me the code example for this ?
The example use case could be the user entered the above expression, then later the program get the value of a & b, and then the program call the method of the generated class to get the return value. For this example use case, would you please write some example code to show me the light ?
Thanks!
Do or do not, there is no try.
|
|
|
|
|
Sure, you can include support for parameters variables.
If the number/names of the variables you want to introduce is pre-defined, your job is quite simple; the key is to produce a parametrized method like this one:
<br />
Public Shared Function MyMethod(a As Single, b As Single) As Single<br />
Return 1 + 2 * 3 - a / b<br />
End Function<br />
and then to invoke it passing actual values (user-supplied) for the method parameters.
Supporting a number of variables not known in advance, with names not known in advance, is quite more complicated: you definitely need to implement an "expression parser" (for example, using the wonderful Spart library[^]), in order to detect variables names and distinguish them from other operands and operators. But, at that point, if your parser actually does a syntax recognition, it also could implement the final calculation, so the code generation in that case would be quite a non-sense.
AV
|
|
|
|
|
I'm working in a library that is capable of returning instances of classes that you can register dinamically, without recompiling the code, of course that class has an inherited implementation of an Interface (so you can at least have some methods and properties standar). But because this "intance generator" probably will be called a lot of time, I'm afraid about the lock issue, and the phisicall source code generation...
In other words, I need to generate the code, compile and invoke the method all in memory. Is this possible?
Just a yes o no will help me, I can continue my research, but if it can't be possible, I won't loose more time on it...
Thanks for all, very interesting article! .
|
|
|
|
|
|
Hi Alberto!
Although using CodeDom for evaluating expressions is a very flexible approach I think there might be a few problems with your implementation.
First, you can perform all code generation and compilation in memory (setting your CompilerParameter's "GenerateInMemory" to true), thus reducing the need for a dll file. The file itself is fine, but you give it a fixed name (=>unsafe, might overwrite an existing file) and from my experience (without explicitely trying out your code) I'd expect the function ProcessExpression() to fail after the first call because the .NET runtime loads the assembly "MyClass.dll" into the AppDomain of the calling application and can't overwrite the dll thereafter.
I stumbled across a similar pitfall in one of my projects so I thought I'd try to keep you from stumbling here
Secondly, you can also use ICodeCompiler.CompileAssemblyFromSource() to compile your code from a string, also removing the need for a file to write to.
That way the code would be faster, you wouldn't need to clean up after you and are not likely to run into access problems for your dll.
Regards,
Mav
|
|
|
|
|
I fully agree with you about the "generate in memory" option. I didn't use it just to give evidence to the generated MyClass.vb and MyClass.dll files; but of course this option has to be mentioned, so I'll update my article.
Thank you, Mav.
|
|
|
|
|
Fine, thanks!
Btw. could you experience the "can't overwrite MyClass.dll" on the second call?
In my project I had to go through lengths to load an assembly into a separate AppDomain and then have objects from this assembly interact with my other classes in the original AppDomain.
It's a shame you can't unload an assembly, just a complete AppDomain with all loaded assemblies
|
|
|
|
|
Yes, in case of physical DLL creation, there is of course a problem on the locked file. Probably, you can find a solution in this interesting discussion thread:
http://blogs.msdn.com/suzcook/archive/2003/07/08/57211.aspx
Cheers, AV
|
|
|
|
|
Thanks, the blog is a really good reference. If only I knew it when I needed it
<OFFTOPIC>
In my project I wanted to have plugins for coloring/formatting ListView entries according to varying criteria. So I defined an interface for this and implemented several "colorizers", but ran into various troubles when I added the possibility to edit and compile these colorizers from within my application. ListViewItem s, for example, are not MarshalByRefObject -derived and thus couldn't be easily transferred between the two AppDomains.
I ended up loading the colorizers into the same AppDomain and stuffing the editing and compilation into another application.
</OFFTOPIC>
|
|
|
|
|