未经管理的DLL无法在ASP.NET服务器上加载

71

这个问题涉及到一个ASP.NET网站,最初是在VS 2005中开发的,现在在VS 2008中。

这个网站使用了两个非.NET的未托管外部DLL,我没有源代码来编译它们,只能直接使用。

在Visual Studio中运行这个网站时,可以正确地定位和访问这些外部DLL,但是当网站发布到Web服务器(运行IIS6和ASP.NET 2.0)而不是开发PC时,它无法定位和访问这些外部DLL,并且会出现以下错误:

Unable to load DLL 'XYZ.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)

这些外部DLL与封装它们的托管DLL和网站的所有其他DLL一样,位于网站的bin目录中。

搜索此问题显示,许多其他人似乎都有从ASP.NET网站访问外部非.NET DLL的同样问题,但我还没有找到有效的解决方案。

我已经尝试过以下方法:

  • 运行DEPENDS工具来检查依赖项,确定前三个文件在路径的System32目录中,最后一个文件在.NET 2框架中。
  • 将这两个DLL及其依赖项放入System32并重新启动服务器,但网站仍然无法加载这些外部DLL。
  • 将ASPNET、IIS_WPG和IUSR(该服务器)的完全权限授予网站bin目录,并重新启动,但网站仍然无法加载这些外部DLL。
  • 将外部DLL添加为项目的现有项,并将其“复制到输出”属性设置为“始终复制”,但网站仍然找不到这些DLL。
  • 还将它们的“生成操作”属性设置为“嵌入资源”,但网站仍然找不到这些DLL。

如果您能提供关于此问题的帮助,将不胜感激!

12个回答

50

这是因为托管的dll文件被复制到.NET Framework目录下的临时位置中。详见http://msdn.microsoft.com/en-us/library/ms366723.aspx

遗憾的是,非托管的dll文件不会被复制,当ASP.NET进程需要加载它们时就会找不到。

一个简单的解决方法是将非托管的dll文件放在系统路径中的某个目录(在命令行中输入“path”可查看您机器上的路径)以便让ASP.NET进程找到它们。System32目录始终在路径中,因此将非托管的dll文件放在那里总是有效的,但我建议添加其他文件夹并将dll文件放在那里,以防止污染System32目录。这种方法的一个主要缺点是您必须为每个应用程序版本重命名非托管的dll文件,否则容易出现自己的dll地狱。


6
这个答案比被采纳的答案更好,因为它解释了为什么你想要这样做。 - Tom W
根据我的经验,当我创建一个新目录并将其添加到PATH中,并将DLL放在其中时,在本地运行时可以很好地找到它。然而,在托管在IIS上时情况并非如此 - 只有当我将DLL放在/Windows/System32中时才能找到它。也许IIS在某些情况下使用了不同的变量来设置路径? - Jake Wood

42

除了将dll放在已经在路径中的文件夹(如system32)中之外,您还可以使用以下代码更改进程中的路径值

System.Environment.SetEnvironmentVariable("Path", searchPath + ";" + oldPath)

当LoadLibrary尝试查找非托管DLL时,它也会扫描searchPath。这可能比在System32或其他文件夹中弄得一团糟更可取。


5
仅适用于PInvoke。如果使用混合模式程序集,那么在任何代码运行之前,DLL将被链接(并失败)。 - Nick Whaley

22

尝试将dll文件放置在 \System32\Inetsrv 目录中。这是Windows Server上IIS的工作目录。

如果这不起作用,请尝试将dll文件放置在System32目录中,将依赖文件放置在Inetsrv目录中。


3
以下是无需污染system32文件夹的答案:https://dev59.com/PnRC5IYBdhLWcg3wS_EF#4598747 - Felix Alcala
9
如果您不需要在运行中修改二进制文件,您可以通过在web.config文件中添加<hostingEnvironment shadowCopyBinAssemblies="false"/>来禁用ShadowCopying。注意不要改变原有意思。 - Amit
我曾因将DLL文件的副本复制到System32中而受到损失,因为我忘记了它,并且我加载了错误版本的Microsoft.Azure.Documents.ServiceInterop.dll,这导致在连接到数据库时出现了奇怪的local queryRanges[0].isMinInclusive错误。我的解决方法最终是确保我的本地IIS DefaultAppPool Identity是我的本地用户。请参见:https://github.com/Azure/azure-documentdb-dotnet/issues/267 - Zachstronaut
你刚刚挽救了我的一天,+1。 - Y.S

12

补充Matt的答案,这是最终在64位服务器2003 / IIS 6上为我工作的方法:

  1. 确保您的dll / asp.net版本相同(32/64位)
  2. 将非托管的dll放入inetsrv目录中(请注意,在64位Windows中,即使创建了sys32 / inetsrv目录,此目录也位于syswow64下)
  3. 将托管的dll保留在/ bin中
  4. 确保两组dll具有读取/执行权限

在64位系统上,inetsrv的位置加1。感谢您提供这个信息。 - Klaus Nji
我发现这太繁琐了,而且不允许使用不同版本的dll。我们通过使用符号链接来解决了这个问题。 - Ristogod

6

使用FileMonProcMon,并过滤掉有问题的DLL名称。这将显示扫描DLL所在目录的位置,并可能会提示任何权限问题。


1
ProcMon非常有用,可以帮助确定依赖关系以及某个DLL无法加载的原因。 - Oliver

4

我不建议在可能在PAAS主机(如Azure Websites)上运行或使用的应用程序或库中采用这种方法。 - yzorg
为什么你不推荐这个方法?我正在考虑在Azure Functions应用程序中使用这种方法,以便从托管的程序集中运行本地可执行文件。 - Štěpán Beneš
当我编写它时,Azure函数并不存在。Azure函数非常小而精确,如果您使其正常工作,那就去做吧! - yzorg

2

在整个一天的苦思冥想后,我终于找到了一个适合我的解决方案。这只是一个测试,但这种方法是有效的。

namespace TestDetNet
{
    static class NativeMethods
    {
        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string dllToLoad);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);


        [DllImport("kernel32.dll")]
        public static extern bool FreeLibrary(IntPtr hModule);
    }

    public partial class _Default : System.Web.UI.Page
    {
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        private delegate int GetRandom();

        protected System.Web.UI.WebControls.Label Label1;
        protected void Page_Load(object sender, EventArgs e)
        {
            Label1.Text = "Hell'ou";
            Label1.Font.Italic = true;
        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            if (File.Exists(System.Web.HttpContext.Current.Server.MapPath("html/bin")+"\\DelphiLibrary.dll")) {
                IntPtr pDll = NativeMethods.LoadLibrary(System.Web.HttpContext.Current.Server.MapPath("html/bin")+"\\DelphiLibrary.dll");
                if (pDll == IntPtr.Zero) { Label1.Text =  "pDll is zero"; }
                else
                {
                  IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "GetRandom");
                  if (pAddressOfFunctionToCall == IntPtr.Zero) { Label1.Text += "IntPtr is zero";   }
                  else
                  {
                    GetRandom _getRandom = (GetRandom)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall,typeof(GetRandom));

                    int theResult = _getRandom();

                    bool result = NativeMethods.FreeLibrary(pDll);
                    Label1.Text = theResult.ToString();
                  }
                }
          }
        }
    }
}

2

我遇到了同样的问题。我尝试了上述所有选项,包括将文件复制到system32、inetpub,设置路径环境变量等等,但都没有起作用。

最后,我通过将非托管dll文件复制到Web应用程序或Web服务的bin目录中来解决了这个问题。


2

8
答案已经四年了,因为我没有追踪确保链接仍然有效,你就将我投票反对?真是个苛刻的群体。 - annakata
3
你应该从链接中提取重要信息,这样即使链接失效,这个答案仍然有效。 - Drax
@Drax - 我认为重要的信息是检查您的路径变量。 - annakata
@annakata,对我来说答案已经很清楚了,但可能对所有感兴趣的用户来说并不是那么易懂 :) - Drax

1

直接在您部署它的位置上运行依赖于XYZ.dll。如果这没有显示任何遗漏,可以使用平台SDK中的fuslogvw工具跟踪装载器错误。此外,事件日志有时会包含关于无法加载DLL的失败信息。


你能提供DEPENDS的下载链接吗? - James McMahon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接