Using a 32bit or 64bit dll in C# DllImport - c#

Here is the situation, I'm using a C based dll in my dot.net application. There are 2 dlls, one is 32bit called MyDll32.dll and the other is a 64bit version called MyDll64.dll.
There is a static variable holding the DLL file name: string DLL_FILE_NAME.
and it is used in the following way:
[DllImport(DLL_FILE_NAME, CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
private static extern int is_Func1(int var1, int var2);
Simple so far.
As you can imagine, the software is compiled with "Any CPU" turned on.
I also have the following code to determine if the system should use the 64bit file or the 32bit file.
#if WIN64
public const string DLL_FILE_NAME = "MyDll64.dll";
#else
public const string DLL_FILE_NAME = "MyDll32.dll";
#endif
By now you should see the problem.. DLL_FILE_NAME is defined in compilation time and not in execution time so the right dll isn't loaded according to the execution context.
What would be the correct way to deal with this issue? I do not want two execution files (one for 32bit and the other for 64bit)? How can I set DLL_FILE_NAME before it is used in the DllImport statement?

I've found the simplest way to do this is to import the two methods with different names, and calling the right one. The DLL won't be loaded until the call is made so it's fine:
[DllImport("MyDll32.dll", EntryPoint = "Func1", CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1_32(int var1, int var2);
[DllImport("MyDll64.dll", EntryPoint = "Func1", CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1_64(int var1, int var2);
public static int Func1(int var1, int var2) {
return IntPtr.Size == 8 /* 64bit */ ? Func1_64(var1, var2) : Func1_32(var1, var2);
}
Of course, if you have many imports, this can be become quite cumbersome to maintain manually.

Here is another alternative that requires that the two DLLs have the same name and are placed in different folders. For instance:
win32/MyDll.dll
win64/MyDll.dll
The trick is to manually load the DLL with LoadLibrary before the CLR does it. It will then see that a MyDll.dll is already loaded and use it.
This can be done easily in the static constructor of the parent class.
static class MyDll
{
static MyDll()
{
var myPath = new Uri(typeof(MyDll).Assembly.CodeBase).LocalPath;
var myFolder = Path.GetDirectoryName(myPath);
var is64 = IntPtr.Size == 8;
var subfolder = is64 ? "\\win64\\" : "\\win32\\";
LoadLibrary(myFolder + subfolder + "MyDll.dll");
}
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("MyDll.dll")]
public static extern int MyFunction(int var1, int var2);
}
EDIT 2017/02/01: Use Assembly.CodeBase so that it works even if Shadow Copying is enabled.

In this case, i should do like this (make 2 folders, x64 and x86 + put the corresponding dll, WITH THE SAME NAME, in both folders):
using System;
using System.Runtime.InteropServices;
using System.Reflection;
using System.IO;
class Program {
static void Main(string[] args) {
var path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
path = Path.Combine(path, IntPtr.Size == 8 ? "x64" : "x86");
bool ok = SetDllDirectory(path);
if (!ok) throw new System.ComponentModel.Win32Exception();
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool SetDllDirectory(string path);
}

There is a static variable holding the DLL file name
It is not a static variable. It's a constant, at compile time. You can't change a compile time constant at runtime.
What would be the correct way to deal with this issue?
Honestly I would recommend just targeting x86 and forgetting the 64-bit version all together, and letting your application run on WOW64, unless your application has a compelling need to run as x64.
If there is a need for x64, you could:
Change the DLLs to have the same name, such as MyDll.dll, and at install / deploy time, put the right one in place. (If the OS is x64, deploy the 64-bit version of the DLL, otherwise the x86 version).
Have two separate builds altogether, one for x86 and one for x64.

What you describe is known as "side-by-side assembly" (two versions of the same assembly, one 32 and the other 64 bit)... I think you will find these helpful:
Using Side-by-Side assemblies to load the x64 or x32 version of a DLL
http://blogs.msdn.com/b/gauravseth/archive/2006/03/07/545104.aspx
http://www.thescarms.com/dotnet/Assembly.aspx
Here you can find a walkthrough for exactly your scenario (.NET DLL wrapping C++/CLI DLL referencing a native DLL).
RECOMMENDATION:
Just build it as x86 and be done with it... or have 2 builds (one x86 and one x64)... as the above techniques are rather complicated...

an alternative approach may be
public static class Sample
{
public Sample()
{
string StartupDirEndingWithSlash = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName) + "\\";
string ResolvedDomainTimeFileName = StartupDirEndingWithSlash + "ABCLib_Resolved.dll";
if (!File.Exists(ResolvedDomainTimeFileName))
{
if (Environment.Is64BitProcess)
{
if (File.Exists(StartupDirEndingWithSlash + "ABCLib_64.dll"))
File.Copy(StartupDirEndingWithSlash + "ABCLib_64.dll", ResolvedDomainTimeFileName);
}
else
{
if (File.Exists(StartupDirEndingWithSlash + "ABCLib_32.dll"))
File.Copy(StartupDirEndingWithSlash + "ABCLib_32.dll", ResolvedDomainTimeFileName);
}
}
}
[DllImport("ABCLib__Resolved.dll")]
private static extern bool SomeFunctionName(ref int FT);
}

Based on Julien Lebosquain's great answer, this is what I ended up doing in a similar case:
private static class Api32
{
private const string DllPath = "MyDll32.dll";
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1(int var1, int var2);
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern int Func2();
...
}
private static class Api64
{
private const string DllPath = "MyDll64.dll";
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern int Func1(int var1, int var2);
[DllImport(DllPath, CallingConvention = CallingConvention.Cdecl)]
private static extern int Func2();
...
}
public static int Func1(int var1, int var2) {
return Environment.Is64BitProcess
? Api64.Func1(var1, var2)
: Api32.Func1(var1, var2);
}
I think this option scales better if you have multiple entry points in the same DLL for the following reasons:
The Api32 and Api64 classes are completely the same except for the single constant defining the path to the DLL file. This means that I can just copy & paste the declarations from one class to the other if anything changes.
No need to specify the EntryPoint, reducing the possibility of typos.

I have used one of the approaches meantioned by vcsjones:
"Change the DLLs to have the same name, such as MyDll.dll, and at install / deploy time, put the right one in place."
This approach requires maintaining two build platforms though see this link for more details: https://stackoverflow.com/a/6446638/38368

The trick I use for V8.Net is this:
Create a new C# "proxy interface" project with all the defines to switch between the different architectures. In my case the project was named V8.Net-ProxyInterface; example:
public unsafe static class V8NetProxy
{
#if x86
[DllImport("V8_Net_Proxy_x86")]
#elif x64
[DllImport("V8_Net_Proxy_x64")]
#else
[DllImport("V8_Net_Proxy")] // (dummy - NOT USED!)
#endif
public static extern NativeV8EngineProxy* CreateV8EngineProxy(bool enableDebugging, void* debugMessageDispatcher, int debugPort);
THIS is the project you will reference. DO NOT reference the next two:
Create two more projects to generate x64 and x86 versions of the library. This is VERY EASY: Just copy-n-paste to duplicate the .csproj file in the same folder and renamed them. In my case the project file was renamed to V8.Net-ProxyInterface-x64 and V8.Net-ProxyInterface-x86, then I added the projects to my solution. Open the project settings for each of them in Visual Studio and make sure the Assembly Name has either x64 or x86 in the name. At this point you have 3 projects: the first "placeholder" project, and the 2 architecture-specific ones. For the 2 new projects:
a) Open the x64 interface project settings, go to the Build tab, select All Platforms for Platform at the top, then enter x64 in Conditional compilation symbols.
b) Open the x86 interface project settings, go to the Build tab, select All Platforms for Platform at the top, then enter x86 in Conditional compilation symbols.
Open Build->Configuration Manager... and make sure that x64 is selected as the platform for x64 projects, and x86 is selected for the x86 projects, for BOTH Debug AND Release configurations.
Make sure the 2 new interface projects (for x64 and x86) output to the same location of your host project (see project setting Build->Output path).
The final magic: In a static constructor for my engine I quickly attach to the assembly resolver:
static V8Engine()
{
AppDomain.CurrentDomain.AssemblyResolve += Resolver;
}
In the Resolver method, I just load the file based on the current platform indicated by the current process (note: this code is a stripped-down version and not tested):
var currentExecPath = Assembly.GetExecutingAssembly().Location;
var platform = Environment.Is64BitProcess ? "x64" : "x86";
var filename = "V8.Net.Proxy.Interface." + platform + ".dll"
return Assembly.LoadFrom(Path.Combine(currentExecPath , fileName));
Finally, go to your host project in the solution explorer, expand References, select the first dummy project you created in step 1, right-click it to open the properties, and set Copy Local to false. This allows you to develop with ONE name for each P/Invoke function, while using the resolver to figure out which one to actually load.
Note that the assembly loader only runs when needed. It is only triggered (in my case) automatically by the CLR system upon the first access to the engine class. How that translates to you depends on how your host project is designed.

I think this could help to load the DLL dynamically:
#if X64
[DllImport("MyDll64.dll", CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
#else
[DllImport("MyDll32.dll", CallingConvention=CallingConvention.Cdecl, EntryPoint=Func1")]
#endif
private static extern int is_Func1(int var1, int var2);

Related

How do I get the name of the current executable in C#? (.NET 5 edition)

How do I get the name the executable was invoked as (equivalent to C's argv[0])? I actually need to handle somebody renaming the executable and stuff like that.
There's a famous question with lots of answers that don't work. Answers tried:
System.AppDomain.CurrentDomain.FriendlyName
returns the name it was compiled as
System.Diagnostics.Process.GetCurrentProcess().ProcessName
strips extension (ever rename a .exe to a .com?), also sees through symbolic links
Environment.GetCommandLineArgs()[0]
It returns a name ending in .dll, clearly an error.
Assembly.GetEntryAssembly().Location
Returns null
System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName
Returns a .dll name again.
The documentation for .net 5.0 says Environment.GetCommandLineArguments()[0] works; however it doesn't actually work. It somehow sees through symbolic links and returns the real executable name.
What I'm trying to do is link all of our stuff into a single multi-call binary so I can use the .net 5 framework reducer on the resulting binary so I don't have to ship about 30MB of .net 5 framework we're not using. I really don't want to do a platform ladder and P/Invoke a bunch of stuff unless I have to.
I'm after argv[0] directly, not the running process executable name. In the case of symbolic links, these differ.
Came across this with .NET 6, where Process.GetCurrentProcess().MainModule?.FileName seems to be working fine now, and there's also Environment.ProcessPath.
If targeting Windows only, it might be safer (more predictable) to use interop. Below are some options, including the native GetModuleFileName and GetCommandLine:
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("Process.GetCurrentProcess().MainModule?.FileName");
Console.WriteLine(Process.GetCurrentProcess().MainModule?.FileName);
Console.WriteLine();
Console.WriteLine("Assembly.GetExecutingAssembly().Location");
Console.WriteLine(Assembly.GetExecutingAssembly().Location);
Console.WriteLine();
Console.WriteLine("Environment.ProcessPath");
Console.WriteLine(Environment.ProcessPath);
Console.WriteLine();
Console.WriteLine("Environment.CommandLine");
Console.WriteLine(Environment.CommandLine);
Console.WriteLine();
Console.WriteLine("Environment.GetCommandLineArgs()[0]");
Console.WriteLine(Environment.GetCommandLineArgs()[0]);
Console.WriteLine();
Console.WriteLine("Win32.GetProcessPath()");
Console.WriteLine(Win32.GetProcessPath());
Console.WriteLine();
Console.WriteLine("Win32.GetProcessCommandLine()");
Console.WriteLine(Win32.GetProcessCommandLine());
Console.WriteLine();
public static class Win32
{
private const int MAX_PATH = 260;
private const int INSUFFICIENT_BUFFER = 0x007A;
private const int MAX_UNICODESTRING_LEN = short.MaxValue;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr GetCommandLine();
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[PreserveSig]
[return: MarshalAs(UnmanagedType.U4)]
private static extern int GetModuleFileName(
IntPtr hModule, StringBuilder lpFilename, [MarshalAs(UnmanagedType.U4)] int nSize);
public static string GetProcessCommandLine()
{
return Marshal.PtrToStringUni(GetCommandLine()) ??
throw new Win32Exception(nameof(GetCommandLine));
}
public static string GetProcessPath()
{
var buffer = new StringBuilder(MAX_PATH);
while (true)
{
int size = GetModuleFileName(IntPtr.Zero, buffer, buffer.Capacity);
if (size == 0)
{
throw new Win32Exception();
}
if (size == buffer.Capacity)
{
// double the buffer size and try again.
buffer.EnsureCapacity(buffer.Capacity * 2);
continue;
}
return Path.GetFullPath(buffer.ToString());
}
}
}
The output when running via dotnet run:
Process.GetCurrentProcess().MainModule?.FileName
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Assembly.GetExecutingAssembly().Location
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Environment.ProcessPath
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Environment.CommandLine
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Environment.GetCommandLineArgs()[0]
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.dll
Win32.GetProcessPath()
C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe
Win32.GetProcessCommandLine()
"C:\temp\ProcessPath\bin\Debug\net6.0\ProcessPath.exe"
Oh, and for a Windows Forms application, there always has been Application.ExecutablePath.
Updated, running it on Ubuntu 22.04 with .NET 6.0.2 (with Win32 interop removed), either via dotnet run or directly as ./ProcessPath:
Process.GetCurrentProcess().MainModule?.FileName
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath
Assembly.GetExecutingAssembly().Location
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
Environment.ProcessPath
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath
Environment.CommandLine
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
Environment.GetCommandLineArgs()[0]
/home/noseratio/ProcessPath/bin/Debug/net6.0/ProcessPath.dll
After watching everything fail, it became necessary to P/Invoke stuff to make this work. While Process.GetCurrentProcess().MainModule?.FileName reliably returns the executable binary (at least when not running under the debugger), this does not provide the command invocation the binary was launched with.
On Windows, GetCommandLine() is P/Invokable and needs only some parsing to get the information. On *n?x, reading /proc/self/cmdline does the same job.
I built a library encapsulating this. https://github.com/joshudson/Emet/tree/master/MultiCall You can find binaries on nuget.org ready to go.
I should have self-answered a long time ago. Better late than never. Nobody seemed to care until now.
Using .NET Core 3.1, this worked for me to restart a currently running program.
string filename = Process.GetCurrentProcess().MainModule.FileName;
System.Diagnostics.Process.Start(filename);
// Closes the current process
Environment.Exit(0);

How do you tell the architecture you're running on in Windows-CE in C#?

I am writing the installer for a WEC7 application that runs on several devices, each with a different architecture and possibly even different OS versions. The application is, for historical reasons, written in C++. This means that the application is compiled for each OS/architecture version. The installer package has all versions as resouces. I just need to figure out which one to install. OS version can be had at System.Environment.OSVersion.Version.Major, but I can't tell the difference between ARM and x86 architectures.
Possible solutions I have run into included:
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.wProcessorArchitecture;
However, that is C++ code and therefore suffers from the same issue, that is: two compiled versions (ARM & x86) and you have to know which to load... but that's why I want to run the code.
I have also investigated System.Management, but that is not available on WEC7 that I can find.
Any suggestions?
You could always P/Invoke the GetSystemInfo call:
[DllImport("coredll.dll")]
public static extern void GetSystemInfo(out SystemInfo info);
public enum ProcessorArchitecture
{
Intel = 0,
Mips = 1,
Shx = 4,
Arm = 5,
Unknown = 0xFFFF,
}
[StructLayout(LayoutKind.Sequential)]
public struct SystemInfo
{
public ProcessorArchitecture ProcessorArchitecture;
public uint PageSize;
public IntPtr MinimumApplicationAddress;
public IntPtr MaximumApplicationAddress;
public IntPtr ActiveProcessorMask;
public uint NumberOfProcessors;
public uint ProcessorType;
public uint AllocationGranularity;
public ushort ProcessorLevel;
public ushort ProcessorRevision;
}

embedding c++ native dll in set up

I want to embed c++ native dll in set up file created with install shield limited edition.
Hint :- My application created by using c# and c++ native dll.
Here is my example :-
My c++ dll_code
extern "c" __declspec(dllexport) int function_c ()
{
int a=10;
return a;
}
My .net code
public partial class Form1 : Form
{
[DllImport(#"C:\Users\bajwa\Documents\Visual Studio 2012\Projects\c++dll\c++_dll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern int function_c();
void csharp_function()
{
int result= function_c(); // calling c++ native function
MessageBox.Show(result);
}
private void button1_Click(object sender, EventArgs e)
{
csharp_function(); // calling c# function.
}
}
When I installed this setup on my computer it runs perfectly. Because C++ native dll is placed on my computer at "C:\Users\bajwa\Documents\Visual Studio 2012\Projects\c++dll\c++_dll.dll".
But when I delete the c++ native dll from that location then it shows the error.
dll not fount at this location
Please help and solve my problem.
I think you will just need to use a relative path for the imported DLL. The easiest thing to do would be to copy the DLL to a known folder relative to the executable (maybe even the same folder) and then use that path. So if they share a folder, your code can become this:
[DllImport("c++_dll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern int function_c();
Then just modify the installer to place the dll in the correct location. To test, just change your code and move the dll over, but it should work.

Load mixed assembly at runtime

I have a project uses Sqlite (System.Data.SQLite.DLL) it is mixed assembly. I need to load it at runtime. As I know it impossible using AssemblyResolve event. So the target is to unpack assembly to Temp directory and show to application where to find it.
The code is:
public static void SaveSqlite()
{
byte[] sqliteAsm = EmbedAssembly.System_Data_SQLite;
string tempFile = Path.GetTempPath();
File.WriteAllBytes(tempFile + "System.Data.SQLite.DLL", sqliteAsm);
}
// and the setup int start method
SaveSqlite();
AppDomain.CurrentDomain.SetupInformation.PrivateBinPath = Path.GetTempPath();
So it not works. Application cant find sqlite assembly but it successfuly saved to Temp dir. How to solve this problem? Thanks.
The solution has founded.
Not using mixed assembly but managed for sqlite interop Using
SQLite.
SQLite.Interop.dll - native dll (unpack to any directory)
Call SetDllDirectory WinAPI to set dll search directory
code for extract sqlite native dll from resource looks like that:
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetDllDirectory(string lpPathName);
//...
string text4 = Path.Combine(text, "SQLite.Interop.dll");
if (!File.Exists(text4))
{
byte[] array3 = LoadCompressedDllFromResource("nativedll.SQLite.Interop.z");
array3 = DecompressBytes(array3);
SaveToFile(array3, text4);
}
//...
pack all over managed assemblies using libz application

Unable to find entry point in DLL

I have a C# application from which I am trying to send a parameter to a C++ function. However, I am getting the error (mentioned in the subject)
C# application:
static class SegmentationFunctions
{
[DllImport("MyApplication.dll", EntryPoint = "fnmain", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int fnmain(string search);
}
}
public partial class MainWindow:Window
{
public MainWindow()
{
InitializeComponent();
string search = "test string here";
int scommand = SegmentationFunctions.fnmain(search);
}
C++ file.h
extern "C" QUERYSEGMENTATION_API int fnmain(char query[MAX_Q_LEN]);
C++ file .cpp
extern "C" QUERYSEGMENTATION_API int fnmain(char searchc[MAX_LEN_Q])
{
do something...
}
Dependency Walker can show you what functions are effectively exported from the DLL. You will be able to see if your fnmain is there at all, or it is _fnmain instead , or has a C++ decoration in its name.
Note that by default visual studio will not copy your native output to the same folder as your managed output.
manually copy native output to your managed build folder and try again - if that is your problem then you need to change the C++ build settings to put the destination folder the same as your managed app folder.
Your code is correct - so long as the QUERYSEGMENTATION_API macro is defined correctly and your dll is in fact built as "MyApplication.dll"
I would manually run the executable from the file system - making sure that the latest exe and dll are in the same folder, and if it fails run depends.exe to figure it out.

Categories