How can I specify the ASP.NET probing path for automatic C++/CLI appdomains?

463 views Asked by At

I have a C++/CLI DLL that needs to run in the context of an ASP.NET WebApi action.

The action is affected by an ActionFilter that uses log4net's LogicalThreadContext to set a property containing a request ID; this works nicely to write the request ID into the log entries.

The problem is that ASP.NET seems to be creating an appdomain when it's time to load the class from the C++/CLI dll. (I can't seem to find any documentation on how that works; I'd love to find some.) When the appdomain plumbing tries to deserialize everything, it chokes because it can't find log4net.dll (see stack trace below).

Fusion logs show that it's looking in C:\Windows\System32\inetsrv, which is incorrect... I would expect it to look in the probing path or along the %PATH% environment variable.

A few data points:

  • log4net.dll is available in the web app's bin directory and works fine for everything else I'm using it for.
  • The C++/CLI code has no dependencies on log4net.
  • If I comment out my calls to log4net.LogicalThreadContext.Properties[...], everything works fine.
  • If I copy log4net.dll from my bin folder to C:\Windows\system32\inetsrv, everything works fine.
  • The web.config file specifies the probing path with a <probing privatePath="bin"/> element.
  • My Application_Start method in global.asax.cs prepends the web app's bin directory to the %PATH% environment variable:

        var currentPathEnvVar = Environment.GetEnvironmentVariable("PATH");
        var binPath = Path.Combine(Server.MapPath("~") ?? String.Empty, "bin");
        _log.DebugFormat("Prepending paths to PATH: [{0}]", binPath);
        Environment.SetEnvironmentVariable("PATH", String.Concat(binPath, Path.PathSeparator, currentPathEnvVar));
    
  • The log shows that the %PATH% is getting prepended correctly:

    2015-06-05 17:07:48,816 1   DEBUG   MyNamespace.WebApiApplication   (null)  Prepending paths to PATH: [C:\Program Files\MyCompany\MyApp\web\bin]
    

So how do I get the appdomain that ASP.NET creates to look in the right place to load assemblies?

Thanks.


Here's the stack trace that gets logged when all this goes down.

2015-06-05 17:08:41,148 23  ERROR   MyNamespace.MyController    (null)  Controller action failed
System.TypeInitializationException: The type initializer for '<Module>' threw an exception. ---> <CrtImplementationDetails>.ModuleLoadException: The C++ module failed to load while attempting to initialize the default appdomain.
 ---> System.Runtime.Serialization.SerializationException: Unable to find assembly 'log4net, Version=1.2.13.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a'.

Server stack trace: 
   at System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name)
   at System.Runtime.Serialization.Formatters.Binary.ObjectMap..ctor(String objectName, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable)
   at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record)
   at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Remoting.Channels.CrossAppDomainSerializer.DeserializeObject(MemoryStream stm)
   at System.Runtime.Remoting.Messaging.SmuggledMethodCallMessage.FixupForNewAppDomain()
   at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoDispatch(Byte[] reqStmBuff, SmuggledMethodCallMessage smuggledMcm, SmuggledMethodReturnMessage& smuggledMrm)
   at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoTransitionDispatchCallback(Object[] args)

Exception rethrown at [0]: 
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at System.AppDomain.IsDefaultAppDomain()
   at <CrtImplementationDetails>.GetDefaultDomain() in f:\dd\vctools\crt\crtw32\h\minternal.h:line 367
   at <CrtImplementationDetails>.DoCallBackInDefaultDomain(IntPtr function, Void* cookie) in f:\dd\vctools\crt\crtw32\h\minternal.h:line 401
   at <CrtImplementationDetails>.DefaultDomain.Initialize() in f:\dd\vctools\crt\crtw32\msilcrt\mstartup.cpp:line 278
   at <CrtImplementationDetails>.LanguageSupport.InitializeDefaultAppDomain(LanguageSupport* ) in f:\dd\vctools\crt\crtw32\msilcrt\mstartup.cpp:line 343
   at <CrtImplementationDetails>.LanguageSupport._Initialize(LanguageSupport* ) in f:\dd\vctools\crt\crtw32\msilcrt\mstartup.cpp:line 546
   at <CrtImplementationDetails>.LanguageSupport.Initialize(LanguageSupport* ) in f:\dd\vctools\crt\crtw32\msilcrt\mstartup.cpp:line 703
   --- End of inner exception stack trace ---
   at <CrtImplementationDetails>.ThrowModuleLoadException(String errorMessage, Exception innerException) in f:\dd\vctools\crt\crtw32\h\minternal.h:line 194
   at <CrtImplementationDetails>.LanguageSupport.Initialize(LanguageSupport* ) in f:\dd\vctools\crt\crtw32\msilcrt\mstartup.cpp:line 713
   at .cctor() in f:\dd\vctools\crt\crtw32\msilcrt\mstartup.cpp:line 754
   --- End of inner exception stack trace ---
   at MyNamespace.MyController.MyAction(String foo, Int64 bar, String baz)
1

There are 1 answers

0
Bobo On

This helped me to resolve similar issue: https://blogs.msdn.microsoft.com/jorman/2007/08/31/loading-c-assemblies-in-asp-net/ Faced option 2.a and proposed solution worked fine:

protected void Application_Start(object sender, EventArgs e){ String _path = String.Concat(System.Environment.GetEnvironmentVariable("PATH"), ";", System.AppDomain.CurrentDomain.RelativeSearchPath); System.Environment.SetEnvironmentVariable("PATH", _path, EnvironmentVariableTarget.Process); }