C# & C++, runtime error when call C++ dll from C# - c#

I have written a C++ wrapper DLL for C# to call. The DLL was tested and worked fine with my C++ test program.
now integrated with C#, I got runtime error and crashed. Cannot use debugger to see more details.
The C++ side has only one method:
#ifdef DLLWRAPPERWIN32_EXPORTS
#define DLLWRAPPERWIN32_API __declspec(dllexport)
#else
#define DLLWRAPPERWIN32_API __declspec(dllimport)
#endif
#include "NB_DPSM.h"
extern "C" {
DLLWRAPPERWIN32_API int WriteGenbenchDataWrapper(string fileNameToAnalyze,
string parameterFileName,
string baseNameToSaveData,
string logFileName,
string& message) ;
}
in the C# side, there is a definition,
[DllImport("..\\..\\thirdParty\\cogs\\DLLWrapperWin32.dll")]
public static extern int WriteGenbenchDataWrapper(string fileNameToAnalyze,
string parameterFileName,
string baseNameToSaveData,
string logFileName,
ref string message);
and a call:
string msg = "";
int returnVal = WriteGenbenchDataWrapper(rawDataFileName,
parameterFileName, outputBaseName, logFileName, ref msg);
I guess there must be something wrong with the last parameter of the function. string& in C++ should be ref string in C#?
EDIT:
Do we really need the extern "C"?
EDIT 2:
after I remove the extern "C from the dll, I got the EntryPointNotFoundException. When I look at the dll by using DLL Export Viewer, I found the function name is "int __cdecl WriteGenbenchDataWrapper(class std:: ..." Do I need to include the " __cdecl"?

There are a bunch of rules for marsheling with PInvoke.
For reference Marsheling between managaed & unmanaged
Focusing on the C# side first.
If you knew a reasonable size of the message up front you could use StringBuilder type and define that size, something like.
[DllImport("DLLWrapperWin32.dll")]
public static extern int WriteGenbenchDataWrapper(string fileNameToAnalyze,
string parameterFileName,
string baseNameToSaveData,
string logFileName,
StringBuilder message
int messageLength );
Impression from the name message (and other posts) indiciates you don't know the size up front, and you won't be passing a partial message to the function so maybe
[DllImport("DLLWrapperWin32.dll")]
public static extern int WriteGenbenchDataWrapper(in string fileNameToAnalyze,
in string parameterFileName,
in string baseNameToSaveData,
in string logFileName,
out string message );
Now on the C/C++ side - to match the second definition
extern "C" // if this is a C++ file to turn off name mangling for this function only
int WriteGenbenchDataWrapper( char * fileNameToAnalyze,
char * parameterFileName,
char * baseNameToSaveData,
char * logFileName,
char ** message ) {
string internalMessage;
SomeFunc( internalMessage ); // these functions won't have extern "C" applied
* message = (char *)::CoTaskMemAlloc(internalMessage.length()+1);
strcpy(* message, internalMessage.c_str());
}
Consideration of unicode/ansi strings is also important, refer to [MarshalAsAttribute(UnmanagedType.LPWSTR)]
For release mode you will want to remove your development path settings "..\..\thirdParty\cogs"

In your C++ code:
I've always needed the extern "C". C++ mangles function names if you don't (the mangling is needed to support function overloading). The extern "C" tells it not to do this.
I also will declare the functions as __stdcall. I believe you can tell C# which type of calling convention to use, but I think __stdcall is the default.
As far as passing a string object, I'm not sure about that, I stick to only using primitives for parameter passing, so I would use const char * and adjust accordingly in my C++ code.
Also, I try to avoid passing by reference. Rather, if I need to return several values, I'll set up a series of getters to handle this (a const char * returns as an IntPtr).
In your C# code:
I use String for the const char *, int for int, and so on. I believe Microsoft has a chart somewhere to tell you what should sub in for what.
When dealing with a returned string, you need to convert it to ANSI. This can be done with a call to Marshal.PtrToStringAnsi().
For Example:
In my C++ code:
extern "C" __declspec(dllexport) const char* __stdcall GetCompany(const char *In) {
return MyGetCompany(In); // Calls the real implementation
}
In my C# code:
[DllImport("TheDLL.dll", EntryPoint = "GetCompany")]
private static extern IntPtr privGetCompany(String In);
// Call this one, not the one above:
public String GetProvince(String In)
{
return Marshal.PtrToStringAnsi(privGetCompany(In));
}
One final note, if you're running on a 64-bit machine, the 'Any CPU' configuration will make a 64-bit C# executable, which will need a 64-bit DLL. If you only have a 32-bit DLL, you'll need to add a configuration (x86).
The error message you got indicates that your C# program is probably finding the DLL correctly and the function as well, so name mangling is not likely the problem. It sounds like calling convention issue or a problem with the parameter passing.

Related

PInvoke implementation of a C SDK function causing memory violation

I am wanting to call a C DLL function from C#. The function is described in the C include header file as:
#ifdef _WIN32
#ifdef CMGO_BUILD_DLL
#define CMGO_DLL_API __declspec(dllexport)
#else
#define CMGO_DLL_API __declspec(dllimport)
#endif
#define CMGO_API_CC __stdcall
#else
#define CMGO_DLL_API
#define CMGO_API_CC
#endif
typedef void* CMGO_DATACHANNEL_HANDLE;
CMGO_DLL_API int CMGO_API_CC cmgo_create_datachannel(const char* host, unsigned short port, CMGO_DATACHANNEL_HANDLE* handle);
A sample C file showing how to use the cmgo_create_datachanell DLL call to create the channel has this example usage:
int main(int argc, char** argv)
{
int st;
char address[] = "127.0.0.1";
unsigned int port = 9800;
CMGO_DATACHANNEL_HANDLE handle = NULL;
struct CMGO_DATACHANNEL_READ_RESULT result;
memset(&result, 0, sizeof(result));
printf("Creating DataChannel [%s,%u] ...\n", address, port);
st = cmgo_create_datachannel(address, port, &handle);
In C# I have written the following:
const string CarmenSDKDLL = "cmgocapi.dll";
[DllImport(CarmenSDKDLL, CallingConvention= CallingConvention.Cdecl)]
static extern int cmgo_create_datachannel(string host, ushort port, IntPtr handle);
static void Main(string[] args)
{
const string ANPRServer = "127.0.0.1";
ushort port = 9800;
IntPtr iHandle = new IntPtr(0);
int iResult = cmgo_create_datachannel(ANPRServer, port, iHandle);
}
When I run the C# code I get an exception
System.AccessViolationException HResult=0x80004003 Message=Attempted to read or write protected memory.
Can anyone pointer me in the right direction and explain what I have written incorrectly in the C# code?
The main problem with your code is you're passing the handle incorrectly - it is in fact a void**, not a void*; meaning, it's an out IntPtr, not an IntPtr
Change the calling code as follows:
const string CarmenSDKDLL = "cmgocapi.dll";
[DllImport(CarmenSDKDLL, CallingConvention = CallingConvention.StdCall)]
extern
static int cmgo_create_datachannel([MarshalAs(UnmanagedType.LPStr)] string host,
ushort port,
out IntPtr handle);
There are a few corrections in the above declaration:
Calling convention - you need to match the declared calling convention, which is __stdcall, not __cdecl
String type - C# strings are Unicode, not ANSI. You need to specify correct marshalling type.
out parameter - Notice the declaration typedef void* CMGO_DATACHANNEL_HANDLE; - it's a void*; however, the function takes a pointer to that type - which is now a void**.
The access violation exception you are getting is specifically due to the last bullet point - the C function is trying to write to an invalid memory location.
Calling code:
const string ANPRServer = "127.0.0.1";
ushort port = 9800;
int errorCode = cmgo_create_datachannel(ANPRServer, port, out var handle);
if(errorCode == 0)
{
// handle is a valid pointer of type IntPtr
}
In general, the errors that happen in the C functions called via P/Invoke may differ quite a bit from the exceptions you see in C# calling code. I recommend looking into Marshal.GetLastWin32Error() documentation and detailed example to improve the error handling in working with P/Invoke.
The handle argument is obviously used to return a handle - you need to declare and call it with ref. Other than that, string arguments generally need some care - for example, by default, C# marshals strings as Unicode, and it seems that the DLL expects ANSI strings. So my guess at the correct P/Invoke signature is
[DllImport(CarmenSDKDLL, CallingConvention= CallingConvention.Cdecl)]
static extern int cmgo_create_datachannel([MarshalAs(UnmanagedType.LPStr)] string host, ushort port, ref IntPtr handle);

Passing a return char* from C++ to C# with DllImport

I am a newbie on both C# WPF and C++.
Recently, I got an external .dll, which returns a char*, and I want to receive the return value in C# by using DllImport. Then, using str.Split(';') to separate the characters. To the purpose, I created a button to show the first character in the string I split on a label when I click the button.
Therefore, I use an IntPtr to receive a char*, which is from C++ .dll, and call Marshal.PtrToStringAnsi() to turn it into a string. However, when I execute the code, it sometimes works but sometimes crushes. Then error code always shows
Unhandled exception at 0x00007FFC06839269 (ntdll.dll) in UITest.exe: 0xC0000374: heap corruption (parameters: 0x00007FFC068A27F0).
I thought my code is reasonable and I couldn't find root causes. Can anyone help me? Thanks!
The below shows the content in .dll and also the code I used in C#.
C++ code in Dlltest.h:
#define DLL_EXPORT extern "C" __declspec(dllexport)
char* getRbtData = nullptr;
DLL_EXPORT char* func_getRbtData();
C++ code in Dlltest.cpp:
char* func_getRbtData()
{
getRbtData = new char(128);
memset(getRbtData, 0, strlen(getRbtData));
char* _getRbtData = "1.0;23.0;55.0;91.0;594.0;";
memcpy(getRbtData, _getRbtData, strlen(_getRbtData));
return getRbtData;
};
C# code in UITest.xaml.cs:
[DllImport("DllTest.dll",EntryPoint = "func_getRbtData", CharSet = CharSet.Ansi)]
public static extern IntPtr func_getRbtData();
string[] words;
private void btn_test_Click(object sender, RoutedEventArgs e)
{
IntPtr intptr = func_getRbtData();
string str = Marshal.PtrToStringAnsi(intptr);
words = str.Split(';');
lb_content.Content = words[1];
}
There are several problems with your code.
On the C++ side, your DLL function is implemented all wrong:
getRbtData = new char(128);
You are allocating a single char whose value is 128, not an array of 128 chars. You need to use new char[128] instead for that.
memset(getRbtData, 0, strlen(getRbtData));
getRbtData is not a pointer to a null-terminated string, so strlen(getRbtData) is undefined behavior. It reads into surrounding memory while calculating the length until it finds a random 0x00 byte in memory.
And then the subsequent memset() into getRbtData will overwrite that surrounding memory. If it doesn't just crash outright.
char* _getRbtData = "1.0;23.0;55.0;91.0;594.0;";
Prior to C++11, this assignment is OK but discouraged. In C++11 and later, this assignment is actually illegal and won't compile.
String literals are read-only data, so you need to use const char instead of char in your pointer type. You should do that even in older compilers.
memcpy(getRbtData, _getRbtData, strlen(_getRbtData));
strlen(_getRbtData) is OK since _getRbtData is a pointer to a null-terminated string.
However, since getRbtData is not allocated with enough memory to receive all of the copied chars, memcpy() into getRbtData is also undefined behavior and will trash memory, if not crash outright.
return getRbtData;
This is OK to pass the pointer to C#.
However, since the memory is being allocated with new (better, new[]), it needs to be freed with delete (delete[]), which you are not doing. So you are leaking the memory.
Marshal.PtrToStringAnsi() on the C# side will not (and cannot) free new'ed memory for you. So your C# code will need to pass the pointer back to the DLL so it can delete the memory properly.
Otherwise, you will need to allocate the memory using the Win32 API LocalAlloc() or CoTaskMemAlloc() function so that the Marshal class can be used on the C# side to free the memory directly without passing it back to the DLL at all.
On the C# side, you are using the wrong calling convention on your DllImport statement. The default is StdCall for compatibility with most Win32 API functions. But your DLL function is not specifying any calling convention at all. Most C/C++ compilers will default to __cdecl unless configured differently.
With that said, try this instead:
Dlltest.h
#define DLL_EXPORT extern "C" __declspec(dllexport)
DLL_EXPORT char* func_getRbtData();
DLL_EXPORT void func_freeRbtData(char*);
Dlltest.cpp
char* func_getRbtData()
{
const char* _getRbtData = "1.0;23.0;55.0;91.0;594.0;";
int len = strlen(_getRbtData);
char *getRbtData = new char[len+1];
// alternatively:
/*
char *getRbtData = (char*) LocalAlloc(LMEM_FIXED, len+1);
if (!getRbtData) return NULL;
*/
memcpy(getRbtData, _getRbtData, len+1);
return getRbtData;
}
void func_freeRbtData(char *p)
{
delete[] p;
// alternatively:
// LocalFree((HLOCAL)p);
}
UITest.xaml.cs
[DllImport("DllTest.dll", EntryPoint = "func_getRbtData", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr func_getRbtData();
[DllImport("DllTest.dll", EntryPoint = "func_freeRbtData", CallingConvention = CallingConvention.Cdecl)]
public static extern void func_freeRbtData(IntPtr p);
string[] words;
private void btn_test_Click(object sender, RoutedEventArgs e)
{
IntPtr intptr = func_getRbtData();
string str = Marshal.PtrToStringAnsi(intptr);
func_freeRbtData(intptr);
// alternatively:
// Marshal.FreeHGlobal(intptr);
words = str.Split(';');
lb_content.Content = words[1];
}
new char(128) returns a pointer to one character, with initial value 128.
I could tell you how to allocate 128 characters, but the chief problem with that is that you can't clean it up so that's not a useful answer. Check the existing questions about returning a string to C#.

String Parameter not Being Marshalled Correctly to C++ DLL

I have been provided with a DLL which is to be called by C#. The DLL contains two methods as follows
extern "C" {
__declspec(dllexport) BSTR GroupInit(LPCTSTR bstrIniFile, bool bDiagErr, bool bProcErr);
}
BSTR GroupInit(LPCTSTR bstrIniFile, bool bDiagErr, bool bProcErr) {
CString strResult = "";
char* sz;
::SetVars(bDiagErr, bProcErr);
if (sz = ::GroupInit((char*)bstrIniFile, 1))
strResult = sz;
return strResult.AllocSysString();
}
I am attempting to call these DLLs from C# by first defining the class:
[DllImport("GrouperServer.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GroupInit(
string strCmdFile,
bool bAllowBadDiagCodes,
bool bAllowBadProcCodes
);
and doing
this.strCommandFilePath = "C:\\MyDir\\MyCommandFile.txt";
string s = Grouper.GrouperServer.GroupInit(this.strCommandFilePath, true, true);
But the DLL is returning the error: 'Cannot find command file: "C"' (the first character of the path only, which I have checked in the C++ DLL). For some reason the string this.strCommandFilePath is not being passed into the C++ method correctly.
What is wrong with the above call?
Edit to address comments.
The method being called in the if (sz = ::GroupInit((char*)bstrIniFile, 1)) statement is defined in a .c file and has the signature
char *GroupInit(char *szCmd, int iType)
{
...
}
It is a mistake to use TCHAR and related types here. The use case of TCHAR is for code that needs to compile for both Windows 9x which has no Unicode support, and Windows NT which does. Those days are long gone and TCHAR is obscuring the problem. What's more, the underlying code uses char* so it makes little sense to pretend that your wrapper code can do anything else. So switch to char.
On top of that you are casting away const. I guess because the function you call accepts a modifiable buffer for a parameter that it does not modify. Best solution is to fix the original library code that erroneously accepts char* and make it accept const char*. If you cannot do that then you'll need to cast away the const. But do that the C++ way with const_cast<>.
So, I'd have the C++ code like this:
BSTR GroupInit(const char* szIniFile, bool bDiagErr, bool bProcErr) {
CString strResult = "";
char* sz;
::SetVars(bDiagErr, bProcErr);
if (sz = ::GroupInit(const_cast<char*>(szIniFile), 1))
strResult = sz;
return strResult.AllocSysString();
}
And the C# code should be:
[DllImport("GrouperServer.dll", CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GroupInit(
string strCmdFile,
bool bAllowBadDiagCodes,
bool bAllowBadProcCodes
);
Now, one wonders what happens to sz. Who is expected to deallocate that? Does it even need to be deallocated? Only you can answer those questions.

C++ dll and C# call

I have a function made in C++ that calls a COM interface's function
Its signature:
BOOL func(LPWSTR strIn, __out LPWSTR strOut)
{
//initcom
//do something
// release pointers
}
In C#:
[DllImport("funcdll.dll")]
static extern bool func(String strIn, ref String strOut);
// use it
for(int i=0;i<10;i++)
{
if(func(strin, strout))
{
//do something with strout
}
}
I have tested my dll in a C++ console application, it works, but in C# it crashes with an unknown error.
You've got three problems that I can see.
The calling conventions don't match. Your C++ code is cdecl and your C# code is stdcall.
The C++ code uses wide strings, but the C# code marshals ANSI strings.
The second parameter doesn't match. Your C# code assumes that the C++ code returns a new pointer to a C string which the C# code then deallocates with the COM allocator. Your C++ code doesn't do this.
Now, dealing with these in more detail.
Calling conventions
This is pretty easy to fix. Simple change the C++ code to stdcall, or the C# code to cdecl. But don't do both. I'd change the C# code:
[DllImport("funcdll.dll"), CallingConvention=CallingConvention.Cdecl]
Unicode/ANSI strings
I presume you are wanting to use Unicode strings since you have explicitly selected them in the C++ code. But P/invoke defaults to marshalling ANSI strings. You can change this again in the DllImport like so:
[DllImport("funcdll.dll"), CallingConvention=CallingConvention.Cdecl,
CharSet=CharSet.Unicode]
Returning a string from C++ to C#
Your current C++ function declaration is so:
BOOL func(LPWSTR strIn, __out LPWSTR strOut)
The __out decorator has no real effect, other than documenting that you want to modify the buffer pointed to by strOut and have those modifications returned to the caller.
Your C# declaration is:
static extern bool func(String strIn, ref String strOut);
Now, ref String strOut simply does not match. A ref string parameter matches this in C++:
BOOL func(LPWSTR strIn, LPWSTR *strOut)
In other words the C# code is expecting you to return a new pointer. In fact it will then proceed to deallocate the buffer you returned in strOut by calling CoTaskMemFree. I'm confident that's not what you want.
Your original C++ code can only return a string to the C# code by modifying the buffer that was passed to it. That code would look like this:
BOOL func(LPWSTR strIn, __out LPWSTR strOut)
{
...
wcscpy(strOut, L"the returned string");
...
}
If this is what you want then you should allocate a sufficient buffer in C# in a StringBuilder object.
[DllImport("funcdll.dll"), CallingConvention=CallingConvention.Cdecl,
CharSet=CharSet.Unicode]
static extern bool func(string strIn, StringBuilder strOut);
...
StringBuilder strOutBuffer = new StringBuilder(128);
bool res = func("input string", strOutBuffer);
string strOut = StringBuilder.ToString();
If you simply cannot decide in the C# code how big a buffer you need then your best bet is to use a BSTR to marshal strOut. See this answer for details.
It's hard for me to tell without seeing the details of your C++ method... but, I've never had much luck using String with P/Invoke.
Try using IntPtr instead of String, and use Marshal.PtrToStringUni for the outgoing string, and marshal your managed string into unmanaged land with Marshal.StringToHGlobalUni and, after the function call, make sure to free the unmanaged string with Marshal.FreeHGlobal.
Also, coming from C99 land, my bools don't work with .NET bools... I don't know why. I have to use byte and check == 1. Don't know if you'll run into this with C++... but, if you want my advice on a place to start which seems least-breakable in my experience, here ya go:
[DllImport("funcdll.dll")]
static extern byte func(IntPtr strIn, out IntPtr strOut);
// use it
string myString = "Testing";
IntPtr stringOut;
IntPtr stringIn = Marshal.StringToHGlobalUni(myString);
if(func(stringIn, out stringOut) == 1)
{
//do something with strout
string stringOutValue = Marshal.PtrToStringUni(stringOut);
// Depending on how you dealt with stringOut in your
// unmanaged code, you may have to: Marshal.FreeCoTaskMem(stringOut);
// or Marshal.FreeHGlobal(stringOut) if you're returning
// an owning reference to a copied string.
}
Marshal.FreeHGlobal(stringIn);
I beleive you're having a calling convention mismatch. The default calling convention in C++ is cdecl while for .NET it's stdcall. try
[DllImport("funcdll.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool func(String strIn, ref String strOut);
Also you might have to specifically tell the marshallar that you want to marshal the strings as LPWSTR using [MarshalAs(UnmanagedType.LPWStr)] attributes.
[DllImport("funcdll.dll", CallingConvention = CallingConvention.Cdecl)]
static extern bool func([MarshalAs(UnmanagedType.LPWStr)]String strIn
,[MarshalAs(UnmanagedType.LPWStr)]ref String strOut);
have a look at http://msdn.microsoft.com/en-us/library/s9ts558h.aspx
In C++ make sure you have something like this (at least for the second argument)
extern "C" BOOL __stdcall func( BSTR * pBstr )
{
*pBstr = SysAllocString( L"Foobar" );
return 0;
}
In C# write something like this (for the second argument) :
static extern bool func( [MarshalAs(UnmanagedType.BStr)] ref String strOut);
Sorry I do not have a big answer but just remember something from my experience.. did you try to use StringBuilder eg changing your c# import function signature as
[System.Runtime.InteropServices.DllImport("funcdll.dll")]
static extern bool func(String strIn, System.Text.StringBuilder strOut);

Sending a C# string as an argument to a unmanaged C++ DLL function

I want to send my C# string to a C++ DLL function. I have succeeded, both with StringBuilder:
[C#]
public static extern int installHook(StringBuilder directory);
StringBuilder myText = new StringBuilder(512);
myfunc(myText);
[C++]
int OPENGLHOOK_API myfunc(char* directory)
{
::MessageBoxA(NULL,directory,"test123",0);
}
and with a simple string & wchar:
[C#]
public static extern int installHook(string directory);
myfunc("myText");
[C++]
int OPENGLHOOK_API installHook(wchar* directory)
{
wstring s = directory;
const wchar_t* wstr = s.c_str();
size_t wlen = wcslen(wstr) + 1;
char newchar[100];
size_t convertedChars = 0;
wcstombs_s(&convertedChars, newchar, wlen, wstr, _TRUNCATE);
::MessageBoxA(NULL,newchar,"test123",0);
}
as it was mentioned in other thread on StackOverflow. The problem is that everytime I do this, I get an error because the function signatures are not the same:
Managed Debugging Assistant 'PInvokeStackImbalance' has detected a problem in 'C:\Users\Dave\Documents\Visual Studio 2010\Projects\OpenGLInjector\Loader\bin\Release\Loader.vshost.exe'.
Additional Information: A call to PInvoke function 'Loader!Loader.Form1::myfunc' 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.
Any idea how I can fix my problem/what to do from here?
I believe the problem is the default calling convention between the two languages. C# is __stdcall and c++ is __cdecl, I believe. Try explicitly stating __stdcall on your C++ method signatures and see if that doesn't resolve the error.
C++:
int OPENGLHOOK_API __stdcall installHook(wchar* directory)
C#:
[DllImport( "yourdll.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode )]
static extern int installHook(string directory);
You need to explicitly describe the unmanaged calling convention for 32bit, and in addition, you will need to explicitly describe the unmanaged string type- ASCII, UTF16, etc.

Categories

Resources