We target multiple runtime versions concurrently (.NET 1.1, .NET 2.0, and .NET 3.5) for some products.
We handle this in several ways:
- Separate Solution and Project files and for each of .NET 1.1, 2.0, and 3.5 SP1, but referencing the same source files.
eg:
\ProductFoo_1_1.sln (.NET 1.1 solution, VS 2003)
\ProductFoo_2_0.sln (.NET 2.0 solution, VS 2008)
\ProductFoo_3_5.sln (.NET 3.5 solution, VS 2008)
\FooLibrary\FooLibrary_1_1.csproj (.NET 1.1 Project, VS 2003)
\FooLibrary\FooLibrary_2_0.csproj (.NET 2.0 Project, VS 2008)
\FooLibrary\FooLibrary_3_5.csproj (.NET 3.5 Project, VS 2008)
\FooLibrary\FooClass.cs (shared amongst all Projects)
\FooLibrary\FooHelpers_1_1.cs (only referenced by the .NET 1.1 project)
\FooService\FooService_3.5.csproj (.NET 3.5 Project, VS 2008)
\FooService\FooService.cs
Defining NET_X_X
symbols in each of the solutions
For .NET Framework specific code, we use preprocessor instructions such as this:
public void SomeMethod(int param)
{
#ifdef NET_1_1
// Need to use Helper to Get Foo under .NET 1.1
Foo foo = Helper.GetFooByParam(param);
#elseif NET_2_0 || NET_3_5
// .NET 2.0 and above can use preferred method.
var foo = new Foo { Prop = param };
foo.LoadByParam();
#endif
foo.Bar();
}
#ifdef NET_3_5
// A method that is only available under .NET 3.5
public int[] GetWithFilter(Func Filter)
{
// some code here
}
#endif
For clarification, the above lines starting with # are preprocessor commands. When you compile a solution, the C# Compiler (csc) pre-processes the source files.
If you have an #ifdef
statement, then csc will evaluate to determine if that symbol is defined - and if so, include the lines within that segment when compiling the project.
It's a way to mark up code to compile in certain conditions - we also use it to include more intensive debugging information in specific verbose debug builds, like so:
#if DEBUG_VERBOSE
Logging.Log("Web service Called with parameters: param = " + param);
Logging.Log("Web service Response: " + response);
Logging.Log("Current Cache Size (bytes): " + cache.TotalBytes);
// etc.
#endif
- We then have NAnt scripts which automate the production of a release for each .NET version.
We happen to control all this through TeamCity, but we can trigger the NAnt scripts manually too.
It does make things more complicated, so we only tend to do it where we need to maintain a legacy .NET 1.1 or 2.0 instance (eg where a customer can't/won't upgrade).
I imagine that when .NET 4.0 rolls around, we'll do the same thing and just add a NET_4_0 symbol.
Use the same memory manager for your application and your DLLs.
For recent version of Delphi that includes the new FastMM memory manager, use SimpleShareMem as the 1st unit in both your application and the DLL projects.
Or download the full FastMM4 from SourceForge, set the Flags in FastMM4Options.Inc (ShareMM, ShareMMIfLibrary, AttemptToUseSharedMM) and put FastMM4 as the 1st unit in both the application and the DLLs projects.
Best Solution
The definition of a string changed with D2009. If you want to make string communication safe, use a PAnsiChar or a WideString.
The basic rule of communication through DLLs is to not use anything specific to Delphi, so no Delphi strings and no TObject descendants. Interfaces, records and COM types work fine, though.