Search code examples
c#header-filesdllimportunmanaged

How can a C# library depend on an unmanaged DLL whose name will only be provided later in the final application?


In C you can compile a static library which depends on a C header defining several functions. You don't need to know which library will end up implementing those functions, so your static library can be implementation agnostic. As long as you provide a valid implementation you can link the static library into the final executable.

How can I do something similar in C#? I want to make a library which depends on a common API that could be implemented by several C++ DLLs. (To be clear, I do mean dynamic linking, not static linking like my analogy above.) But to use DllImport and P/Invoke I have to provide the DLL name. Obviously I have to do that at some point, but I'd like to create a class library DLL which depends on an API from an at-that-stage-unspecified DLL, and then only provide the name of that unmanaged DLL in the final application project.


Solution

  • Name you provide to [DllImport] must be compile time constant, so you can't pass "dynamic" value here. However, you can use some default or even dummy name and then resolve this name at runtime to the valid library, using NativeLibrary.SetDllImportResolver.

    For example, in one project I use native C libgphoto library. On windows I include it into distribution precompiled and so happens that functionality I need is split between 2 dlls named "libgphoto2-6.dll" and "libgphoto2_port-12.dll". However on linux and macos - you can install this library from package and all functionality will be in library named just "gphoto".

    So then I define my imports using "windows" dll names:

    private const string LibGPhotoName = "libgphoto2-6";
    [DllImport(LibGPhotoName)]
    public static extern int gp_widget_get_id(IntPtr widget, out int id);
    

    But then if I'm not on windows I do:

    NativeLibrary.SetDllImportResolver(typeof(GPhoto2).Assembly, (name, asm, search) => {
        if (name == "libgphoto2-6" || name == "libgphoto2_port-12")
        {
            return NativeLibrary.Load("gphoto2", asm, search);
        }
        return IntPtr.Zero;
    });
    

    So if runtime wants to load native library "libgphoto2-6" or "libgphoto2_port-12" - I tell it to load "gphoto2" instead.

    You can do the same in your case.