Reputation: 694
Right now I have a c++ DLL project, which I use for a wrapper for a lower-level functionality in .Net.
C++ code:
string testA(uint a) {
cout << a;
return "egrt";
}
bool testB(uint a) {
cout << a;
return false;
}
C++ header:
#pragma once
#ifdef API_EXPORTS
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
typedef unsigned int uint;
extern "C" API string testA(uint a);
extern "C" API bool testB(uint a);
C# code:
[DllImport(...)] extern static string testA(uint a);
[DllImport(...)] extern static bool testB(uint a);
static void Main() {
testA(10);
testB(13);
}
For some reason, the parameter in testA is not the number I gave, but in testB, it works normally.
Is that normal behavior? What am I doing wrong? How can I avoid that?
I'd be happy to give more details about the solution setup if needed.
Upvotes: 1
Views: 122
Reputation: 597610
The default calling convention for DllImport
is stdcall
, but the default calling convention in C/C++ is usually cdecl
instead. The symptom you describe can easily be caused by a calling convention mismatch between the two languages, so make sure both sets of code agree on which calling convention to use.
Also, you can't safely pass a C++ std::string
(or any other class-based string type) across the DLL boundary in general, and definitely not between languages. When crossing language boundaries, it is very important to use only portable POD types. Most languages are compatible with C, so use only types that C can use (so, in this case, use char*
/wchar_t*
for strings).
There are also memory management issues that have to be taken into account, as well. How memory is allocated, who is responsible for freeing the memory and how, etc. In this case, if a C/C++ DLL returns a char*
string, and C# automatically marshals that data into a native string
, then C# will try to free the char*
string using CoTaskMemFree()
, so the DLL needs to allocate the char*
string using CoTaskMemAlloc()
, eg:
char* __stdcall testA(uint32_t a) {
cout << a;
const char *str = "egrt";
char *result = (char*) CoTaskMemAlloc(strlen(str)+1);
if (result)
strcpy(result, str);
return result;
}
bool __stdcall testB(uint32_t a) {
cout << a;
return false;
}
#pragma once
#include <cstdint>
#ifdef API_EXPORTS
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
extern "C" API char* __stdcall testA(uint32_t a);
extern "C" API bool __stdcall testB(uint32_t a);
[DllImport(..., CharSet=CharSet.Ansi)]
extern static string testA(uint a);
[DllImport(...)]
extern static bool testB(uint a);
static void Main() {
testA(10);
testB(13);
}
If that is not an option for the DLL to do, then the char*
string must be marshaled as an IntPtr
instead, and the DLL will have to export an additional function for C# to call to free the char*
string properly, eg:
char* __stdcall testA(uint32_t a) {
cout << a;
const char *str = "egrt";
char *result = ...; // allocated some other way...
if (result)
strcpy(result, str);
return result;
}
void __stdcall freeA(void *p) {
cout << p;
// deallocate p as needed...
}
bool __stdcall testB(uint32_t a) {
cout << a;
return false;
}
#pragma once
#include <cstdint>
#ifdef API_EXPORTS
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
extern "C" API char* __stdcall testA(uint32_t a);
extern "C" API void __stdcall freeA(void *p);
extern "C" API bool __stdcall testB(uint32_t a);
[DllImport(...)]
extern static IntPtr testA(uint a);
[DllImport(...)]
extern static void freeA(IntPtr p);
[DllImport(...)]
extern static bool testB(uint a);
static void Main() {
IntPtr p = testA(10);
// string s = Marshal.PtrToStringAnsi(p);
freeA(p);
testB(13);
}
Upvotes: 3