Monday, March 29, 2010

Generating .Net 1.1 dll from .net 3.5

Recently, I was working on a project that had half its code developed using .Net 1.1 framework while the other half developed using .Net 3.5. Though the pieces worked independently, a few windows services written in .Net 1.1 required an assembly generated at runtime. Now this assembly was being generated from the web site (previously written in .Net 1.1 framework), which now was re-written using 3.5.

This was a problem, because the .Net 3.5 code could compile code at run time targeting either 2.0 or the 3.5 framework. Since the services written in 1.1 required an assembly to be compiled in 1.1, it could not load the assembly that was being generated.

A bit of research and the use of .Net Reflector tool, provided the solution.

Lets start with the code that will compile an assembly at runtime using code written in a text file targeting .Net 3.5 framework.


Dim Compiler As VBCodeProvider = Nothing
Dim pvdr As CodeDomProvider
Dim CompilerParams As CompilerParameters
Dim CompileResults As CompilerResults

Public Function CompileCode() As String
Dim strReturn As String = ""
Try
CompilerParams = New CompilerParameters
With CompilerParams
.TreatWarningsAsErrors = False
.WarningLevel = 4
.GenerateInMemory = False
.IncludeDebugInformation = True

Dim References() As String = {"System.dll",
"System.Data.dll", "Microsoft.VisualBasic.dll"}
.ReferencedAssemblies.AddRange(References)
.OutputAssembly = "C:\Projects\ThisIsATestDLL.dll"

End With

Dim provOpt = New Dictionary(Of String, String)
provOpt.Add("CompilerVersion", "v3.5")
Compiler = New VBCodeProvider(provOpt)


CompileResults = Compiler.CompileAssemblyFromFile(CompilerParams,
"C:\RandD\VS08\TestClassLib\Test.vb")

If CompileResults.Errors.HasErrors Then
For Each Err As CodeDom.Compiler.CompilerError In
CompileResults.Errors
strReturn &= Err.ErrorText & "
@Line: " & Err.Line & vbCrLf
Next
End If

Catch ex As Exception
Throw New Exception(ex.Message, ex)
End Try
Return strReturn
End Function



That is the key. If we switch this to

provOpt.Add("CompilerVersion", "v2.0")


Our code will compile in version 2.0 framework.

What we want is to comile in 1.1. It is not as simple as changing the version to say "v1.1". That wont work. To understand how to accomplish this, we have to look at the code of VBCodeProvider's constructor. I used the .Net reflector to take a look at this.

The CodeDOMCompiler code does the following

If version is 3.5 then
look at the environment variables. If environment variables COMPLUS_InstallRoot and COMPLUS_Version is not present then compile code to default 3.5 version. Else read those environment variable versions to do the compile.
If the version is not 3.5 then it is defaulted to 2.0.

As you can see, the only two values that can be passed to the VBCodeProvider's constructor is "v3.5" or "v2.0".

So in order to compile the assembly in 1.1, we will introduce the env variables in code.

Imports System.CodeDom
Imports System.CodeDom.Compiler

Public Class CodeCompiler
Dim Compiler As VBCodeProvider = Nothing
Dim pvdr As CodeDomProvider
Dim CompilerParams As CompilerParameters
Dim CompileResults As CompilerResults

Public Function CompileCode() As String
Dim strReturn As String = ""

Try
CompilerParams = New CompilerParameters
With CompilerParams
.TreatWarningsAsErrors = False
.WarningLevel = 4
.GenerateInMemory = False
.IncludeDebugInformation = True

Dim References() As String = {"System.dll",
"System.Data.dll", "Microsoft.VisualBasic.dll"}
.ReferencedAssemblies.AddRange(References)
.OutputAssembly = "C:\Projects\ThisIsATestDLL11.dll"


End With

'You can put these values in the config file if you dont want to 'hard code it.
Environment.SetEnvironmentVariable("COMPLUS_InstallRoot",
"C:\WINDOWS\Microsoft.NET\Framework")
Environment.SetEnvironmentVariable("COMPLUS_Version",
"v1.1.4322")


Dim provOpt = New Dictionary(Of String, String)
provOpt.Add("CompilerVersion", "v3.5")


Compiler = New VBCodeProvider(provOpt)
CompileResults = Compiler.CompileAssemblyFromFile(CompilerParams,
"C:\RandD\VS08\TestClassLib\Test.vb")

If CompileResults.Errors.HasErrors Then
For Each Err As CodeDom.Compiler.CompilerError In
CompileResults.Errors
strReturn &= Err.ErrorText & "
@Line: " & Err.Line & vbCrLf
Next
End If

Catch ex As Exception
Throw New Exception(ex.Message, ex)
End Try
Return strReturn
End Function
End Class



The lines of code

Environment.SetEnvironmentVariable("COMPLUS_InstallRoot", "C:\WINDOWS
\Microsoft.NET\Framework")
Environment.SetEnvironmentVariable("COMPLUS_Version",
"v1.1.4322")


does the trick. It foxes the .Net code into believing that we have 3.5 version to be compiled, but since we introduce the env variables before calling the VBCodeCompiler's constructor, it will read the env variables and compile the code in 1.1. It's that simple.

These env variable setting is only for that current code and once the code runs out of scope, the env variables are not persisted.

I had posted this solution on www.experts-exchange.com too.. Hope it helps someone!

Enjoy!

No comments:

Post a Comment