dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.19k stars 4.72k forks source link

C++/CLI libraries may fail to load due to `ijwhost.dll` not being on the search path #38231

Open elinor-fung opened 4 years ago

elinor-fung commented 4 years ago

To support C++/CLI libraries in .NET Core, ijwhost was created as a shim for finding and loading the runtime. All C++/CLI libraries are linked to this shim, such that ijwhost.dll is found/loaded when the C++/CLI library is loaded.

By default, Windows' DLL search will look for dependencies of a DLL as if they were loaded with just the module name. This means that, depending on how a host application loads the C++/CLI library, the C++/CLI library may fail to load due to ijwhost.dll not being found. In order for the load to work one of the following needs to be true:

C++/CLI libraries cannot dictate how their hosts load them and authors should not need to be concerned with how the ijwhost shim is found and loaded. The runtime/tooling should make the usage of ijwhost hidden to the user such that it just works without user intervention.

In .NET Framework, the equivalent shim (mscoree/mscoreei) is system-wide, so this was not an issue.

Potential options:

@jkoritzinsky @AaronRobinsonMSFT @vitek-karas

ghost commented 4 years ago

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

jkoritzinsky commented 4 years ago

Would it be possible to ask the C++/CLI project system to include an assembly manifest like the one in your workaround for #37972 in either their templates or automatically as part of the build?

Ninds commented 4 years ago

Hello, I am observing the same issue when building Excel XLL's against .NET 5 (preview) with a modified XLW. XLW has a hook to add the containing directory to the PATH :

xlw::PathUpdater::PathUpdater()
{
    MEMORY_BASIC_INFORMATION theInfo ;
    HMODULE theHandle = NULL;
    char theDLLPathChar [MAX_PATH + 1] = "";
    DWORD dwRet = 0;
    std::string originalPathValue(StringUtilities::getEnvironmentVariable("PATH"));
    bool ok(!originalPathValue.empty());

    dwRet = static_cast<DWORD>(VirtualQuery (((LPCVOID)this), &theInfo,(static_cast<DWORD> (sizeof (MEMORY_BASIC_INFORMATION)))));
    if (dwRet)
    {
        theHandle = ((HMODULE) (theInfo.AllocationBase));
        GetModuleFileName (theHandle, theDLLPathChar , MAX_PATH);
        xlw::XlfServices.StatusBar = theDLLPathChar;
    }
    else
    {
        ok = false;
        std::cerr << XLW__HERE__ <<" Could not attain path of DLL" << std::endl;
    }
    if(ok)
    {
        std::string theDLLPath(theDLLPathChar);
        std::string newPathValue(originalPathValue);
        std::string::size_type pos = theDLLPath.find_last_of("\\");
        newPathValue+= ";"+theDLLPath.substr(0,pos);

        if (!SetEnvironmentVariable("Path", newPathValue.c_str()))
        {
            std::cerr << XLW__HERE__ << " SetEnvironmentVariable failed to set PATH" << std::endl;
            ok = false;
        }
        else
        {
            std::cerr << XLW__HERE__ << " PATH set successfully " << std::endl;
        }
    }
    if(!ok)
    {
        std::cerr << XLW__HERE__ << " Warning: Unable to initialise PATH to directory of library " << std::endl;
    }
} 

This is executed quite early on when the XLL is loaded and works for most other dependant dlls that need to be found. It does not work for ijwhost.dll however.

For some reason it's too late. Setting the PATH variable explicitly and externally makes everything work as expected.

For .NET Core 3.1 we don't even get that far :

1>C:\Program Files\dotnet\sdk\5.0.100-preview.5.20279.10\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.targets(565,5): error NETSDK1114: Unable to find a .NET Core IJW host. The .NET Core IJW host is only available on .NET Core 3.1 or higher when targeting Windows.

AaronRobinsonMSFT commented 4 years ago

/cc @agocke

mikeoliphant commented 3 years ago

This is still an issue for me with .NET 5.0 and VS 16.8.2.

I resolved it by adding a linker manifest to handle "Ijwhost.dll", but it took me hours of troubleshooting and scouring the internet to figure that out. It would be great if it could just be handled automatically - ideally with "Ijwhost.dll" going away - either being statically linked or through some other mechanism.

This is part of a larger problem of C++/CLI being unfriendly for plugin architectures. If your dll is being loaded by a host, the default assembly load path isn't where your plugin is, so you have to implement an AssemblyLoadContext resolve handler to load assemblies. It works - but it is an extra step that takes time to figure out and implement.

This is stuff that should "just work", particularly with .NET Core becoming the new mainstream.

vitek-karas commented 3 years ago

@mikeoliphant could you please describe in a bit more details this:

If your dll is being loaded by a host, the default assembly load path isn't where your plugin is

I don't think I follow what is the problem here. Is it that loading plugins requires a resolver (regardless of C++/CLI being used or not)? I don't see how C++/CLI makes this different.

mikeoliphant commented 3 years ago

I don't think I follow what is the problem here. Is it that loading plugins requires a resolver (regardless of C++/CLI being used or not)? I don't see how C++/CLI makes this different.

I hadn't thought about that, but it makes sense that the path issue would still exist in a purely managed context. In C++/CLI the situation is exacerbated by the issue with "Ijwhost.dll".

datduyng commented 2 years ago

I am facing this same issue. Our .net6 core web server runnning on Window nano server 1809 is taking dependency on a c++ library. when running the NanoServerApiScanner.exe it says that ijwhost.dll is missing

image

When searching for the ijwhost.dll file there are many instance of this dll found

image

Any help would be greatly appreciated

Ruffnik commented 2 years ago

I got this solved by including the ijwhost.dll in the linker manifest

bj-rn commented 7 months ago

I resolved it by adding a linker manifest to handle "Ijwhost.dll", but it took me hours of troubleshooting and scouring the internet to figure that out.

@mikeoliphant do you have that process documented somewhere?

mikeoliphant commented 7 months ago

I resolved it by adding a linker manifest to handle "Ijwhost.dll", but it took me hours of troubleshooting and scouring the internet to figure that out.

@mikeoliphant do you have that process documented somewhere?

LinkManifest