文章目錄
- Loading C++ Assemblies in ASP.Net
環境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的Managed 程式碼中使用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類,在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
Jerry_Orman
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);
}
2011-11-11 21:22