Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ijwhost.dll loading not always working for C++/CLI assembly. #37972

Closed
obiwanjacobi opened this issue Jun 16, 2020 · 12 comments
Closed

Ijwhost.dll loading not always working for C++/CLI assembly. #37972

obiwanjacobi opened this issue Jun 16, 2020 · 12 comments

Comments

@obiwanjacobi
Copy link

I am working on a solution where managed plugins are loaded into (3rd party) native host applications. The managed plugins use a C++/CLI interop library (part of my project) to let the native host recognize it as an native plugin and load it. That interop library then loads the managed plugin and marshals the calls between host and plugin. All plugin files are deployed into a single directory (ala dotnet publish) - including ijwhost.dll. So far so good.

But some hosts fail to load the plugin. I am uncertain of the reason why but I think it has to do with where the current directory is pointing to? Anyway when I copy the ijwhost.dll into the host's .exe location, it works again. In this situation setting the environment variables to do a core-host trace does not produce any results - not even a trace file. Which seem to indicate that the whole ijwhost-mechanism is not starting up (I have no knowledge of how that works in detail).

I am looking for ideas and suggestions of how I could try to fix this problem because having to copy a dll into a 3rd parties install folder is very 'suboptimal' for me and all the people that will use products build with my library.

Here is the project source code and ask if you need more info or want me to try something.

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Jun 16, 2020
@Dotnet-GitSync-Bot
Copy link
Collaborator

I couldn't figure out the best area label to add to this issue. Please help me learn by adding exactly one area label.

@ghost
Copy link

ghost commented Jun 16, 2020

Tagging subscribers to this area: @vitek-karas, @swaroop-sridhar
Notify danmosemsft if you want to be subscribed.

@elinor-fung
Copy link
Member

Would you be able to point at an example host where loading the C++/CLI library fails?

It might also be useful to run something like ProcMon and check that C++/CLI library is loaded, where it looks for ijwhost.dll, and that it is indeed failing to find ijwhost.dll.

@elinor-fung
Copy link
Member

elinor-fung commented Jun 18, 2020

cc @jkoritzinsky

Okay, I think I found a host (validator.exe) based on your project's documented issues.

Looking through that loading code, it is simply doing a LoadLibraryW. Windows' DLL search will look for dependencies of a DLL as if they were loaded with just the module name. In this case, that means it is looking for ijwhost.dll in the default search paths (hence why copying it next to the host executable works).

I suspect this is going to be an issue with any host that loads a C++/CLI library (from a directory not on the default search path) without specifying either LOAD_WITH_ALTERED_SEARCH_PATH or LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR flags such that dependencies are searched for in the same directory as the C++/CLI library.

If the above is correct, this would be a rather unfortunate, since ideally C++/CLI library authors should not need to deal directly with ijwhost.dll (aside from making sure it is deployed alongside the library).

@elinor-fung
Copy link
Member

elinor-fung commented Jun 18, 2020

@obiwanjacobi Running ProcMon could still be useful to check that the above is the issue. Another thing to try (sorry, I know it is not nice / pretty):

  • Specify to delay load ijwhost.dll in your C++/CLI project
  • Add a failure hook in your C++/CLI project and explicitly handle ijwhost.dll - e.g.
    FARPROC WINAPI delayLoadFailureHook(unsigned dliNotify, PDelayLoadInfo pdli)
    {
        if (dliNotify != dliFailLoadLib || ::strcmpi(pdli->szDll, "ijwhost.dll") != 0)
            return NULL;
    
        // Get full path to ijwhost.dll based on current module path
        const char *path = <...>;
        return (FARPROC)::LoadLibraryA(path);
    }
    
    const PfnDliHook __pfnDliFailureHook2 = delayLoadFailureHook;

That should make it so that when ijwhost.dll isn't found, the failure hook in your C++/CLI library can load it from the directory containing your C++/CLI library.

@jkoritzinsky I know you worked on ijwhost and getting the C++ team to consume it - thoughts on if anything could be done here?

@obiwanjacobi
Copy link
Author

I added ijwhost.dll as delay loaded in the linker options - confirmed /DELAYLOAD:"Ijwhost.dll" was present on the linker command line.

Added the code:

#include "pch.h"
#include <delayimp.h>

extern "C"
{
     FARPROC WINAPI delayLoadFailureHook(unsigned dliNotify, PDelayLoadInfo pdli)
    {
        if (dliNotify != dliFailLoadLib || 
            _strcmpi(pdli->szDll, "ijwhost.dll") != 0) {
            return NULL;
        }

        ::OutputDebugStringA("Loading Ijwhost.dll");
        
        const char* path = "C:\\Users\\marc\\Documents\\MyProjects\\public\\Jacobi\\Public\\GitHub\\vst.net\\Source3\\Code\\x64\\Debug\\Ijwhost.dll";
        return (FARPROC)::LoadLibraryA(path);
    }

    const PfnDliHook __pfnDliFailureHook2 = delayLoadFailureHook;
}

I build everything in Debug-x64 and ran the validator.exe to test.

This (debug) output indicates that Ijwhost.dll is loading, but now the C++/CLI assembly is a 'bad format'?
Note that the C++/CLI Interop project is renamed to TestPlugin.vst3 => they're the same assembly.

... <more here - looks like standard windows dlls> ...

'validator.exe' (Win32): Loaded 'C:\Users\marc\Documents\MyProjects\public\Jacobi\Public\GitHub\vst.net\Source3\Code\x64\Debug\Jacobi.Vst3.TestPlugin.vst3'. 
'validator.exe' (Win32): Loaded 'C:\Users\marc\Documents\MyProjects\public\Jacobi\Public\GitHub\vst.net\Source3\Code\x64\Debug\Ijwhost.dll'. 
'validator.exe' (Win32): Loaded 'C:\Program Files\dotnet\host\fxr\3.1.5\hostfxr.dll'. 
'validator.exe' (Win32): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.5\hostpolicy.dll'. 
'validator.exe' (Win32): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.5\coreclr.dll'. 
'validator.exe' (Win32): Loaded 'C:\Windows\System32\oleaut32.dll'. 
'validator.exe' (Win32): Loaded 'C:\Windows\System32\bcrypt.dll'. 
The thread 0x7acc has exited with code 0 (0x0).
'validator.exe' (Win32): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.5\System.Private.CoreLib.dll'. 
'validator.exe' (CoreCLR: DefaultDomain): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.5\System.Private.CoreLib.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'validator.exe' (Win32): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.5\clrjit.dll'. 
'validator.exe' (CoreCLR: clr_libhost): Loaded 'C:\Users\marc\Documents\MyProjects\public\Jacobi\Public\GitHub\vst.net\Source3\Code\x64\Debug\Jacobi.Vst3.TestPlugin.vst3'. 
Exception thrown at 0x00007FFFC84EA799 in validator.exe: Microsoft C++ exception: HRException at memory location 0x0000001346EFA778.
Exception thrown at 0x00007FFFC84EA799 in validator.exe: Microsoft C++ exception: [rethrow] at memory location 0x0000000000000000.
Exception thrown at 0x00007FFFC84EA799 in validator.exe: Microsoft C++ exception: HRException at memory location 0x0000001346EFA778.
Exception thrown at 0x00007FFFC84EA799 in validator.exe: Microsoft C++ exception: [rethrow] at memory location 0x0000000000000000.
Exception thrown at 0x00007FFFC84EA799 in validator.exe: Microsoft C++ exception: HRException at memory location 0x0000001346EFA778.
Exception thrown at 0x00007FFFC84EA799 in validator.exe: Microsoft C++ exception: [rethrow] at memory location 0x0000000000000000.
Exception thrown at 0x00007FFFC84EA799 in validator.exe: Microsoft C++ exception: HRException at memory location 0x0000001346EFA778.
Exception thrown at 0x00007FFFC84EA799 in validator.exe: Microsoft C++ exception: Exception at memory location 0x0000001346EFCBC8.
'validator.exe' (Win32): Loaded 'C:\Windows\System32\version.dll'. 
An unhandled exception of type 'System.BadImageFormatException' occurred in System.Private.CoreLib.dll
Could not load file or assembly 'Jacobi.Vst3.Interop, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. An attempt was made to load a program with an incorrect format.
Unhandled exception. System.BadImageFormatException: Could not load file or assembly 'Jacobi.Vst3.Interop, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. An attempt was made to load a program with an incorrect format.
File name: 'Jacobi.Vst3.Interop, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' ---> System.BadImageFormatException: An attempt was made to load a program with an incorrect format. (0x8007000B)
   at System.Runtime.Loader.AssemblyLoadContext.LoadFromInMemoryModuleInternal(IntPtr ptrNativeAssemblyLoadContext, IntPtr hModule, ObjectHandleOnStack retAssembly)
   at System.Runtime.Loader.AssemblyLoadContext.LoadFromInMemoryModule(IntPtr moduleHandle)
   at Internal.Runtime.InteropServices.InMemoryAssemblyLoader.LoadInMemoryAssembly(IntPtr moduleHandle, IntPtr assemblyPath)

Interesting to note is that I don't see my OutputDebugString in the debug output...?

I am out of my league here... Please advice. ;-)

@jkoritzinsky
Copy link
Member

Try adding a #praga unmanaged before that code block and a #praga managed after. Otherwise the compiler might make that function managed, which would break.

@obiwanjacobi
Copy link
Author

#pragma unmanaged
extern "C"
{
    ... same code as before
}
#pragma managed

Same result.

@elinor-fung
Copy link
Member

Boo, it looks like linking with /delayload causes us to be unable to load it library as a managed assembly.

I tried another workaround using assembly manifests. This seems to work with my simple repro.

Added an additional manifest for embedding (if going through VS, project properties > Manifest Tool > Input and Output > Additional Manifest Files) with the contents:

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <file name="ijwhost.dll"/>
</assembly>

@elinor-fung
Copy link
Member

Filed #38231 for discussion of creating a long-term solution.

@obiwanjacobi
Copy link
Author

Yep - works for me.
Thank you very much!

@agocke
Copy link
Member

agocke commented Jul 6, 2020

Closing as dup of #38231

@agocke agocke closed this as completed Jul 6, 2020
@agocke agocke removed the untriaged New issue has not been triaged by the area owner label Jul 6, 2020
Ninds added a commit to xlw/xlw that referenced this issue Jul 26, 2020
amaitland added a commit to cefsharp/CefSharp that referenced this issue Aug 14, 2020
Add assembly manifest for VC++ projects to prevent loading issues of ijwhost.dll

dotnet/runtime#38231
dotnet/runtime#37972 (comment)

Not sure exactly how to test this works as expected

Reference
https://docs.microsoft.com/en-us/windows/win32/sbscs/assembly-manifests

#3197
@ghost ghost locked as resolved and limited conversation to collaborators Dec 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants