I am trying to make a wrapper for a native c++ .dll using P/Invoke.
The source code for the .dll has the following entry point specified:
// .h-file
CHROMAPRINT_API ChromaprintContext *chromaprint_new(int algorithm);
And the method implementation:
// .cpp-file
ChromaprintContext *chromaprint_new(int algorithm)
{
ChromaprintContextPrivate *ctx = new ChromaprintContextPrivate();
ctx->algorithm = algorithm;
ctx->fingerprinter = new Fingerprinter(CreateFingerprinterConfiguration(algorithm));
return (ChromaprintContext *)ctx;
}
The ChromaprintContextPrivate type is a structure:
>// .cpp-file
struct ChromaprintContextPrivate {
int algorithm;
Fingerprinter *fingerprinter;
vector<int32_t> fingerprint;
};
My C# wrapper code:
// .cs-file
[System.Runtime.InteropServices.DllImportAttribute(
"libchromaprint.dll",
EntryPoint = "chromaprint_new")]
private static extern System.IntPtr chromaprint_new(int algorithm);
public static IntPtr Chromaprint_New(ChromaprintAlgorithm algorithm)
{
// Hardcoded parameter for testing
return chromaprint_new(0); // (int)algorithm
}
Calling IntPtr ptr = Chromaprint_New(0); raises the following MDA exception:
A call to PInvoke function 'MyProject.ChromaprintWrapper!'MyProject.ChromaprintWrapper.LibChromaPrint::chromaprint_new' 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.
So I understand what the problem is (the number of entries on the stack isn't what is expected). I'm assuming the method parameter int algorithm is ok. I'm not sure about the return type though. Should it be a structure instead of a pointer?
I got the C# code above by running the .h-file through P/Invoke Interop Assistant. Is the return type wrong? what should it be?
What is the C# representation of vector<int32_t> fingerprint;?
(See ChromaprintContextPrivate structure above.)
You most likely need to specify the calling convention.
Try the following:
[System.Runtime.InteropServices.DllImportAttribute("libchromaprint.dll",
EntryPoint = "chromaprint_new",
CallingConvention=CallingConvention.Cdecl)]
By default, this uses Winapi (which is effectively StdCall) to make it easier to call into the Windows API, but this is not typically the default on most C++ libraries.
It's because of how the parameters are actually passed, the calling convention.
Try
[DllImport("libchromaprint.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr chromaprint_new(int algorithm);
You need to declare cdecl calling convention on the dllimport attribute.
Related
Suppose we have the following C++ code:
typedef int (*getIntPtr)(void);
extern "C" __declspec(dllexport) void InvokeFuncPtr(getIntPtr funcPtr) {
std::wcout << funcPtr();
}
We can match this definition in C#:
[DllImport("NativeLib.dll", CallingConvention = CallingConvention.Cdecl), SuppressGCTransition]
public static unsafe extern void InvokeFuncPtr(delegate* unmanaged[Cdecl]<int> funcPtr);
And then we can use this function like so:
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
public static int ReturnInt() => 123;
// ... Elsewhere:
unsafe {
InvokeFuncPtr(&ReturnInt);
}
When InvokeFuncPtr is marked with SuppressGCTransition this results in the program crashing with the error "Fatal error. Invalid Program: attempted to call a UnmanagedCallersOnly method from managed code.". If we remove the SuppressGCTransition attribute it works as expected, and 123 is printed to the console.
Is this expected behaviour? I imagine it amounts to the runtime seeing ReturnInt() as being invoked from managed code, simply with a couple of extra steps of indirection. If so, is there any way around this, or should I simply leave the SuppressGCTransition attribute off?
Yes, you should leave off SuppressGCTransition, as the stack is what the runtime uses to identify whether the caller is managed or unmanaged.
If there is no transition in the stack, there is no way for the runtime to tell that the stack transitioned to unmanaged.
Alternatively, you can leave off UnmanagedCallersOnly, and instead marshal the delegate with Marshal.GetFunctionPointerForDelegate.
This question already has answers here:
A call to PInvoke function has unbalanced the stack when including a C DLL into C#
(3 answers)
Closed 10 years ago.
I have an unmanged C++ dll that I have written and tested. The unmanged code woks fine when built and run in an unmanged console app. The function declaration is shown below.
#ifndef IMPORT_MYLIB
# define MYLIB_API __declspec(dllexport)
#else
# define MYLIB_API __declspec(dllimport)
#endif
namespace gsod_data_parsing {
extern "C"
{
MYLIB_API int parse_raw_gsod_file_nocb(
const char *path,
int temp_threshold
);
}
}
I am trying to call this from a managed application. I declare the function in my C# file like this:
[DllImport("GSODDLL.dll")]
public static extern int parse_raw_gsod_file_nocb(
[MarshalAs(UnmanagedType.LPStr)] string path,
int temp_threshold
);
These functions are then being executed on a couple parallel tasks like shown below:
// Start a task - this runs on the background thread...
task1 = Task.Factory.StartNew(() =>
{
int t1 = parse_raw_gsod_file_nocb(filePaths[i], this._frostTemp);
return (t1 == 0);
}, this._tokenSource.Token);
It appears to run fine at first but then (I believe when the function has finished executing) I get the following error.
A call to PInvoke function 'Database
Creator!Database_Creator.Form1::parse_raw_gsod_file_nocb' 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 saw a similar question here but I don't quite understand the accepted answer.
Anyone have any ideas?
Thanks
So it turns out my problem was due to calling convention mismatch. According to this document the default calling convention in windows for C/C++ programs is Cdecl. Also, according to this document the default calling convention for PInvoke is StdCall. I originally did not specify any calling convention so it was defaulting to StdCall. Since these conventions specify how the stack is cleaned up after the function call, it makes sense that the error was being thrown at the end of the function's execution.
Changing my PInvoke declaration to this fixed my problem:
[DllImport("GSODDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int parse_raw_gsod_file_nocb(
[MarshalAs(UnmanagedType.LPStr)] string path,
int temp_threshold
);
I got an external DLL (c++) with the follwing method:
void _stdcall Set_Config(char* config)
I use the following c# code to call the method:
[DllImport(DllName,CharSet=CharSet.Auto)]
public static extern void Set_Config(String config);
But when i execute my c# code i get either an acces violation exception or an System.Runtime.InteropServices.SEHException.
(My dll is 32 bit, and my c# compiler compiles to 32 bit)
I also tried to replace String config with Stringbuilder, but the same result.
Can someone help me with this problem or give some example code how i have to call the c++ method?
CharSet.Auto will encode as UTF-16. But your function accepts char* and so the text is encoded as 8 bit text, presumably ANSI.
[DllImport(DllName, CharSet = CharSet.Ansi)]
public static extern void Set_Config(string config);
Calling the function is trivial.
I am assuming that the string is being passed into the function. On the other hand, it is odd that the parameter is char* rather than const char*. Either the developer doesn't know how to use const, or perhaps the function really does send data back to the caller.
If the data flows the other way, then you are in trouble. You'd need to pass a StringBuilder but you've no way to tell the DLL how large a buffer is available. If the data is flowing the other way then the code looks like this:
[DllImport(DllName, CharSet = CharSet.Ansi)]
public static extern void Set_Config(StringBuilder config);
Call the function like this:
StringBuilder config = new StringBuilder(256); // cross your fingers
Set_Config(config);
Either way, you need to be more clear as to what this function is actually doing. You cannot hope to call this function until you know whether to pass data in, or receive data out.
You have to pass an IntPtr which is a raw pointer to your string. (From my memories, Marshal.StringToBSTR)
public static extern void Set_Config(IntPtr config);
I am currently working on a project with really short deadline, so I don't have much time to understand everything. Also, I am not an expert in C++ development and memory management.
So, what I am trying to do is to create a DLL in with both C and C++ code. Then, I would like to call this DLL in a C# code. Currently, the communication between C++ and C# is OK. The problem comes up when I try to transfer a string from the DLL to the C# code. The error is this one :
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at Microsoft.Win32.Win32Native.CoTaskMemFree(IntPtr ptr)
at System.StubHelpers.CSTRMarshaler.ClearNative(IntPtr pNative)
at NMSPRecognitionWrapper.Program.GetResultsExt()
at NMSPRecognitionWrapper.Program.<Main>b__0() in <my dir>\Program.cs:line 54
at NMSPRecognitionWrapper.Program.StartRecognitionExt()
at NMSPRecognitionWrapper.Program.Main(String[] args) in <my dir>\Program.cs:line 60
Also, I can give you some piece of code below (really simplified !). Actually, the C++ expose two methods : StartRecognition() launch operations to get some data from microphone, then process them and store the results. GetResults() return an instance of the results previously stored. The WrapperCallback() allows the C# part to be called when a Result is able for processing. The C# part, when the Callback is called, will ask to get the results using the GetResults() method.
I know the architecture may seem really inappropriate in this presentation, but I don't want to explain the whole project to validate the model, please be sure everything is correct.
To finish, the problem is when the C# callback call the GetResults() method. Trying to access to the resultsForCS seems to be impossible from the C#.
C++ part - header
// NMSPRecognitionLib.h
#pragma once
#include <iostream>
using namespace std;
extern "C" __declspec(dllexport) char* GetResults();
extern "C" static void DoWork();
extern "C" __declspec(dllexport) void StartRecognition();
C++ part - sources
#include "stdafx.h"
#include "NMSPRecognitionLib.h"
static char * resultsForCS;
static SUCCESS ProcessResult(NMSPCONNECTION_OBJECTS *pNmspConnectionObjects, LH_OBJECT hResult)
{
[...]
char* szResult;
[...]
resultsForCS = szResult;
DoWork();
[...]
return Success;
error:
return Failure;
} /* End of ProcessResult */
extern "C" __declspec(dllexport) char* GetResults()
{
return resultsForCS;
}
extern "C"
{
typedef void (*callback_function)();
callback_function gCBF;
__declspec(dllexport) void WrapperCallback(callback_function callback) {
gCBF = callback;
}
static void DoWork() {
gCBF();
}
}
extern "C" __declspec(dllexport) void StartRecognition()
{
char* argv[] = { "path", "params" };
entryPoint(2, argv);
}
C# part
class Program
{
[DllImport("NMSPRecognitionLib.dll", EntryPoint = "GetResults")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string GetResultsExt();
public delegate void message_callback_delegate();
[DllImport("NMSPRecognitionLib.dll", EntryPoint = "WrapperCallback")]
public static extern void WrapperCallbackExt(message_callback_delegate callback);
[DllImport("NMSPRecognitionLib.dll", EntryPoint = "StartRecognition")]
public static extern void StartRecognitionExt();
static void Main(string[] args)
{
WrapperCallbackExt(
delegate()
{
Console.WriteLine(GetResultsExt());
}
);
StartRecognitionExt();
Console.WriteLine("\nPress any key to finish... ");
var nothing = Console.ReadLine();
}
}
I understand that the problem comes because I am using a pointer to store the results (char *), but I actually don't know how to do this in another way. The szResults type is char * too and I can't change this !
Yes, the return type is the problem. The pinvoke marshaller must do something to release the memory that was allocated for the string. The contract is that memory allocations that need to be released by the caller must be allocated from the COM heap. CoTaskMemAlloc() in native code, also exposed in .NET as Marshal.AllocCoTaskMem().
This rarely comes to a good end, most native code allocates with malloc() or ::operator new, allocating from a heap that's created by the C runtime library. The wrong heap. So inevitably the CoTaskMemFree() call will fail. Ignored silently in Windows XP and earlier, a kaboom on Vista and up.
You must stop the pinvoke marshaller from trying to release the memory. Do so by declaring the return value as IntPtr. And use Marshal.PtrToStringAnsi() to recover the string.
You still have a Big Problem, the kind of problem that bedevils any native code that tries to use this function as well. You still have a string buffer that needs to be released. You cannot do that from C#, you can't pinvoke the correct version of free() or ::operator delete. A memory leak is inevitable. The only thing you can hope for is that the native code takes care of it, somehow. If it doesn't then you must use C++/CLI to interop with it. With the additional requirement that the native code needs to be rebuilt with the same compiler so that it uses the same shared CRT. Code that's difficult to use correctly from native code is also hard to pinvoke. That's a design flaw, always allow the caller to pass a buffer to be filled in so there's never a question who owns the memory.
Looking at:
at Microsoft.Win32.Win32Native.CoTaskMemFree(IntPtr ptr)
at System.StubHelpers.CSTRMarshaler.ClearNative(IntPtr pNative)
at NMSPRecognitionWrapper.Program.GetResultsExt()
I can see that your callback is called, but the runtime tries to free some memory. I think it assumes your pointer would be to com memory. Try converting the string yourself, it is easy!
[DllImport("NMSPRecognitionLib.dll", EntryPoint = "GetResults")]
public static extern IntPtr GetResultsExt();
[...]
string result = Marshal.PtrToStringAnsi(GetResultsExt())
No 100% guarantee, but worth a try.
I have found that it is usually easier to write a wrapper in C++/CLI around the C++ native code. A C++/CLI class can directly call and use native C++, but is accessible from C# (and any .Net language). In my experience, using DLLImport as you do leads to hard to debug and find errors.
This question already has answers here:
A call to PInvoke function has unbalanced the stack when including a C DLL into C#
(3 answers)
Closed 10 years ago.
I have an unmanged C++ dll that I have written and tested. The unmanged code woks fine when built and run in an unmanged console app. The function declaration is shown below.
#ifndef IMPORT_MYLIB
# define MYLIB_API __declspec(dllexport)
#else
# define MYLIB_API __declspec(dllimport)
#endif
namespace gsod_data_parsing {
extern "C"
{
MYLIB_API int parse_raw_gsod_file_nocb(
const char *path,
int temp_threshold
);
}
}
I am trying to call this from a managed application. I declare the function in my C# file like this:
[DllImport("GSODDLL.dll")]
public static extern int parse_raw_gsod_file_nocb(
[MarshalAs(UnmanagedType.LPStr)] string path,
int temp_threshold
);
These functions are then being executed on a couple parallel tasks like shown below:
// Start a task - this runs on the background thread...
task1 = Task.Factory.StartNew(() =>
{
int t1 = parse_raw_gsod_file_nocb(filePaths[i], this._frostTemp);
return (t1 == 0);
}, this._tokenSource.Token);
It appears to run fine at first but then (I believe when the function has finished executing) I get the following error.
A call to PInvoke function 'Database
Creator!Database_Creator.Form1::parse_raw_gsod_file_nocb' 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 saw a similar question here but I don't quite understand the accepted answer.
Anyone have any ideas?
Thanks
So it turns out my problem was due to calling convention mismatch. According to this document the default calling convention in windows for C/C++ programs is Cdecl. Also, according to this document the default calling convention for PInvoke is StdCall. I originally did not specify any calling convention so it was defaulting to StdCall. Since these conventions specify how the stack is cleaned up after the function call, it makes sense that the error was being thrown at the end of the function's execution.
Changing my PInvoke declaration to this fixed my problem:
[DllImport("GSODDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int parse_raw_gsod_file_nocb(
[MarshalAs(UnmanagedType.LPStr)] string path,
int temp_threshold
);