dllimport broke between .NET 4.0 and 4.8 - c#

I have been using a pre-compiled DLL in some software for a few years now. Now that software required a new feature so I dusted off my project and made my changes. It was originally built for .NET 4.0, but since that is unsupported I switched to .NET 4.8, trying to keep the same major version to avoid too many differences.
My original code I did not specify a calling convention so I assume StdCall was used, but when I went to run it I got a "PInvoke has left the stack unbalanced" exception. I changed all call types to Cdecl, which resolved that error. I think this was correct to do, but it could be related to my problem still.
Now one particular method is called, (one that is supposed to give a human readable reason for an error code) the application closes. If I try to execute the method in the watch window I get an error that the target process exited with a seemingly random code (-1073740940) which does not seem like the intention of the DLL.
Since the DLL hasn't changed, I assume something about dllimport has that is causing this issue. The documentation for the DLL denotes a single long input and a char* return type which I assume means it is an ANSI string, but I've tried multiple return marshal types. This is what I think is the most correct DLLImport I've tried (the DLL I'm using is not public so I've changed the DLL name and entry point).:
[DllImport("provided.dll", EntryPoint = "getErrorString", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string GetDLLErrorString(long errorCode);

Related

Loading c++ assembly from dotnet core works with DllImport but not Assembly.LoadFrom [duplicate]

This question already has answers here:
How can I specify a [DllImport] path at runtime?
(10 answers)
Closed 3 years ago.
I am loading a c++ assembly in my Dotnet core 3.1 application.
If I use DllImport, I can load and use the assembly as expected.
[DllImport(
"lib/mylibrary.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "MyEndpoint"
)]
However, I want to select the appropriate library for the platform, windows or linux. I tried loading the DLL dynamically using System.Reflection.Assembly.LoadFrom but this gives the error Bad IL Format.
I have tried a few different ways, but all give the same error
// read bytes
var bytes = File.ReadAllBytes("lib/myLibrary.dll");
var assembly = System.Reflection.Assembly.Load(bytes); //bad IL format
// Load From
var assembly = System.Reflection.Assembly.LoadFrom("lib/myLibrary.dll"); //bad IL format
//Load from assembly path
var assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath("lib/myLibrary.dll"); //bad IL format
//From embedded resource
var stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("my-library.manifestname.dll");
var assembly System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(stream); //bad IL format
What am I doing wrong here?
EDIT:
solution:
static class MyWrapper
{
static MyWrapper {
// The linked solution suggests setting the dll path using kernel32, this only works for windows. Set Environment.CurrentDirectory instead.
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Environment.CurrentDirectory = "lib/windows";
}
else
{
Environment.CurrentDirectory = "lib/unix";
}
}
...
[DllImport(
"my_library", //IMPORTANT: drop the extension to preserve compatibility between dylib, dll, so, etc.
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "MyEntryPoint"
)]
}
Assembly.LoadFrom and all the other overloads you are using deal with managed assemblies and not native libraries.
DllImport is just a clever way to hide a lot of machinery, in fact your native library must be loaded, presumably via LoadLibrary, then the runtime needs to bind the method information declared in your class to exported function in your native library, finally the runtime needs to make the runtime association between your method and the function, including the marshaling.
If you would like to emulate what the runtime does under the hood, you can read this document which gives you an idea about the whole process. Please note that this refers to net standard.
Keep in mind the DllImport is cross platform, while your own implementation must be declined for bitness (e.g. x64, x86, arm...) and supported platforms (e.g. linux, windows, macos...) and each of them has its own quirks. For this, see How can I specify a [DllImport] path at runtime?.

LoadLibrary() returns 0 in C#

I am creating an Autodesk Revit add-in in C#. Output of this project is a DLL itself. Within this project, I am loading another unmanaged DLL by calling:
IntPtr pDll = LoadLibrary("E:\\Rep3DLib\\builds\\Release\\Index.dll");
int a = Marshal.GetLastWin32Error();
Within debugger, I see that pDll has value 0 and a has value 127 (Error: The specified procedure could not be found.). This is surprising because I am not even looking any particular procedure within the DLL. I just want to load the DLL.
Now I delete the unmanaged DLL from E:\\Rep3DLib\\builds\\Release\\Index.dll and in the above code, a has value 126 (Error: The specified module could not be found.). This does make sense. It also means that when the DLL is present at the correct location, the code is able to find it but somehow it's looking for a procedure which it's not able to find.
To debug this problem, I created another project within the same solution. Here's the code for this project:
namespace testIndex {
class Program {
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadLibrary(string dllToLoad);
static void Main(string[] args) {
IntPtr pDll = LoadLibrary("E:\\Rep3DLib\\builds\\Release\\Index.dll");
int a = Marshal.GetLastWin32Error();
}
}
}
When I run this, pDll has a non-zero value and I am able to call the functions defined within the Index.dll.
Note that both the projects have target platform as x64. I also verified that if I build the Revit add-in and run it on a different machine, it does find the DLL. So somehow it's not finding the DLL only on my machine although the path E:\\Rep3DLib\\builds\\Release\\Index.dll is definitely correct.
Any idea what could be wrong?
Here's the answer to the question I posted above. My add-in was using a dependent DLL named "pcl_features_release.dll". Revit was loading another add-in before mine, which was also loading its own "pcl_features_release.dll" which might be slightly different than mine. As a result, although Revit was finding my DLL, it was not able to load it.
Another question: In this case, two add-ins are dependent on "pcl_features_release.dll", which is a DLL supplied by Point Cloud Library (http://pointclouds.org/). What is the best practice to avoid conflict in such cases. I can not change someone else's add-in. How do I make sure that my add-in loads my "pcl_features_release.dll" that is being supplied with the add-in?

c# calling LoadLibrary from a 64-bit process to load a 64-bit library fails

I'm having a problem on a machine while attempting at loading a library with a P/Invoke call to LoadLibrary.
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr LoadLibrary(string dllToLoad);
This is my configuration. The calling assembly (A) is compiled in x64 and it calls another assembly (B) compiled as AnyCPU. From B I call LoadLibrary(dll_C_Path) to a library C that is 64-bit. All this works on my machine running Win10 64-bit, but it fails on another machine running Win7 64-bit with the following error (after calling GetLastError): "%1 is not a valid Win32 application".
On B, before calling LoadLibrary I've verified that Environment.Is64BitProcess=true. I've opened the library C with DependencyWalker and it appears 64-bit. But the most strange thing to me is that on one machine works while on another not. What can be the cause?
EDIT
I'm passing the full absolute path to LoadLibrary. Here below a screenshort from Depency Walker (library C is aec.dll). I'm not used to it, but one thing I noticed is that the msvcr120.dll, which aec.dll depends on, has not the icon of a 64-bit. Can this give some hint?
There are two possible explanations:
Your code finds a 32 bit DLL, or an otherwise invalid image. We don't know whether you rely of the DLL search path or specify an absolute path.
Your code finds a 64 bit DLL but when it resolves its dependencies, a 32 bit or otherwise invalid module is found.
You should do some debugging to work out what is wrong. I would write a simple C++ program to load the library and avoid the extra complexity of p/invoke. I'd use Dependency Viewer in profile mode to determine which dependency is not valid.

Using DllImport on class library [duplicate]

I'm getting this weird error on some stuff I've been using for quite a while. It may be a new thing in Visual Studio 2010 but I'm not sure.
I'm trying to call a unamanged function written in C++ from C#.
From what I've read on the internet and the error message itself it's got something to do with the fact that the signature in my C# file is not the same as the one from C++ but I really can't see it.
First of all this is my unamanged function below:
TEngine GCreateEngine(int width,int height,int depth,int deviceType);
And here is my function in C#:
[DllImport("Engine.dll", EntryPoint = "GCreateEngine", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr CreateEngine(int width,int height,int depth,int device);
When I debug into C++ I see all arguments just fine so thus I can only think it's got something to do with transforming from TEngine (which is a pointer to a class named CEngine) to IntPtr. I've used this before in VS2008 with no problem.
I had a _cdecl c++ dll that I called without any trouble from Visual Studio 2008, and then the identical code in Visual Studio 2010 would not work. I got the same PInvoke ... has unbalanced the stack error as well.
The solution for me was to specify the calling convention in the DllImport(...) attribute:
From:
[DllImport(CudaLibDir)]
To:
[DllImport(CudaLibDir, CallingConvention = CallingConvention.Cdecl)]
I guess they changed the default calling convention for DLLImport between .NET 3.5 and .NET 4.0?
It could also be that in the .NET Framework version 3.5, the pInvokeStackImbalance MDA is disabled by default. Under 4.0 (or maybe VS2010) it is enabled by default.
Yes. Technically, the code was always wrong, and previous versions of
the framework silently corrected it.
To quote the .NET Framework 4 Migration Issues document: "To improve
performance in interoperability with unmanaged code, incorrect calling
conventions in a platform invoke now cause the application to fail. In
previous versions, the marshaling layer resolved these errors up the
stack... If you have binaries that cannot be updated, you can include
the <NetFx40_PInvokeStackResilience> element in your application's configuration file to enable calling
errors to be resolved up the stack as in earlier versions. However,
this may affect the performance of your application."
An easy way to fix this is to specify the calling convention and make sure it is the same as in the DLL. A __declspec(dllexport) should yield a cdecl format.
[DllImport("foo.dll", CallingConvention = CallingConvention.Cdecl)]
Maybe the problem lies in the calling convention. Are you sure the unmanaged function was compiled as stdcall and not something else ( i would guess fastcall ) ?
Use the following code, if say your DLL has the name MyDLL.dll and you want to use the function MyFunction within the Dll
[DllImport("MyDLL.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void MyFunction();
this worked for me.
In my case (VB 2010 and DLL compiled with Intel Fortran 2011 XE) the problem exists when my application targets .NET Framework 4. If I change targeted framework to version 3.5, then everything works fine as expected.
So, I would guess the reason is something introduced in .Net Framework 4 but I have no idea at the moment which one
Update: The problem was solved by recompiling Fortran DLL and explicitly specifying STDCALL as calling convention for export names in the DLL.

"PInvokeStackImbalance was detected" from HDFDotNet 1.8.7

I have been attempting to update to the latest HDF5DotNet wrappers (1.8.7) and am getting the following warnings (when running in DEBUG mode from VS2010):
PInvokeStackImbalance was detected
Message: A call to PInvoke function 'HDF5DotNet!::H5Fopen' has unbalanced the stack.
This is likely because the managed PInvoke signature does not match the unmanaged target signature.
Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
I am using the pre-compiled binaries (HDF5DotNet assembly for .NET Framework 4.0 32-bit), but got the same result when I compiled from source.
Strangely, when executing my application that calls the HDF5DotNet wrappers in non-DEBUG mode, I see no problems. I did notice that between 1.8.6 and 1.8.7 all the calling conventions were switched from Cdecl to StdCall. Could this be causing this? I've seen other forums saying the CallingConvention should BE Cdecl...
Thanks!
Yes, calling a stdcall function as cdecl or the other way round causes a stack imbalance. The main difference between these conventions is that with cdecl the caller is responsible for removing the arguments from the stack, with stdcall the callee is responsible.
I guess in release mode you have the same bug. But you don't get the error because some runtime checks are disabled. A native program would crash in most cases where you use the wrong calling convention, but it seems like the .net interop code has a more robust stack handling that masks this problem.

Categories