Advanced MSBuild: Ignoring Bitness when building Plugins

TL;DR: When developing plugins for an application that is shipped as both 32 and 64 bit, it may be possible to publish a single artifact working with both by building as AnyCpu, ignoring the MSB3270 warning.

This post is part of the Advanced MSBuild series.

Requirements: compatibility with both 32-bit and 64-bit

In my current project I had to maintain and develop plugins for a third party application.
The application comes with a couple of assemblies against which plugins can be developed and compiled.
I will call those the application’s API assemblies.
The application will provide the assemblies at runtime, i. e. they are not deployed along with the plugin’s own assemblies.

The same plugin was required to work with multiple (around 5) different versions of the application at any given time.
There will be another post on how to implement this without duplicating code and writing boilerplate adapters.
This post is about the build implications instead.

The application has been in 32 bit for a long time, with 32-bit API assemblies, let’s say until version 3.
Newer versions of the application are released in 64 bit with 64-bit API assemblies, let’s say versions 4 and 5.
Nevertheless, some preview beta versions of version 4 and 5 are still delivered in 32-bit with 32-bit API assemblies, even though the release version would be 64-bit.
The plugins need to work with the beta versions as well so that people can try them out and give feedback to the third party supplier.

What to do?

We could produce both 32-bit (<TargetPlatform>x86</TargetPlatform>) and 64-bit (<TargetPlatform>x64</TargetPlatform>) artifacts of the plugin for versions 4 and 5.
But that would add to the complexity of the project structure and bloat the build process.
It would also be more difficult for users to pick the right artifact, especially with the plugins folder being the same for 32-bit and 64-bit beta versions.
Better to avoid all that work and explaining.

Historically the plugins were compiled against the 32-bit API assemblies and thus rightfully declared <TargetPlatform>x86</TargetPlatform> in their csproj.
The plugin’s code is managed code only, so there is no intrinsic reason to pick a target platform.
But if we do not declare a target platform (to build an AnyCpu assembly) , we get MSBuild warning MSB3270 “There was a mismatch between the processor architecture of the project being built … and the processor architecture of the reference …”.
This of course happens for both 32-bit and 64-bit API assembly references.

Ideally, we would have an AnyCpu API assembly from the third party and just deliver an AnyCpu plugin dll compiled against that.
But we don’t, so we can’t.
Or can we?

The solution turns out to be very simple:

We can literally ignore the issue

Of course we will not do so blindly, but in an informed manner, understanding why we know better than MSBuild in this case.

The artifact at hand is a plugin, which means it will be loaded by the third party application which is also responsible for loading the API assemblies (in contrast to e. g. an Exe assembly that uses an API assembly to actively call into a third party application).
When the third party application loads our plugin assemblies, the .NET runtime tries to dynamically bind our plugin assemblies’ calls into the API assemblies to the already loaded API assemblies.
The binding has to succeed, or we get a MissingMethodException at runtime and the application crashes.
That is what the compiler and MSBuild try to check at build-time.

We now make the very reasonable assumption that the API surface of a 32-bit API assembly is identical to the one of a 64-bit API assembly if they have the same assembly version (or more lavishly: assembly versions that promise runtime-compatibility according to the employed versioning scheme).
Thus, as long as we compile the plugin code against the right API assembly version, it does not matter whether that API assembly is in 32-bit or 64-bit or AnyCpu.
The runtime binding will succeed.

And that is why we can just ignore MSB3270 here.
Of course we do not want to have to remember to ignore this warning each time we read it, but suppress it.
The documentation for MSB3270 tells us there is even a specific property we can set to do just that.
So we can just remove the TargetPlatform to build for AnyCpu and set the property:

<ResolveAssemblyWarnOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOnTargetArchitectureMismatch>

We have achieved our goal of making the plugin compatible with both 32-bit and 64-bit applications, without build warnings, without additional complexity for either developers or users, without any changes in our build infrastructure.
Just by intelligently ignoring the issue ;-).