How does Assembly.LoadFrom resolve unmanaged dependencies? - c#

I'm confused by the behavior of Assembly.LoadFrom. In my application I call Assembly.LoadFrom to a .NET .exe and start it using EntryPoint.Invoke (this odd approach is useful for building a launcher app on non-Windows platforms).
I had assumed that since the assemblyFile was in a different folder, that it would fail to find some managed .dll dependencies that are located in the same folder as it. But it worked; it didn't fail...
It would appear that when I called Assembly.LoadFrom(assemblyFile), it checked the folder containing assemblyFile for managed dependencies of assemblyFile. I didn't expect this. What would happen if that assembly had unmanaged dependencies (such as a DllImport), would it still search that same directory? And is this behavior framework specific?

The Assembly has nothing to do with loading unmanagged libraries. What does the loading for unmanaged libraries is the DllImport call which is lazy (does not load till the first call).
DllImport in turn (in .NET, I don't know what Mono does on other platforms) calls LoadLibary on windows. LoadLibary has a known set of rules for how it resolves it's dependencies:
If a DLL with the same module name is already loaded in memory, the system uses the loaded DLL, no matter which directory it is in. The system does not search for the DLL.
If the DLL is on the list of known DLLs for the version of Windows on which the application is running, the system uses its copy of the known DLL (and the known DLL's dependent DLLs, if any). The system does not search for the DLL. For a list of known DLLs on the current system, see the following registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs.
If those two points are not met and SafeDllSearchMode is enabled (it is by default for for XP SP2 and newer) it uses the following order
The directory from which the application loaded.
The system directory. Use the GetSystemDirectory function to get the path of this directory.
The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
The current directory.
The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.
So to answer your question, no the directory your managed assembly is located in is not searched when looking for unmanaged assemblies, only the directory of the application that loaded your managed assembly.
However all hope is not lost, you can call SetDLLDirectory and add the folder of your managed assembly and it will include it in the search when looking for the unmanaged DLL, it will change the search order to
The directory from which the application loaded.
The directory specified by the lpPathName parameter (in the SetDLLDirectory call).
The system directory. Use the GetSystemDirectory function to get the path of this directory. The name of this directory is System32.
The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. The name of this directory is System.
The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
The directories that are listed in the PATH environment variable.
If you need to add more than one folder for searching see the MSDN for documenation on AddDllDirectory for the steps required to allow for multiple search directories.

None of the .NET specific configuration affects the way unmanaged DLLs are located. Normal operating system search rules are in effect, regardless of how the CLR finds managed assemblies. The underlying OS call on Windows is LoadLibrary(), a winapi function that has no support for altering search rules itself. You can specify a full path name to avoid searching but that's almost never practical in pinvoke.
If this is a problem then you'll have to write explicit code to help the OS locate the file. Common techniques are pinvoking SetDllDirectory(), altering the PATH environment variable with Environment.SetEnvironmentVariable() and changing the default directory by assigning Environment.CurrentDirectory

Related

Not finding DLL When Ran as a Startup Program

My program runs perfectly except when it is launched as a startup program. When launched as a start up program it is not able to find a first party DLL (or one of it's dependencies).
I get the following exception:
Could not find file or assembly X or one of it's dependencies: C:\Windows\SysWow64\X.dll
It is looking for the first party DLL in the C:\Windows\SysWOW64\ directory instead of in the local directory C:\Program Files\MyProgram\.
The confusing part is, if I launch the program manually everything works fine.
How do I go about finding the source of this problem? I tried using Fusion Log but it only told me the same thing as the exception: That it was trying to load from C:\Windows\SysWOW64 only.
I read that this can occur when your application uses Assembly.Load - The culprit program does use Assembly.LoadFrom - but again, this works fine except when done at start up.
Furthermore, the culprit program does have some [DllImport] attributes.
The issue was to do with using Assembly.LoadFrom and the current directory at start up.
The Assembly.LoadFrom states that it:
assemblyFile may be absolute or relative to the current directory, and the assembly is loaded into the domain of the caller.
When the program is set as a start up program the current directory is
C:\Windows\SysWOW64 for whatever reason.
As you can demonstrate by outputting Directory.GetCurrentDirectory().
Instead of using Assembly.LoadFrom which does not follow the normal probing I am using Assembly.Load as this searches the normal directories where DLLs are contained.
The load context contains assemblies found by probing: in the GAC, in a host assembly store if the runtime is hosted, or in the ApplicationBase and PrivateBinPath of the application domain. Most overloads of the Load method load assemblies into this context.
The load-from context contains assemblies for which the user provided a path not included in the directories searched by probing. LoadFrom, CreateInstanceFrom, and ExecuteAssembly are examples of methods that load by path.
This also explains why Fusion was telling me it was only searching for DLLs in C:\Windows\SysWOW64 - as that's what LoadFrom does, only searches in the directory given. Which in my case was the current directory.

Add reference of a .NET library residing in Windows System32 folder

I am using a .NET dll file for image operations and have added it in my C# application using "Add reference" in solution explorer and use its classes. There are many applications use that dll so I do not want to place the dll file in an application specific folder like:
C:\Program Files\<Application Name>\ or
C:\Program Files (x86)\<Application Name>\ or
C:\ProgramData\<Application Name>\ or
C:\Users\<Username>\AppData\
And would like to place the dll file in a common folder like C:\Windows\ or C:\Windows\System32 folder or any other folder that is commonly accessible by all applications in Windows.
It works fine when I put the the dll file in the same folder of the calling exe, but when I place it in C:\Windows\System32 directory, the C# code of my application is not able to locate the dll and gives error:
Unable to load DLL: The specified module could not be found.
(Exception from HRESULT: 0x8007007E)
How do I instruct my code to look for the dll in the system32 directory or in any other common directory?
What other common folder can I use for shared dll and libraries?
This is what the Global Assembly Cache is designed for. As listed in the MS documentation here.
Each computer where the common language run-time is installed has a
machine-wide code cache called the global assembly cache. The global
assembly cache stores assemblies specifically designated to be shared
by several applications on the computer.
But there are pitfalls to using it so be aware of MS's own advice:
You should share assemblies by installing them into the global
assembly cache only when you need to. As a general guideline, keep
assembly dependencies private, and locate assemblies in the
application directory unless sharing an assembly is explicitly
required.

Mixed Assembly Not Discovering Native DLLs

I have a mixed mode dll with native dll dependencies.
I am loading the mixed mode dll from a C# exe using Assembly.Load. However, the location of the mixed mode dll is not in the application bin directory, therefore it fails because it only looks for the native C++ dll's in the bin and the folders in the PATH environment variable.
I thought using the option /assemblylinkresource was suppose to stop this and force the native dll's to be found in the alternate directory alongside the deployed mixed mode dll. This is proving to be not correct.
Is there a way to create a multi-file assembly with native dll's using an existing C++/CLI mixed mode dll? The only examples (http://msdn.microsoft.com/en-us/library/xawyf94k(v=vs.100).aspx) I've seen are using .netmodules coupled with a native dll.
Therefore, the solution is to either:
a) some how force the application to search for native dependencies in a directory of your choosing; or
b) package the native dll's into the one managed mixed mode assembly (is this even possible??) - given statically linking the dependencies is not an option.
Normal Windows DLL searching rules apply. So yes, there's no hope it will ever find those DLLs. A "multi-file assembly" isn't going to work either, you cannot merge native code. Options you have, roughly in preferred order:
Call SetDllDirectory() to add the path that contains the DLLs to the set of directories where Windows will look. May fail if the external code uses it as well.
Use Environment.SetEnvironmentVariable() to append the path to the PATH environment variable. This only changes the process' copy of the PATH so is a reasonable approach. May fail on machines that have a bloated PATH that is reaching the limit.
Set Enviroment.CurrentDirectory to the path with the DLLs. May fail if the external code tinkers with it as well.
Record the path for each DLL at install time in the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs registry key.
Install the DLLs in the Windows side-by-side cache and use a manifest to tell Windows about it. This is hard to get right.
Potential candidate for a solution, not tested with MMA: This is probably not the optimal solution you were hoping for, but I thought I would add it as it could help you along the way to achieve solution a). In c++ you could control loading path and path search order by either manually setting the directory to be searched using SetDllDirectory (only available from XP SP1) or by manually loading the dll using LoadLibraryEx.
I guess one could use P/invokes to get access to these calls in C#
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
private static extern void SetDllDirectory(string lpPathName);

A problem regarding dll inheritance

I have created a dll that will be used by multiple applications, and have created an installer package that installs it to the program files, as well as adds it to the Global Assembly Cache.
The dll itself uses log4net, and requires a xml file for the logging definitions.
Therefore when the installer is run, the following files get copied to the install directory within program files:
The main dll that I developed
- The Log4Net.dll
- the Log4Net.xml file
I am now experiencing a problem. I have created a test console application for experimentation. I have added my dll as a reference, and set the 'local copy' flag to false.
When I compile the test console exe however, I noticed that it has copied the log4net.dll and log4net.xml files to the bin directory. And when running the test console, it appears that it will only work if the log4net.dll is in the same directory as the exe. This is dispite the fact that the test console application does not use log4net, only the dll that was added as a reference does.
Is there some way to have it so that the log4net.dll & xml files used will be the ones that were installed to the program files, rather than any application needed to copy over local copies? The applications that will be using my dll will not be using log4net, only the dll that they are referencing uses it.
Many thanks
Don't install into the Global Assembly Cache! Even if your library dll is used by multiple applications each should have it's own local copy. Otherwise you get into a whole world of pain for saving a few KB of disk space.
Always copy the required dlls locally. If you are really sure that the application won't need it you can simply delete the unnessesary dlls later or don't include them in the installer. But if your application will call ANY reference there it will crash at runtime. So best option is to leave them there (after all they WERE referenced for a reason).
No, it's not possible (at least not without much efford) to have .Net load dlls from arbitrary locations on the disk. And it should be this way (look up DLL-hell if you want to know why).
I suspect your problem is the configuration. You must use fully qualified names if you want it to work from the GAC. As per the documentation at http://logging.apache.org/log4net/release/faq.html:
"When loading an assembly from the GAC the fully qualified assembly name, including the version, culture and public key must be specified. This is in the standard syntax supported by System.Type.GetType. See the next FAQ on how to get the version and public key for an assembly."
I managed to resolve this by adding Log4net.dll to the GAC as well. It will now run without needing a local copy the dll.
It does however require a local copy of the XML file, to correctly log.

.NET Load assemblies at runtime Again

I posted a similar question a time ago. I need to load an assembly at runtime.
This is easy if I know the absolute path of the dll at runtime.
But I dont :( The assembly.Load() or LoadFromFile() fails if the file is not in the application root.
The only thing I have is the dll name. The dll could be located in the root, system32 or in even in the GAC.
Is it possible for .net to automatically determine where the dll is at, like for example :
it should first look in the root. If its not there move on to the system folders, else try the GAC.
EDITED
I am using plug-in architecture. I do not need to register the dll. I have a multi user application. I have a applications table containing information regarding applications. Each application has a dll path associated with it, containing certain algorithms associated with that app. Hope this helps.
I hope you've read up on the following. My suggestion...
How the runtime locates assemblies
You can give Assembly.LoadWithPartialName a whirl.. might work.. it says it will search the application folder and the GAC unlike Assembly.Load. (However its less safe.. coz you might end up with the wrong version of the DLL since you dont specify all 4 parts of the Assembly Name)
Also try AppDomainSetup.PrivateBinPath (AppDomain.AppendPrivatePath has been deprecated in favor of this) to add subfolders of the application root to the list of folders to probe assemblies for. You can also try copying over files from other places into a [AppFolder]\MySandboxForDLLsToLoad, which is added to the PrivateBinPath.
When the current application looks for assemblies, it looks in several locations (bin folder, gac, etc..) if it can not find one, then the developer needs to manually tell the application where to look. You can do this by intercepting the AssemblyResolve event, and using the event args to tell the CLR where your assembly is.
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
....................
Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var strTempAssmbPath=
Path.GetFullPath("C:\\Windows\\System32\\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll");
var strTempAssmbPath2=
Path.GetFullPath("C:\\Windows\\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll");
if (File.Exists(strTempAssmbPath))
return Assembly.LoadFrom(strTempAssmbPath);
if (File.Exists(strTempAssmbPath2))
return Assembly.LoadFrom(strTempAssmbPath2);
}
You can hook up into AppDomain.CurrentDomain.AssemblyResolve event and load the assembly for your application manually on demand.
Yes there is a way and it depends on whether your dll is weakly(without a public key token) or strongly(with a public key token) named. If its weakly named and you have added a reference to the assembly with the dll within VS, then VS will first look in the application folder root and if it does not find it, it will then look in the subdirectories that you specify as the value of the privatePath attribute in your XML config file.
If its a strongly named dll, then the CLR will search in the GAC so you need to install the dll in the GAC. The CLR can also look in the application directory if you install an XML config file that has its codeBase element pointing to the dll's path.
To specify an assembly in the GAC you can use the /reference:[DLL name] switch when compiling your assembly.
The dll will be found automatically is it is in some location referenced in the PATH system variable (i.e., system32) or if the dll is registered. You can't find it if it can be anywhere on disk, but no one needs that functionality anyway.
EDIT: can the dll's be registered? how are you getting the assembly name if you don't know of the assembly ahead of time? Are you creating some type of plug-in architecture? I think it would help if you explained your situation a bit more.
EDIT2: If that is the case, why not provide a way for the user to register his or her plug in? You can get all the information that you need from an open file dialog. That or simply make them dump it into a folder that you specify.
You have three options
Install the assembly in the global assembly cache (GAC)
Use an application configuration (.config) file with the tags
Use the AssemblyResolve event
You can find the details here.
The resolution algorithm used by .NET to find the assemblies and their dependents is straight forward.
.NET identifies the required version. Usually the information about the dependant assemblies is present in the application's assembly manifest.
.NET searches GAC (Global Assembly Cache), only if the assembly is strong named.
If not found in GAC, and if a .config file presnt, then .NET searches the location specified in the cofiguration file, else .NET searches directory containing the executable (.EXE)
After this, If the assembly is still not found, the application terminates with error.
Click here for the video explaining the .net resolution algorithm, and click here for a video on late binding assemblies
Register this assembly into the GAC. How to do that
Use this
Assembly.LoadWithPartialName(assemblyName);

Categories