Setting dllimport programmatically in C#
Asked Answered
N

9

9

I am using DllImport in my solution.
My problem is that I have two versions of the same DLL one built for 32 bit and another for 64 bit.

They both expose the same functions with identical names and identical signatures. My problem is that I have to use two static methods which expose these and then at run time use IntPtr size to determine the correct one to invoke.

private static class Ccf_32
{
    [DllImport(myDllName32)]
    public static extern int func1();
}

private static class Ccf_64
{
    [DllImport(myDllName64)]
    public static extern int func1();
}

I have to do this because myDllName32 and myDllName64 must be constant and I have not found a way to set it at run time.

Does anyone have an elegant solution for this so I could get rid of the code duplication and the constant IntPtr size checking.

If I could set the file name, I would only have to check once and I could get rid of a ton of repeated code.

Nicollenicolson answered 23/8, 2009 at 23:21 Comment(1)
No sense in selecting it at runtime if the difference is is the whole compilation.Time
A
13

You can probably achieve this with the #if keyword. If you define a conditional compiler symbol called win32, the following code will use the win32-block, if you remove it it will use the other block:

#if win32
    private static class ccf_32
    {
        [DllImport(myDllName32)]
        public static extern int func1();
    }
#else    
    private static class ccf_64
    {
        [DllImport(myDllName64)]
        public static extern int func1();
    }
#endif

This probably means that you can remove the class wrapping that you have now:

    private static class ccf
    {
#if win32
        [DllImport(myDllName32)]
        public static extern int func1();
#else    
        [DllImport(myDllName64)]
        public static extern int func1();
#endif
    }

For convenience, I guess you could create build configurations for controlling the compilation symbol.

Allotment answered 23/8, 2009 at 23:28 Comment(2)
Yeah, but I'd like to keep the 'Any CPU' option as opposed to have 32 and 64 bit versions.Nicollenicolson
While it works, this approach still results in the code duplication the original poster wants to avoid, plus restricts you to compiling separate versions for 32 and 64 bit. You can remove the code duplication and continue to target both 32 and 64 bit versions with one compiled DLL/EXE by taking deanis approach to use SetDllDirectory, or mine to use LoadLibrary.Teresita
T
20

I prefer to do this by using the LoadLibrary call from kernel32.dll to force a particular DLL to load from a specific path.

If you name your 32-bit and 64-bit DLLs the same but placed them in different paths, you could then use the following code to load the correct based on the version of Windows you are running. All you need to do is call ExampleDllLoader.LoadDll() BEFORE any code referencing the ccf class is referenced:

private static class ccf
{
    [DllImport("myDllName")]
    public static extern int func1();
}

public static class ExampleDllLoader
{
    [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
    private extern static IntPtr LoadLibrary(string librayName);

    public static void LoadDll()
    {
        String path;

        //IntPtr.Size will be 4 in 32-bit processes, 8 in 64-bit processes 
        if (IntPtr.Size == 4)
            path = "c:/example32bitpath/myDllName.dll";
        else
            path = "c:/example64bitpath/myDllName.dll";

        LoadLibrary(path);
    }
}
Teresita answered 13/12, 2009 at 14:37 Comment(2)
It looks like this solution requires that the user has admin rights. Can someone confirm?Posen
This shouldn't require admin rights, so long as the user has permissions to read the DLL that is being loaded. I've used it in situations where a normal user is running the application.Teresita
A
13

You can probably achieve this with the #if keyword. If you define a conditional compiler symbol called win32, the following code will use the win32-block, if you remove it it will use the other block:

#if win32
    private static class ccf_32
    {
        [DllImport(myDllName32)]
        public static extern int func1();
    }
#else    
    private static class ccf_64
    {
        [DllImport(myDllName64)]
        public static extern int func1();
    }
#endif

This probably means that you can remove the class wrapping that you have now:

    private static class ccf
    {
#if win32
        [DllImport(myDllName32)]
        public static extern int func1();
#else    
        [DllImport(myDllName64)]
        public static extern int func1();
#endif
    }

For convenience, I guess you could create build configurations for controlling the compilation symbol.

Allotment answered 23/8, 2009 at 23:28 Comment(2)
Yeah, but I'd like to keep the 'Any CPU' option as opposed to have 32 and 64 bit versions.Nicollenicolson
While it works, this approach still results in the code duplication the original poster wants to avoid, plus restricts you to compiling separate versions for 32 and 64 bit. You can remove the code duplication and continue to target both 32 and 64 bit versions with one compiled DLL/EXE by taking deanis approach to use SetDllDirectory, or mine to use LoadLibrary.Teresita
L
11

I know this is a really old question (I'm new - is it bad to answer an old question?), but I just had to solve this same problem. I had to dynamically reference a 32-bit or 64-bit DLL based on OS, while my .EXE is compiled for Any CPU.

You can use DLLImport, and you don't need to use LoadLibrary().

I did this by using SetDLLDirectory. Contrary to the name, SetDLLDirectory adds to the DLL search path, and does not replace the entire path. This allowed me to have a DLL with the same name ("TestDLL.dll" for this discussion) in Win32 and Win64 sub-directories, and called appropriately.

public partial class frmTest : Form
{
    static bool Win32 = Marshal.SizeOf(typeof(IntPtr)) == 4;
    private string DLLPath = Win32 ? @"\Win32" : @"\Win64";

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool SetDllDirectory(string lpPathName);
    [DllImport("TestDLL.dll", SetLastError = true)]
    static extern IntPtr CreateTestWindow();

    private void btnTest_Click(object sender, EventArgs e)
    {
        string dllDir = String.Concat(Directory.GetCurrentDirectory(), DLLPath);
        SetDllDirectory(dllDir);

        IntPtr newWindow = CreateTestWindow();
    }
}
Lezlie answered 1/5, 2012 at 13:28 Comment(1)
This is a great approach, especially if you have multiple DLLs that you need 32 and 64 bit versions of. I believe I like the SetDllDirectory approach even better than using the LoadDll method that I explained in my answer. It's also a whole lot less code than the wrapper approaches, and let's you compile Any CPU.Teresita
A
2

Why not wrap them into a method?

private static class ccf_32_64
{
    private static class ccf_32
    {
        [DllImport(myDllName32)]
        private static extern int func1();
    }

    private static class ccf_64
    {
        [DllImport(myDllName64)]
        private static extern int func1();
    }

    public static int func1()
    {
        if (32bit)
        {
            return ccf_32.func1();
        }
        else
        {
            return ccf_64.func1();
        }
    }
}
Akimbo answered 23/8, 2009 at 23:28 Comment(2)
That's essentially what I have now :-)Nicollenicolson
Well once you wrap it up you don't have to worry about it.Akimbo
H
2

One alternative option is to have both the 32- and 64-bit versions of the unmanaged DLL have the same name, but have them live in separate folders in your build output (say, x86\ and x64\).

Then, your installer or however else you're distributing this is updated so it knows to install the proper DLL for the platform it's installing on.

Heredes answered 24/8, 2009 at 0:7 Comment(0)
G
1

you can create two methods and choose one in a runtime, so you can keep Any CPU

public static class Ccf
{
    [DllImport(myDllName32)]
    private static extern int func32();

    [DllImport(myDllName64)]
    private static extern int func64();


    public static int func()
    {
        if(Environment.Is64BitProcess)
        {
            return func64();
        }
        return func32();
    }

}

Grin answered 26/10, 2017 at 20:51 Comment(0)
I
0

You can't do this the way you want. You need to think of the DllImport attribute as metadata that is used at compile time. As a result you can't change the DLL it is importing dynamically.

If you want to keep your managed code targeted to "Any CPU" then you either need to import both the 32-bit and 64-bit libraries wrapped as two different functions that you can call depending on the runtime environment or use some additional Win32 API calls to late-load the correct version of the unmanaged assembly at runtime and additional Win32 calls to execute the required methods. The drawback there is that you won't have compile time support for any of that type of code for type safety, etc.

Izolaiztaccihuatl answered 24/8, 2009 at 0:37 Comment(0)
L
0

Hmm, I'm wondering if you could create an interface and then a class with the methods based on the 32 bit and 64 bit dlls.

I'm not sure if there is an explicit method to determine if you are running 64 bit, but the following might work: allow unsafe code and have an unsafe function that gets a pointer to some address and then determine whether the pointer is 4 or 8 bytes in size. Based on the result determine which implementation of the interface to create.

Liebman answered 17/11, 2009 at 5:26 Comment(0)
D
0

You can determine whether you are running 64Bits or not by checking the size of the IntPtr type (which is called native int anyways). Then you can load the approriate DLL using an imported LoadLibraryW call, get the function pointer using GetProcAddress, and then, check out Marshal.GetDelegateForFunctionPointer

This not nearly as complicated as it might look like. You have to DllImport both LoadLibraryW and GetProcAddress.

Denomination answered 17/11, 2009 at 5:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.