This question relates to an ASP.NET website, originally developed in VS 2005 and now in VS 2008.
This website uses two unmanaged external DLLs which are not .NET and I do not have the source code to compile them and have to use them as is.
This website runs fine from within Visual Studio, locating and accessing these external DLLs correctly. However, when the website is published on a webserver (runnning IIS6 and ASP.NET 2.0) rather than the development PC it cannot locate and access these external DLLs, and I get the following error:
Unable to load DLL 'XYZ.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)
The external DLLs are located in the bin directory of the website, along with the managed DLLs that wrap them and all the other DLLs for the website.
Searching this problem reveals that many other people seem to have the same problem accessing external non.NET DLLs from ASP.NET websites, but I haven't found a solution that works.
I have tried the following:
Running DEPENDS to check the dependencies to establish that the first three
are in System32 directory in the path, the last is in the .NET 2
framework.
I put the two DLLs and their dependencies in
System32 and rebooted the server, but website still
couldn't load these external DLLs.
Gave full rights to ASPNET, IIS_WPG and IUSR (for that server) to
the website bin directory and rebooted, but website still couldn't
load these external DLLs.
Added the external DLLs as existing items to the projects and set
their "Copy to Output" property to "Copy Always", and website
still can't find the DLLs.
Also set their "Build Action" property to "Embedded resource" and
website still can't find the DLLs.
Any assistance with this problem would be greatly appreciated!
This happens because the managed dlls get shadow copied to a temporary location under the .NET Framework directory. See http://msdn.microsoft.com/en-us/library/ms366723.aspx for details.
Unfortunately, the unmanaged dlls do NOT get copied and the ASP.NET process won't be able to find them when it needs to load them.
One easy solution is to put the unmanaged dlls in a directory that is in the system path (type "path" at the command line to see the path on your machine) so that they can be found by the ASP.NET process. The System32 directory is always in the path, so putting the unmanaged dlls there always works, but I would recommend adding some other folder to the path and then adding the dlls there to prevent polluting the System32 directory. One big drawback to this method is you have to rename the unmanaged dlls for every version of your application and you can quickly have your own dll hell.
As an alternate to putting the dll in a folder that is already in the path (like system32) you can change the path value in your process by using the following code
System.Environment.SetEnvironmentVariable("Path", searchPath + ";" + oldPath)
Then when LoadLibrary tries to find the unmanaged DLL it will also scan searchPath. This may be preferable to making a mess in System32 or other folders.
Try putting the dlls in the \System32\Inetsrv directory. This is the working directory for IIS on Windows Server.
If this doesn't work try putting the dlls in the System32 directory and the dependency files in the Inetsrv directory.
Adding to Matt's answer, this is what finally worked for me for 64-bit server 2003 / IIS 6:
make sure your dlls / asp.net are the same version (32 / 64 bit)
Put the unmanaged dlls in inetsrv dir (note that in 64 bit windows, this is under syswow64, even though the sys32/inetsrv directory is created)
Leave the managed dlls in /bin
Make sure both sets of dll's have read/execute permissions
Take a look with FileMon or ProcMon and filter on the names of the troublesome DLLs. This will show you what directories are scanned in search of the DLLs, and any permission issues you might have.
Another option is embedding the native DLL as a resource in the managed DLL. This is more complicated in ASP.NET, as it requires writing to temporary folder at runtime. The technique is explained in another SO answer.
Always worth checking the path variable in your environment settings too.
I have come across the same issue. And I tried all above options, copying to system32, inetpub, setting path environment, etc nothing worked.
This issue is finally resolved by copying unmanaged dll to the bin directory of web application or web service.
Аfter struggling all day over this problem and finally I found a solution which suits me. It's just a test, but the method is working.
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();
}
}
}
}
}
}
Run DEPENDS on XYZ.dll directly, in the location that you have deployed it to. If that doesn't reveal anything missing, use the fuslogvw tool in the platform SDK to trace loader errors. Also, the event logs sometimes contain information about failures to load DLLs.
On Application_start use this:
(customise /bin/x64 and bin/dll/x64 folders as needed)
String _path = String.Concat(System.Environment.GetEnvironmentVariable("PATH")
,";"
, System.Web.Hosting.HostingEnvironment.MapPath("~/bin/x64")
,";"
, System.Web.Hosting.HostingEnvironment.MapPath("~/bin/dll/x64")
,";"
);
System.Environment.SetEnvironmentVariable("PATH", _path, EnvironmentVariableTarget.Process);
Try putting the DLL in the Windows/SysWOW64 folder. It was the only thing that worked for me.
Related
I am trying to resolve an issue with ASP.Net Framework 4.8 site using EFCore 3.1.16 in IIS. Microsoft.Data.SqlClient has a process lock on SNI.dll which causes issues with xcopy deployment in IIS.
I have tried a strategy of copying the SNI.dll to the same shadow copy location as Microsoft.Data.SqlClient so it doesn't have to try and access the DLL in the bin folder as outlined in https://github.com/lscorcia/sqlclient.snishadowcopy.
// Look for the main Microsoft.Data.SqlClient assembly in the
// shadow copy path
var sqlClientShadowAssembly = Directory.GetFiles(
currentShadowPath, "Microsoft.Data.SqlClient.dll",
SearchOption.AllDirectories).FirstOrDefault();
// Extract the directory information from the shadow assembly path
var sqlClientShadowPath =
Path.GetDirectoryName(sqlClientShadowAssembly);
// Find out the process bitness and choose the appropriate native
// assembly
var moduleName = Environment.Is64BitProcess ? "x86\\SNI.dll"
: "x64\\SNI.dll";
// Compute the source and target paths for the native assembly
var sourceFile = Path.Combine(currentPrivatePath, moduleName);
var targetFile = Path.Combine(sqlClientShadowPath, "SNI.dll");
File.Copy(sourceFile, targetFile);
However, it still tries to access the bin location first instead of the sni.dll that is in the same folder location.
I have checked that the Microsoft.Data.SqlClient in the shadow location is being used correctly by deleting the DLL and confirming that a FileNotFound exception is thrown.I have also tried copying directly into the same folder and also copying into an x64 sub folder in the shadow location.
In my case, the error occured only when my IIS application is located on an UNC path (e.g. "\\myserver\myshare\myapplication"). Here is a workaround that worked in my scenario.
Use P/Invoke to SetDllDirectory:
[DllImport(#"kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool SetDllDirectory(string lpPathName);
(See also this MSDN article about DLL load order)
Then, early in my Global.asax.cs Application_Start method (before any DB calls), call it like this:
SetDllDirectory(Server.MapPath(#"~/bin"))
After that, everything works as expected.
I still to consider this to be kind of a hack/workaround, but at least, it is working now in my scenario.
As I do understand, you can call SetDllDirectory multiple times to add additional directories (i.e. not overwrite the existing one).
So in case someone reading this might have other assemblies that refer to native DLLs in "x86" or "x64" folders, one might do something like this:
var bitness = Environment.Is64BitProcess ? #"x64" : #"x86";
SetDllDirectory(Server.MapPath($#"~/bin/{bitness}"));
I've also tried serving my test application from a local path (like "C:\Inetpub\wwwroot") and here, the error does not occur, even when not calling SetDllDirectory.
I'm still not sure why the error occurs for UNC paths only, and not for local paths, as I would expect that the shadow copied managed assemblies to fail the DllImports, too.
(I've also posted the above in this GitHub issue)
We have a bunch of sample ASP.NET applications. I'd rather not copy the DLLs to the Bin folder in every sample because then all the files have to be updated on a change.
Is there a way to point the ASP.NET app at the references like can be done with a Forms or command line app?
Update: To answer the questions below let me explain a bit better what the situation is here. We have a commercial product that is a set of DLLs. We provide a number of sample applications using it, including several that are ASP.NET apps. All of this, our product DLLs and our sample source, is installed on a user's computer.
When they go to compile one of the samples we want that sample to use the version of the DLLs installed with it. The install process has place in GAC as the default option, but the user can uncheck that if they do not want the DLLs in the GAC and in that case we have to access the DLLs where they were installed, or copy them to each Bin directory.
To add reference right click on bin folder in visual studio and you will find add reference option there.
The question is clear but the background is not.
It makes no sense to me to have a couple of applications which all use the same application dll. Is it just a library used by all applications?
There are some options depending on what you have and what you need:
1. add reference directly
If you really just want to add a reference, right-click on "references" in your VS project and select "add reference". Choose the reference an click ok. After that, right-click the added reference and select "properties" and set "local copy" to false, so it's not copied to the bin folder.
Maybe the option names are a bit different, but I don't have an english VS right now. Should be nearly what you have to do.
EDIT
In this window set "local copy" to "false" (here shown as "Lokale Kopie", sorry, it's german).
2. put dlls in GAC
If you have a shared code library for a couple of applications on the same server you could think about putting that library into the GAC.
3. problems with source control
As mentioned from IrishChieftain you maybe use source control and don't want to update the dlls in the bin folder every time. The solution for that would be just ignoring the bin folder in your source control.
You could add any references you'd like using the context menu on the References node of your project:
In the 'Add Reference' dialog click on the Browse tab and you can browse to and add any outside references you need. For common assemblies we usually put them in a common location on the file system and then reference them from that location whenever we need to.
There are occasions where you will not want to reference certain assemblies at design-time. There is an avenue for you to load them, based on logic, at run-time with the AppDomain.AssemblyResolve event handler. If you have assemblies that you can reference through a level of abstraction and that you do not want to reference directly you can use the following pattern to omit them and execute some algorithm at run-time to find and then add the assembly to the AppDomain.
This is a snippet that I put in the global.asax, Application_Start handler:
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
// use this space to add logic to find your assemblies
return null; // will throw a Dependency-related exception
}
Let me know if there are any questions or if I am missing the point of your question.
-G
I have an Asp.Net web site (there is no .csproj file).
This web site's source code is controlled by SVN.
I excluded the bin folder from the source control.
All the external assemblies are referenced from the DLL folder which is at the root of my SVN.
I try to deploy this website with cruisecontrol.net.
The problem :
Cruise control load all the files from subversion, it runs msbuild.exe against the .sln file. This results in an error : can't find the external assemblies (because the bin folder is excluded).
The solution I found so far :
Before my msbuild task , do a robocopy of the dll from my source control to the /bin folder.
Is there any other solution ? (I don't want to edit my configuration every time I add an external assembly to my project).
EDIT :
I finally used the "refresh file" technique here is the program I used to create them
class Program
{
private const string PATH_BIN = #"F:\WebSite\bin\";
private const string PATH_REFERENCE = #"C:\DLL\";
static void Main(string[] args)
{
foreach (string aFile in Directory.GetFiles(PATH_BIN))
{
if(!aFile.EndsWith(".dll"))
continue;
string pathRefreshFile = aFile + ".refresh";
if(File.Exists(pathRefreshFile))
continue;
string referenceFilePath = PATH_REFERENCE+Path.GetFileName(aFile);
if (!File.Exists(referenceFilePath))
continue;
using (StreamWriter sw = new StreamWriter(pathRefreshFile))
{
sw.Write(referenceFilePath);
}
}
}
}
I'm assuming that you have .dll files that are third party, rather than ones created by class library projects that you could just include in your solution and add as a project reference. We have a similar scenario with items like the Microsoft Anti-Xss library and other common third party .dlls that we reference in almost all our web apps.
Instead of excluding the \bin directory, exclude *.dll. This way, when you add a reference to the .dll, Visual Studio will add the .dll to the \bin directory AND a .dll.refresh file to the \bin directory.
That.Refresh file tells Visual Studio where to grab the .dll file from. As long as the original .dll is in that location then you should be able to build on the build server OR on a brand new developer's PC.
For example, we have a \shared\commonDlls directory in source control, where the .dlls are checked into source control.
All of our other apps reference these when needed. As long as these are checked in, when the build server builds your project, it will know to go tot he \shared\commonDlls directory to copy that dll in.
This enables us to have one master source for the .dll files so that we don't have to copy it into a few dozen different web apps when it's time to upgrade. We put it into our shared\commonDlls directory (overwriting the original), check it in, and all of our web apps are now using the latest version.
edit - added - links to answers on related topics that have bearing on this answer:
SVN and binaries
How manage references in SVN/Visual Studio?
Best Practice: Collaborative Environment, Bin Directory, SVN
Edit - added - section from our ccnet.config file pointing to a .sln file
This is probably not needed, but since I mentioned ointing ot the .sln file in my comment below I thought I'd add it.
<msbuild>
<description>DonationRequests</description>
<workingDirectory>D:\dev\svn\trunk\Intranet\DonationRequests</workingDirectory>
<executable>C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe</executable>
<projectFile>DonationRequests.sln</projectFile>
<buildArgs>/p:Configuration=Debug /v:diag</buildArgs>
<timeout>900</timeout>
<logger>C:\Program Files\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll</logger>
</msbuild>
public const string LIB_GVC = "gvc.dll";
public const string LIB_GRAPH = "graph.dll";
public const int SUCCESS = 0;
[DllImport(LIB_GVC)]
public static extern IntPtr gvContext();
Later, in the main method I call gvContext() and it throws the DllNotFoundException. In my project, I have gone into the Project->Properties and set the reference paths so that I have a folder called "resources" which contains all my DLLs including gvc.dll. I thought this would do the trick but it didn't. What do I need to do?
Note: I cannot use Add Reference as I normally would, I realize that this behavior is normal considering Graphviz is not a C# library. I'm also a bit fuzzy on terminology, why is it called an "unmanaged DLL"? It seems to be because it wasn't compiled from C# code but is that true/not the whole story?
I'm following this tutorial if it helps clarify anything.
The problem is the executable is not finding the path to the executable. Try placing in the /bin folder after you ahve it running and see it works. If so, you resources folder is either a) not found or b) you have a copy operation on compile that is not set up correctly.
As for "what is unmanaged", COM and Native components have their memory handled either by the library itself (native always, COM may be handled by a runtime in some instance) or by something other than .NET. The CLR cannot manage the memory usage, as they are not .NET components. That is why they are called "unmanaged".
I have a native C++ DLL which is used as a plug-in in another application. This DLL has an embedded manifest and depends on a private assembly located in a folder external to the application. The application fails to load my plug-in DLL because my DLL depends on a private assembly which is never found (it is not located in the application directory, nor in the winsxs folder but in my plug-in directory whose location is not controlled by the application). The question is: how is it possible to make the system find my private assembly which is located in my own specific directory ? As an analogy, I would need an equivalent of setDllDirectory() but for assemblies. Or another way so that the system find my private assembly.
Constraints:
Because my DLL is a plug-in, I cannot install anything in the directory and sub-directories of the application. I also cannot modify the behavior of the application.
I would like to avoid sharing the assembly in winsxs.
I also have to use an assembly and not a simple DLL (that I could load with LoadLibrary) to avoid version conflicts.
Thanks.
you need to have a global location where you could give a list of directories that form your (from java-terminology) class-path. Once you have this list just traverse the directories looking for dll's.
I have used the Registry in windows for this. An environment variable or the /etc folder under linux would also do.
This is how linkers do it, and this is one way that the .NET GAC does (it specifies a list of directories in a registry key).
-- edit 1 --
Your problem seems quite a uncomfortable mix, it's like trying to eat with a straight jacket on :P. Why don't you drop the assembly requirement and have a function in a native DLL that returns a version identifier (name-version), and then have some application logic to determine if it may use the DLL.
For example file-name (fn) = monkey-1-1 and a function call returns a struct:
struct version-id
{
const char * name;
int major_number;
int minor_number;
};
Then fn monkey-1-2 would not conflict on file-name or internal conversioning. and your programming conventions or application logic would make sure no adverse collisions occur.