Delta Engine Blog

AI, Robotics, multiplatform game development and Strict programming language

Conditionally linking against DLLs depending on configuration/platform

Dynamic Linking

When developing for multiple platforms you will eventually run into a situation where you have to link against different platform specific libraries depending on which platform you compile for.

A very basic example would be compiling a very simple (non-graphic) application for Windows and Linux. On Windows you want to use the original .NET libraries and on Linux you want to link against the Mono libraries.

Without dynamic linking you have to create two different projects in the same folder and let them share the same code files. This is cumbersome and probably causes errors, because you may forget to include newly created files in one of the projects.

With dynamic linking you can use a single project file and instead create two project (and solution) configurations for each platform. The project configurations declare preprocessor defines (assume WINDOWS and LINUX), which we use to differentiate between the two configurations. You can also use these defines in code files to execute platform specific code.

(Note: For simplicity's sake I have only created debug configurations in the following example, no release configs)

The following snippet of the MultiPlatformTest.csproj file shows the two project configurations for our test application.

(Note: All of the modifications made here must be done using a text editor of your choice in the .csproj file)


  true
  full
  false
  bin\Debug\
  TRACE;DEBUG;WINDOWS
  prompt
  4


  true
  bin\Debug Linux\
  TRACE;DEBUG;LINUX
  full
  AnyCPU
  prompt



So, how do we declare different references based on platform?

"MSBuild Conditional Constructs" is the answer!

The XML of .csproj files know an element called "Choose", which allows you to implement simple "if"s inside the project definition. We can use this element to implement our multiplatform application.

First of all we have to define something that we can check for in the "When" part of the "Choose" element.

Add a new XML element to each of the above <PropertyGroups>:


  
  
  



At the same time, change the old preprocessor define in <DefineConstants> to use this new element. Our project configurations should now look like this:

  
    true
    full
    false
    bin\Debug\
    WINDOWS
    TRACE;DEBUG;$(MyPlatform)
    prompt
    4
  
  
    true
    bin\Debug Linux\
    LINUX
    TRACE;DEBUG;$(MyPlatform)
    full
    AnyCPU
    prompt
    true
  


(Note: I've also added the <NoStdLib> element to the Linux define, which is required when you want to link against the Mono version of mscorlib.)

Open the project in Visual Studio (or let VS reload it) and toggle between the two configurations in the project properties. The text box with "Conditional compilation symbols" will correctly show "WINDOWS and "LINUX" (respectively).

Now we still have to use these defines correctly. Let's replace the whole "Reference" ItemGroup block with the following:

  
  
  
    
      
        
        
        
      
    
    
      
        
        
        
        
      
    
  


(Note: Replace <Path_to_Mono> with your local Mono path)

Reload the project file.

The first thing you may notice is a shortcoming of Visual Studio:

In the solution explorer the "References" part of the project is now empty. (You may have to click the little "+" symbol to refresh this)
This is only a visual bug; the compiler and Intellisense handle this correctly!

You can easily verify this by adding a new reference (e.g. "System.Windows.Forms") to one of your configs and putting a "#using System.Windows.Forms" into one of your project code files. When switching between the configs you will get compiler/parser errors with the configuration where the reference is missing. Intellisense will only work with the config where the reference was added.

Congratulations! You can now add all your platform specific references to the new ItemGroup blocks and put all non-platform specific references into the old ItemGroup block.

By the way, you also do this with any other define you have. For example you can check for "DEBUG" define and only link testing libraries into debug builds.

References:
  • http://msdn.microsoft.com/en-us/library/ms164307.aspx