ASP.NET与非托管DLL的那些事儿【转+增】
https://www.cnblogs.com/yeahgis/archive/2011/11/12/2246341.html
环境VS2010
语言:ISO C++、C++\CLI和C# 多语言集成编程
最近在用ASP.NET(C#)开发一个WMS服务器的原型,由于标准C++开发的dll无法直接被C#引用,因此采用(类似SWIG自动包装的效果)C++\CLI进行二次封装和桥接(其实这也是SuperMap的套路,与ESRI的COM的确是不样)。现在遇到这样一个问题,首先做个假设:
(1)最底层的库是标准C++编写,最终生成的DLL假设叫isocpp.dll,这样的dll也叫做native dll,属于unmanaged(非托管)dll。
(2)为了让C#能够调用这个isocpp.dll,我使用C++\CLI对它进行了二次封装,在VS2010中编译时打开CLR支持,生成isocpp_clr.dll。
这样C#就可以直接把这个dll添加到项目的引用里面了,而winForm中只要保证isocpp_clr.dll与isocpp.dll在同一路径下,即可在.net的托管代码中使用isocpp_clr.dll中封装的类和方法,而所有这些类和方法实际是由isocpp.dll进行执行和计算的。因此在桌面程序环境中这样进行桥接是没有任何问题的。
但是换到asp.net的web环境下就产生了很恶心的一个问题:无法加载文件或者程序集isocpp_clr.dll,或者它的某一个依赖项,抛出system.io.filenotfoundexception,注意,此时所有的dll都已经在网站的bin目录下,并且所有dll的相对路径以及各自的绝对路径是没有任何问题的。翻了很多资料,而ASP.NET TEAM对这个问题所给出的解释是:由于asp.net网站在运行时会把所有的文件拷贝到C盘一个临时目录下运行,但是ASP.NET仅会把所依赖的托管dll拷贝过去,比如我的isocpp_clr.dll是可以被自动拷贝过去的,但是万一某个托管dll又依赖于某些非系统的非托管dll时,就会遇到无法加载文件或者程序集,filenotfoundexception这个异常。
明白了怎么回事,要想解决这个问题也并非容易的事情,一开始以为如果DEBUG下不能运行,那么发布出来我把所有的托管还有非托管的dll全都放到bin目录下总行了吧,没想到我又错了,问题依旧。
到这里不得不说国内的网络真的是像一个垃圾填埋场,搜索来搜索去愣是一堆人在那里ctrl c、ctrl v,真的是太水了,浪费不少时间。最后还是在一个老外的BLOG上找到了解决方法。原文在下面,我是采用2.a办法解决了问题:
(1)对非托管的dll,在对他们进行C++\CLI封装时,在项目的工程属性---连接器--生成事件的POST BUILT SCRIPT中采用命令行调用al.exe进行托管包装,方法:
al.exe /link:"..\bin\a.dll" /out:"..\bin\a_wrp.dll" /platform:x86
al.exe /link:"..\bin\b.dll" /out:"..\bin\b_wrp.dll" /platform:x86
al.exe /link:"..\bin\c.dll" /out:"..\bin\c_wrp.dll" /platform:x86
其他native dll...
(2)这样每个非托管dll会自动生成一个托管的dll,我理解相当于代理,把这个托管dll引入到.net工程的引用中即可保证这些本地的非托管dll可以被拷贝过去。作者说到这里只要把所有的dll都放到网站的bin目录下还不行,还需要为网站的进程设置环境变量。但是作完了这些我在本机debug时网站就能正常运行了,这也让人很费解。
(3)为网站的Process设置临时的环境变量:为网站添加Global类(Global.asax),在application_start中将当前网站的bin目录添加到Process级别的PATH环境变量中,这样就可以保证在任何情况下网站程序(一般是dll)都能找的到托管或者非托管的dll了,我的测试结果也证明了这一点。代码:
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);
}
全文转来,备忘也表示对原作者的感谢。
BTW,吐槽一下ms...
链接及原文:http://blogs.msdn.com/b/jorman/archive/2007/08/31/loading-c-assemblies-in-asp-net.aspx
Loading C++ Assemblies in ASP.Net
RATE THIS
31 Aug 2007 4:29 PM
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.
- 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.
- 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.
- The Windows system directory. Use the GetSystemDirectory function to get the path of this directory.
- The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
- The directories that are listed in the PATH environment variable.
============
The options:
============
- Use DLLImport to load the dll using a relative or absolute path at runtime.
- 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.
- Native C++ DLL project
- 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 - Managed C++ DLL Project
- 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
- 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.
- ASP.Net Project
- Reference the managed C++ DLL - The managed C++ DLL, Native C++ DLL, and NativeWrapper DLL are moved into the applications /bin folder.
- 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:
- Native C++ DLL Project - don’t need to do anything in the Post Build Script
- Managed C++ DLL Project
- Configure Delay Load DLL’s and specify the Native C++ DLL
- ASP.Net Project
- Reference the Managed C++ DLL - only Managed C++ DLL is in the /bin
- In web.config, add the following…use a path where the Native C++ DLL is located:
<appSettings>
<add key="NativePath" value="C:\MyNativeDLLs"/>
</appSettings> - 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);
}