Azure and msshrtmi.dll
The Problem
A while back we received some Azure pull requests which added an Azure project and fixed all our SQL scripts so they were compatible with SQL Azure and that was pretty good, FunnelWeb would run in Azure.
The problem though is updates to the my.config required a full new deployment, so I wanted to support loading the configuration from the Role Environment config files instead when running in Azure.
So in our configuration settings class I put something like this:
public string Get(string name)
{
return (RoleEnvironment.IsAvailable)
? RoleEnvironment.GetConfigurationSettingValue(name)
: myConfigSettings.Get(name);
}
All good, local testing works beautifully. So I start with deploying to Azure Websites via git using the same script we use to deploy to AppHarbor. And boom:
---> System.TypeInitializationException: The type initializer for 'Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment' threw an exception. ---> System.IO.FileNotFoundException: Could not load file or assembly 'msshrtmi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.
at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.InitializeEnvironment()
at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment..cctor()
--- End of inner exception stack trace ---
at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.get_IsAvailable()
It seems the static initialiser for RoleEnvironment relies on a native dll which is installed into the GAC by the Azure SDK... Isn't the point of IsAvailable to check if you are running in Azure... =(
So I start searching for that assembly, turns out it lives at "C:\Program Files\Microsoft SDKs\Windows Azure\.NET SDK\2012-06\bin\runtimes\base\x64\msshrtmi.dll" and "C:\Program Files\Microsoft SDKs\Windows Azure\.NET SDK\2012-06\bin\runtimes\base\x86\msshrtmi.dll" for the two different CPU architectures.
So I start off by putting the x64 version in the bin directory, which then blew up with
Could not load file or assembly 'msshrtmi' or one of its dependencies. An attempt was made to load a program with an incorrect format.
So I put the x86 version into the bin directory, and huzzah we were up and running. I then updated my blog to the latest build (anyone on here yesterday would have seen it going up and down for about an hour :P) and boom, back to Could not load file or assembly 'msshrtmi' or one of its dependencies. An attempt was made to load a program with an incorrect format.
Obviously this is not a good solution, and FunnelWeb now can only run on a x86 app pool, which my host is running an x64 app pool.
My Solution
I have removed all the other FunnelWeb initialisation code, and just left the important parts.
public class MvcApplication : HttpApplication
{
private static string basePath;
private void Application_Start()
{
basePath = Server.MapPath("/");
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
// Unfortunately this unmanaged dll is required by azure to check if we are running in azure
// There is also a x86 and x64 version which is why we have to take this approach
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.StartsWith("msshrtmi,", StringComparison.OrdinalIgnoreCase))
{
var fileName = Path.Combine(basePath, "bin", ((IntPtr.Size == 4) ? "x86" : "amd64"), "msshrtmi.dll");
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
return Assembly.LoadFile(fileName);
}
return null;
}
}
I then create a amd64 and x86 folder in the _bin_deployableAssemblies folder so they get copied into the bin folder during the build.
When the AppDomain tries to resolve the native file, I dynamically load the correct version.
Comments on my approach would be great.
Comments
Jörg
Good morning Jake,
yeah it's a pitty how architecture-specific assembly-references are handled & your solution is in fact the best thing you can do (from my past experience) if your own process/project is compiled as AnyCPU.
However, If you do compile to x86/x64 arch specifically & have two different reference assemblies for each arch, you can also add conditional references in your .csproj file, see http://stackoverflow.com/questions/3832552/conditionally-use-32-64-bit-reference-when-building-in-visual-studio , particularly this part works perfectly fine.
Jenda
Thanks for saving my but :-) I'm sure I'll find this trick helpful other times as well.
Reid
This is so hacky. My solution is hacky too, but I prefer it. The main issue is that azure needs msshrtmi.dll in x64, but the MVC view compiler will only compile the views in x86.
I changed:
to:
so it deletes the msshrtmi.dll file before the MVC view compiler runs and adds it back afterwards.