Loading C++ Assemblies in ASP.Net

When you reference a Native C++ assembly from ASP.Net you may run into the following error:

System.IO.FileNotFoundException: The specified module could not be found.
(Exception from HRESULT: 0x8007007E)

[FileNotFoundException: The specified module could not be found. (Exception from HRESULT: 0x8007007E)]
System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) +0
System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +211
System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +141
System.Reflection.Assembly.Load(String assemblyString) +25
System.Web.Configuration.CompilationSection.LoadAssemblyHelper(String assemblyName, Boolean starDirective) +32

[ConfigurationErrorsException: The specified module could not be found. (Exception from HRESULT: 0x8007007E)]
System.Web.Configuration.CompilationSection.LoadAssemblyHelper(String assemblyName, Boolean starDirective) +596
System.Web.Configuration.CompilationSection.LoadAllAssembliesFromAppDomainBinDirectory() +3591161
System.Web.Configuration.CompilationSection.LoadAssembly(AssemblyInfo ai) +46
System.Web.Compilation.BuildManager.GetReferencedAssemblies(CompilationSection compConfig) +177
System.Web.Compilation.BuildProvidersCompiler..ctor(VirtualPath configPath, Boolean supportLocalization, String outputAssemblyName) +180
System.Web.Compilation.ApplicationBuildProvider.GetGlobalAsaxBuildResult(Boolean isPrecompiledApp) +3558605
System.Web.Compilation.BuildManager.CompileGlobalAsax() +51
System.Web.Compilation.BuildManager.EnsureTopLevelFilesCompiled() +462

[HttpException (0x80004005): The specified module could not be found. (Exception from HRESULT: 0x8007007E)]
System.Web.Compilation.BuildManager.ReportTopLevelCompilationException() +57
System.Web.Compilation.BuildManager.EnsureTopLevelFilesCompiled() +612
System.Web.Hosting.HostingEnvironment.Initialize(ApplicationManager appManager, IApplicationHost appHost, IConfigMapPathFactory configMapPathFactory, HostingEnvironmentParameters hostingParameters) +642

[HttpException (0x80004005): The specified module could not be found. (Exception from HRESULT: 0x8007007E)]
System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +3539851
System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +69
System.Web.HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr) +252

 

The core cause to this problem is in the way the operating system loads native DLL's at runtime. Native DLL's are loaded using the following logic which does not include the Temporary ASP.net Files nor the applications /bin folder. This problem will also occur in any .Net application if the Native DLL is not included in the /bin folder with the .EXE file or if the DLL is not in the Path Environment Variable.
 

  1. The directory from which the application loaded.  In the case of ASP.Net, this will resolve to %windir%\Microsoft.Net\Framework\v###\ or %windir%\system32\inetsrv for IIS 6.  
  2. The current directory.  In the case of ASP.Net, this will resolve to %windir%\System32\inetsrv for IIS 6.  If using the built-in web server, this resolves to a path under C:\Program Files\Microsoft Visual Studio 8.
  3. The Windows system directory.  Use the GetSystemDirectory function to get the path of this directory. 
  4. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory. 
  5. The directories that are listed in the PATH environment variable.

============
The options:
============

  1. Use DLLImport to load the dll using a relative or absolute path at runtime.
  2. Set the PATH Environment Variable so the ASP.Net process can locate the C++ DLL.  You can set this property at runtime so that it only affects the process running your code.  You can also set this globally in the System Properties (Environment Variables | PATH property). Setting this programmatically does not require a reboot and you can point the PATH to the /bin folder of the web app if you want to be able to do XCopy deployments of your ASP.Net application.  Here are the steps to set the Path programmatically from ASP.Net.

 Option 2.a - If you want your Native C++ DLL’s loaded from the /bin of the ASP.Net application. 

  1. Native C++ DLL project
    1. Use al.exe to build a NativeWrapper for the Native C++ DLL.  This allows you to bring the Native C++ DLL along with the DLL that is referencing it.  You can add this in the Post Build Script of the Native C++ DLL Project to automate this.

      al.exe /link:"$(TargetPath)" /out:"$(TargetDir)$(TargetName).NW.dll" /platform:x86

    2. Managed C++ DLL Project

      1. Reference the NativeWrapper DLL - when you build this project, the native wrapper and Native C++ DLL files are copied to the output directory along with the managed C++ DLL
      2. Set Delay Load DLLs to the Native DLL.  (C++ Project Properties, Expand Linker, select Input, Delay Loaded DLLs) - This prevents the Native DLL from loading when the process starts, giving you a chance to set the PATH Environment variable.  If you don’t set this property, moci.net and its dependencies (i.e. the Native DLL) will be loaded before any of your ASP.Net code can run and this will fail unless you have set the PATH environment variable on the machine level.
    3. ASP.Net Project

      1. Reference the managed C++ DLL - The managed C++ DLL, Native C++ DLL, and NativeWrapper DLL are moved into the applications /bin folder.

      2. Add a Global.asax with the following code.  (Right-click the Web Application, select Add, New Item, select Global Application Class).  You could also use an HTTPModule compiled into a DLL if you don’t want people to be able to change your code.  Application_Start runs one time when the application loads and this code will set the PATH environment variable for the process to include the /bin directory of the application.

        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);
        }

 Option 2.b - If you want your Native C++ DLLs to load from an installation path outside of the web site you can avoid the AL command.  You would still need to set the Delay Load property on any Managed C++ DLL that loads the Native C++ DLL’s as well as set the Environment Variable.  If you choose to go this route, you can load the path to your Native C++ DLL’s dynamically from the web.config file at runtime:

  1. Native C++ DLL Project - don’t need to do anything in the Post Build Script
  2. Managed C++ DLL Project
    1. Configure Delay Load DLL’s and specify the Native C++ DLL
  3. ASP.Net Project
    1. Reference the Managed C++ DLL - only Managed C++ DLL is in the /bin

    2. In web.config, add the following…use a path where the Native C++ DLL is located:
         <appSettings>
          <add key="NativePath" value="C:\MyNativeDLLs"/>
         </appSettings>

    3. In global.asax, add the following:

      protected void Application_Start(object sender, EventArgs e){
          String _path = String.Concat(System.Environment.GetEnvironmentVariable("PATH"), ";", ConfigurationSettings.AppSettings["NativePath"]);
          System.Environment.SetEnvironmentVariable("PATH", _path, EnvironmentVariableTarget.Process);
      }

Comments

  • Anonymous
    December 07, 2007
    The comment has been removed

  • Anonymous
    May 07, 2008
    Fantastic post.  Excellent explanation of the issue and proposed solutions.

  • Anonymous
    June 17, 2008
    The comment has been removed

  • Anonymous
    July 16, 2008
    Really, fantastic post.  Many, many thanks.

  • Anonymous
    September 12, 2008
    Really, truly fantastic post. Where have you been in the last 2 years? I am searching for this since ice age. Cheers!

  • Anonymous
    August 19, 2009
    I just got the same issue suddenly, my code was working and I had the Path variable set right to the folder which had the unmanaged dll's :S

  • Anonymous
    January 05, 2010
    The comment has been removed

  • Anonymous
    April 30, 2010
    This posted helped me a ton, thank you!  I modified 2a slightly to create 2c:

  1. use delay-loading linker settings but drop native wrapper gen - works fine w/o it
  2. keep native DLL's out of "bin" dir as that is recommendation that ASP.NET Quick Response team gave me.  I use "bin_native" instead.
  3. shadow copy native DLL's to AppDomain.DynamicDirectory so automatic updating of app by ASP.NET still triggered by xcopy deployment
  4. use static Global constructor instead of Application_Start - probably no difference static Global()        {            AppDomain curDomain = AppDomain.CurrentDomain;            String binDir = Path.Combine( curDomain.BaseDirectory, "bin_native" );            String shadowCopyDir = curDomain.DynamicDirectory;            // C++/CLI project needs to link with delay-loading on these DLL's for this to work            String aafIoDllSrc = Path.Combine(binDir, "Aaf.IO.dll");            String aafIoDllDst = Path.Combine(shadowCopyDir, Path.GetFileName(aafIoDllSrc)); #if DEBUG            String boostThreadDllSrc = Path.Combine(binDir, "boost_thread-vc90-mt-gd-1_37.dll");            String boostThreadDllDst = Path.Combine(shadowCopyDir, Path.GetFileName( boostThreadDllSrc ) ); #else            String boostThreadDllSrc = Path.Combine(binDir, "boost_thread-vc90-mt-1_37.dll");            String boostThreadDllDst = Path.Combine(shadowCopyDir, Path.GetFileName( boostThreadDllSrc )); #endif            try            {                File.Copy(aafIoDllSrc, aafIoDllDst, true);            }            catch (System.Exception ex)            {                _log.Warn( ex.ToString() );             _log.WarnFormat( "Unable to update {0}", aafIoDllDst );            }            try            {                File.Copy(boostThreadDllSrc, boostThreadDllDst, true);            }            catch (System.Exception ex)            {                _log.Warn(ex.ToString());                _log.WarnFormat("Unable to update {0}", boostThreadDllDst);            }            String path = Environment.GetEnvironmentVariable("PATH");            path = String.Concat(path, ";", shadowCopyDir);            Environment.SetEnvironmentVariable("PATH", path, EnvironmentVariableTarget.Process);        }
  • Anonymous
    April 12, 2013
    Good information, got native dlls working in IIS, thanks.

  • Anonymous
    March 21, 2014
    The comment has been removed

  • Anonymous
    June 27, 2014
    We are having the same problem. It seems the dll works fine when in debugging ASP.NET but as soon as we publish to IIS 6.0 the dll can not be found.

  • Anonymous
    November 24, 2014
    Can you believe that after more than 7 years, this blog is still the best resource I can find to resolve my problem?!...

  • Anonymous
    March 31, 2015
    My researches seem to reveal that to load a native module (dll) it is necessary to register the module to IIS. But to register a module to IIS you need to have administrator access. As I have no control of the server on which I am trying to publish this appears to be preventing me using a native dll in this way.

  • Anonymous
    April 14, 2015
    What a wonderful post. Thank you very much!