How a native C++ plug-in DLL can load a private assembly located in a specific directory - assemblies

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.

Related

IIS Shadow Copied DLL accessing dependency DLLs from bin location

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)

Native DLL dependencies with ASP.NET MVC Projects

EDIT: I found a way to get it to work locally, but on Azure I still get System.IO.FileNotFoundException on that assembly.
My question might seem like a duplicate to this question here. But it is slightly different, I have already tried that solution and it did not work. Here are the details.
I have an ASP.NET MVC App that has a Reference added to a third party CLR DLL. That third-party DLL requires a native DLL which it invokes. Now if I had control over where the Shadow Copying occurs and what is copied, I would be in paradise. The Shadow Copying misses copying that native DLL despite it's Build Action set as Content and Copy To Output Dir set as Copy Always.
So I searched internet and ran into this discussion on SO, which is same as what was mentioned earlier. I tried adding the code that sets the PATH Environment Variable inside Application_Init and Application_Start of Global.asax, I set the breakpoints in both the methods and to my surprise I get the ASP.NET Error Page before it even hits the breakpoint. This leads me to believe that the referenced assembly at the time of binding hits the native DLL and invokes it. What can I do? Can I delay the reference binding somehow?
EDIT: Yes we can, I opened the Referenced DLL's code which was written in Managed C++, I adjusted the linker setting to Delay Load the Native DLL and now my Application_Start executes first. Yayy! but that does not solve the same problem I am having on Azure
Here is the test solution with DLLs
Here is the source code for the Native DLL
Here is the source code for the Referenced Assembly that uses the Native DLL
To download the Native DLL distribution, Go to their distribution page, choose the windows archive with the bitness you desire (I am using 32-bit), and you will find amzi.dll inside APIs/bin directory.
Actual problem was the wrapper DLL not recognized on Azure server because of lack of support of earlier frameworks and toolsets, as well as Debug CRT.
I used XDT/Application_Start to set the PATH environment variable to include the location of my native DLL
I upgraded my Managed C++ Wrapper DLL to use Toolset 14.0 and .NET 4.6.2
Used Linker Setting of /DELAYLOAD on Managed C++ Wrapper DLL
After downloaded the DLLs and source code which you provided, I found that the native DLL depends on x64 platform. Firstly, we need to change the Platform property of our web app to x64 using Azure portal. If the platform button is disabled, you need to scale up your web app plan to Basic level plan or higher level.
In addition, the original path may end with “;”, so we need to check whether it contains “;” and append right content to it. Code below is for your reference.
string path = Environment.GetEnvironmentVariable("PATH");
Trace.TraceError(path);
string binDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Bin");
Trace.TraceError(binDir);
if (path.EndsWith(";"))
{
Environment.SetEnvironmentVariable("PATH", path + binDir);
}
else
{
Environment.SetEnvironmentVariable("PATH", path + ";" + binDir);
}
To test whether the path is set successfully, you could add a page to test it.
public ActionResult GetPath()
{
string path = Environment.GetEnvironmentVariable("PATH");
return Content(path);
}
After path is set, the native DLL can be load successfully on my side.
On my end I added a throw new ApplicationException("Test") at the beginning of Application_Start and instead of getting my test exception, I was getting the DLL load error.
It means the setting path code will not executed. To fix it, you could remove the native DLL reference from your web application. Now your application could work fine and set the path environment variable. Then you could add the native DLL reference back.
Another way to do it is that we could create a webjobs and set the path environment variable in webjobs and deploy this webjobs before deploying your web application.
I am using 32-bit distributions, my native dlls depends on x86/32-bit.
If you use 32-bit distributions and the platform targets of your CLR DLL and your web application are set to "x86 or Any CPU", you won't need to change platform to x64 in web app. Please change it back to x86.

How does DllImport in ASP.Net look for the DLL?

Is it documented somewhere how ASP.Net sets up search paths for native DLLs? I need to be able to replicate the logic in my own code.
For more background: I'm maintaining a managed library (say Managed.DLL) that wraps a native library (say Native.DLL) that in turn uses another native DLL (say Driver.DLL). So far Managed.DLL has been importing functions from Native.DLL using .Net's DllImport attribute, but now I have to change this to hand-coded calls to LoadLibrary and GetProcAddress to get more control; in particular, I need to be able to call FreeLibrary to unload Native.DLL and I can't do this when Native.DLL has been loaded via DllImport.
And here comes the problem: While just DllImport("Native.DLL") is sufficient to locate both Native.DLL and Driver.DLL, calling LoadLibrary("Native.DLL") fails with ERROR_FILE_NOT_FOUND when Managed.DLL is used in an ASP.Net application, because the directory containing Managed.DLL is not on the search path for native code DLLs.
My first thought was to use Assembly.GetExecutingAssembly().Location and then issue the LoadLibrary call with a full path, but then Native.DLL fails to find Driver.DLL, because the directory containing them both is still not on the search path.
I could work around this by using the Assembly.GetExecutingAssembly().Location value to set the native DLL search path with SetDllDirectory, but this has two major drawbacks:
1) SetDllDirectory changes global WinAPI settings and could interfere with other code within the same ASP.Net worker process that also uses native code DLLs; I have also verified that using DllImport attribute does not mess with this setting, so changing it now could indeed break something that was working before.
2) It would still not work for debugging ASP.Net applications from within Visual Studio, because VS copies the managed resources into a temporary directory, but leaves the native DLLs in the project build directory, so they end up in different locations in the debugging session (and the temporary directory is wiped for every debugging session, so copying the native DLLs into it manually does not work either; I had to copy the native DLLs into IIS's directory for the debugging session to find them and this is clearly not acceptable solution).
I really would like to do the compatible thing here, but so far haven't been able to find out what this is and after couple of days of fruitless searches any pointers would be greatly appreciated.
To answer my own question:
1) The keyword I was missing regarding the copying of Managed.DLL was "shadow copying" (James Schubert explains it much better than any official Microsoft documentation I've seen) and the trick is to use Assembly.CodeBase instead of Assembly.Location, because the former gives the the original location of Managed.DLL and the latter the location of the shadow copy (John Sibly and Sneal shared nice code snippets to extract the directory name from the URI in Assembly.CodeBase).
2) The way to make dependencies of the native DLLs available is to explicitly load them using LoadLibrary before they are needed (and since this increments their reference counts, also release them with FreeLibrary when done).
So, the loading sequence is
string dir = Assembly.GetExecutingAssembly().CodeBase;
dir = new Uri(dir).LocalPath;
dir = Path.GetDirectoryName(dir);
IntPtr driver = LoadLibrary(dir + Path.DirectorySeparatorChar + "Driver.DLL");
IntPtr managed = LoadLibrary(dir + Path.DirectorySeparatorChar + "Managed.DLL");
and the unloading sequence
FreeLibrary(managed);
FreeLibrary(driver);
(note also the order of LoadLibrary and FreeLibrary calls).

User Defined class typedef in unmanaged code in IIS causing hangup but not in VS

Background: I have a DLL I created that includes 2 c files. These c files reference a third c file which defines a user defined type (we'll call it class_pointer), which is a pointer of type class.
E.g.
typedef class pointer_class *class_pointer;
then defines the class:
typedef class pointer_class {..}
pointer_class has various variables and functions associated with it that the original 2 c files make use of through class_pointer.
I am using this DLL in an ASP.NET C# web application. I am using PInvoke to import the functions into the dll. However, when I go to call on these functions that involve the class_pointer, the website running on IIS hangs. This does not happen in the VS debugger. If I comment out said class_pointers, everything runs smoothly -- I have access to the DLL and everything.
I have tried changing the permissions on all the DLLs included in my bin directory (just to be safe) for NETWORK SERVICE to have read/execute permissions. The dll will work without the class_pointers, so I don't think it is an issue of permissions. Does anyone have any advice on what might be causing IIS to hang when these class_pointers are involved?
I finally was able to figure this out with the help of Microsoft's debugging tools.
The class_pointers were written by another developer that has since left the place I work. In the pointer_class, there was a function to get the current application path. When running on the web, this was set to the inetsrv directory in SYSWOW64 (The machine I was running on was a 64bit machine). To solve the issue, we set the application path to the website when we are running the web, rather than where the .exe application was running from (SYSWOW64/inetsrv).
Because the application path was wrong, the native dll was unable to load some files in and was putting up popup warning messages. These pop up messages were waiting for a user response and since we couldn't get one on the web, the application hanged!
Hope this helps someone else out there!

Visual Studio DllNotFoundException

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".

Resources