Passing strings from C# to C++ DLL and back -- minimal example - c#

I am trying to make the absolute simplest minimal example of how to pass strings to and from a C++ DLL in C#.
My C++ looks like this:
using std::string;
extern "C" {
string concat(string a, string b){
return a + b;
}
}
With a header like
using std::string;
extern "C" {
// Returns a + b
__declspec(dllexport) string concat(string a, string b);
}
My C# is
[DllImport("*****.dll", CallingConvention = CallingConvention.Cdecl)]
static extern string concat(string a, string b);
}
And I am calling it with:
Console.WriteLine(concat("a", "b"));
But this gives a System.AccessViolationException. This seems like it out to be the most trivial thing to deal with, but I am completely stuck on it. When I tried to do a similar experiment with a function "Add" that took two doubles and returned a double I had no problems.

You cannot pass a C++ std::string across an interop boundary. You cannot create one of those in your C# code. So your code can never work.
You need to use interop friendly types at the interop boundary. For instance, null-terminated arrays of characters. That works well when you allocate and deallocate the memory in the same module. So, it's simple enough when passing data from C# to C++.
C++
void foo(const char *str)
{
// do something with str
}
C#
[DllImport("...", CallingConvention = CallingConvention.Cdecl)
static extern void foo(string str);
....
foo("bar");
In the other direction you would typically expect the caller to allocate the buffer, into which the callee can write:
C++
void foo(char *str, int len)
{
// write no more than len characters into str
}
C#
[DllImport("...", CallingConvention = CallingConvention.Cdecl)
static extern void foo(StringBuilder str, int len);
....
StringBuilder sb = new StringBuilder(10);
foo(sb, sb.Capacity);

This is the simplest way I like - pass a string in, and use a lambda to get the response
C#
public delegate void ResponseDelegate(string s);
[DllImport(#"MyDLL.dll", EntryPoint ="Foo", CallingConvention = CallingConvention.StdCall)]
public static extern void Foo(string str, ResponseDelegate response);
...
Foo("Input", s =>
{
// response is returned in s - do what you want with it
});
C++
typedef void(_stdcall *LPEXTFUNCRESPOND) (LPCSTR s);
extern "C"
{
__declspec(dllexport) void __stdcall Foo(const char *str, LPEXTFUNCRESPOND respond)
{
// Input is in str
// Put your response in respond()
respond("HELLO");
}
}

Related

type conversion problem for marshaling datatypes from C# to C++

I'm currently working on an C# (.NET Framework 4.7.2) application using some business logic from an unmanaged C++ library. I try to pass data (interop) back and forth from C# to C++. I may not use C++/CLI, no common language runtime allowed in my project.
It works fine for int. Unfortunately as soon as I try to send another datatype I'm getting an conversion error e.g. float 4.2f becomes 1 and string "fourtytwo" turns into -1529101360.
My C# code looks like this:
// works fine, creates an instance of TestClass
var test = TestProxy.Wrapper_Create("test");
// int, works fine, a = 42
var a = TestProxy.TryInt(test, 42);
// float, problem, b = 1
var b = TestProxy.TryFloat(test, 4.2f);
// string, problem, c = -159101360
var c = TestProxy.TryString(test, "fourtytwo");
My C# Interop Proxy class to call the native (unmanaged) C++ code looks like this:
public static class TestProxy
{
private const string coreDLL = "test.core.dll";
[DllImport(coreDLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Wrapper_Create(string name);
[DllImport(coreDLL, EntryPoint = "?TryInt#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryInt(IntPtr instance, int n);
[DllImport(coreDLL, EntryPoint = "?TryFloat#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryFloat(IntPtr instance, float n);
[DllImport(coreDLL, EntryPoint = "?TryString#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryString(IntPtr instance, string n);
}
My native (unmanaged) C++ looks like that:
the header file:
#ifdef TESTCORE_EXPORTS
#define TESTCORE_API __declspec(dllexport)
#endif
#pragma once
extern "C"
{
class TESTCORE_API TestClass
{
private:
char* name;
public:
TestClass(char*);
int TryInt(int);
float TryFloat(float);
char* TryString(char*);
};
TESTCORE_API TestClass* Wrapper_Create(char* name);
}
the implementation file:
#include "stdafx.h"
#include "TESTCore.h"
TestClass::TestClass(char* n)
{
name = n;
}
int TestClass::TryInt(int n)
{
return n; // works fine
}
float TestClass::TryFloat(float n)
{
return n; // something goes wrong here
}
char* TestClass::TryString(char* n)
{
return n; // something goes wrong here
}
extern "C"
{
TESTCORE_API TestClass * Wrapper_Create(char* name)
{
return new TestClass(name);
}
TESTCORE_API int TryInt(TestClass * instance, int n)
{
if (instance != NULL)
{
return instance->TryInt(n);
}
}
TESTCORE_API float TryFloat(TestClass * instance, float n)
{
if (instance != NULL)
{
return instance->TryFloat(n);
}
}
TESTCORE_API char* TryString(TestClass * instance, char* n)
{
if (instance != NULL)
{
return instance->TryString(n);
}
}
}
Do you know how to correctly marshal float, string from C# to C++ and back?
Thank you!
C++ doesn't have standard ABI. It's rarely a good idea to use C++ classes across DLLs, even when you have same language on both sides.
There're better ways.
Replace your __thiscall class methods with global functions, cdecl or stdcall whichever you like (but note C# and C++ have different defaults, if you'll do nothing C++ will use cdecl, C# will import as stdcall). You can pass "this" pointer of the class in the first argument, IntPtr in C#, just like you're doing now. Also if you'll write extern "C" or use a module definition file, they will have human-readable names.
If you want objects, use COM. Declare an interface that inherits from IUnknown, implement it in C++ (I usually use ATL), and export a global function to create an instance of that object (2 lines in ATL, CComObject<T>::CreateInstance followed by AddRef). No need to register, type libraries, you just need to implement IUnknown (but see this if you want to use them from multiple threads)
Update: strings are indeed harder. Apply [MarshalAs(UnmanagedType.LPTStr)] to the argument. Apply [return: MarshalAs(UnmanagedType.LPTStr)] to the function. Specify PreserveSig=true in your DllImport. Finally, modify the C++ code to return a copy of the string, i.e. call strlen then CoTaskMemAlloc (don't forget about the '\0') then strcpy.
Easier way to deal with strings is like this:
HRESULT TryString( TestClass *instance, BSTR i, BSTR *o )
At least there're CComBSTR and _bstr_t built-in classes to deal with memory management.

C# pinvoke of C function returning char* [duplicate]

This question already has answers here:
Can a local variable's memory be accessed outside its scope?
(20 answers)
Closed 8 years ago.
This is what I came up with:
The unmanaged function:
extern "C" __declspec(dllexport) char* callme(const char * sing) {
char buf[10];
sprintf(buf,"hey %s",sing);
return buf;
}
The calling function:
class Program
{
[DllImport("testnonclr.dll", CharSet = CharSet.Auto, CallingConvention =
CallingConvention.Cdecl)]
public static extern IntPtr callme([In,MarshalAs(UnmanagedType.LPStr)]
string sing);
static void Main(string[] args) {
Console.Write( Marshal.PtrToStringAuto (callme("baby") ));
Console.ReadLine();
}
}
The result: a bunch of gibberish
Ok, thanks to the help of some users and more digging, this is what I ended up doing:
extern "C" {
__declspec(dllexport) char* __stdcall callme(const char * sing)
{
static char *buf=NULL;
char fubb[10];
sprintf(fubb,"hey %s",sing);
ULONG usize=strlen(fubb)+sizeof(char);
buf=(char *)::GlobalAlloc (GMEM_FIXED,usize);
strcpy(buf,fubb);
return buf;
}
}
and C#:
class Program
{
[DllImport("testnonclr.dll", CharSet = CharSet.Unicode, CallingConvention = C
CallingConvention.StdCall )]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string callme([In, MarshalAs(UnmanagedType.LPStr)] string sing);
static void Main(string[] args)
{
string rrr = callme("baby");
Console.WriteLine(rrr);
Console.ReadLine();
}
}
You cannot return a pointer to a local variable since those are allocated on the stack (and the stack may get destroyed when the function returns).
You'd need to allocate memory on the heap (preferably using CoTaskMemAlloc so the CLR can free up the memory later) on your C function and return a pointer to that memory.
As a side note, you can directly return a string when marshalling (assuming it's a c null-terminated unicode string), no need to return IntPtr.
More info here: http://limbioliong.wordpress.com/2011/06/16/returning-strings-from-a-c-api/

Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt

I am trying to pass a byte array to a c++ dll:
c++:
extern "C" __declspec(dllexport) char* myfunction(byte bytes[])
{
char *byteschar = (char*)bytes;
//do somethings with it
return byteschar;
}
c#:
[DllImport("mydll", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl
,CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string myfunction(byte[] bytes);
but I get a System.AccessViolationException when I call myfunction.
When I run the executable without the debugger it seems to be working fine
If you want a buffer be allocated in C# and filled in C++, the approach is a little bit different.
You should allocate a kind of "unmanaged" buffer, pass to the DLL and then convert the result and free the buffer. It's exactly the same way in C, but calling from a managed environment.
Your C++ code should be something like:
extern "C" __declspec(dllexport) void myfunction(char* buffer, int length)
{
//Fill buffer with something observing the maximum length of the buffer.
}
The signature of your DLL in C# should be:
[DllImport("mydll", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl
,CharSet = CharSet.Ansi)]
public static extern string myfunction(IntPtr buffer, Int32 length);
To call it from C#, you should do:
IntPtr unmanagedBuffer = Marshal.AllocHGlobal(100);
// Your Unmanaged Call
myfunction(unmanagedBbuffer, 100);
string yourString = Marshal.PtrToStringUni(unmanagedBuffer);
Marshal.FreeHGlobal(unmanagedBuffer);
Don't forget to call FreeHGlobal if you don't want a memory leak in your app. It's interesting to protect this in "try/finally" clauses.
Other observation is the encoding of the string. Uni, means Unicode. If you use another string representation, check for an equivalent PtrToStringXXX function.
It suppose to be:
extern "C" __declspec(dllexport) char* myfunction(unsigned char * bytes)
{
//do somethings with it
return bytes;
}

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.

Interop sending string from C# to C++

I want to send a string from C# to a function in a native C++ DLL.
Here is my code:
The C# side:
[DllImport(#"Native3DHandler.dll", EntryPoint = "#22",
CharSet = CharSet.Unicode)]
private static extern void func1(byte[] path);
public void func2(string path)
{
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] arr = encoding.GetBytes(path);
func1(this.something, arr);
}
The C++ side:
void func1(char *path)
{
//...
}
What I get in the C++ side is an empty string, every time, no matter what I send.
Help?
Thanks.
It looks like you have 2 issues. The first is your native C++ uses an ANSI string but you are specifying unicode. Secondly, it's easiest to just marshal a string as a string.
Try changing the DllImport to the following
[DllImport(
#"Native3DHandler.dll",
EntryPoint = "#22",
CharSet = CharSet.Ansi)]
private static extern void func1(void* something, [In] string path);
Works fine for me with no extra marshalling instructions in VS2008:
C# side:
[DllImport("Test.dll")]
public static extern void getString(StringBuilder theString, int bufferSize);
func()
{
StringBuilder tstStr = new StringBuilder(BufSize);
getString(tstStr, BufSize);
}
C++ side:
extern "C" __declspec(dllexport) void getString(char* str, int bufferSize)
{
strcpy_s(str, bufferSize, "FOOBAR");
}
Your declaration is wrong. The parameter should be of type string, and you should set the character set encoding to Ansi, like so:
[DllImport(#"Native3DHandler.dll", EntryPoint = "#22",
CharSet = CharSet.Ansi)]
private static extern void func1(string path);
This assumes that you are not modifying the contents of the path variable in your C++ code. Then, you pass the string parameter directly (no need for the wrapper).
If you just want to send a string, just declare func1's parameter as a string. If you want to receive a string, declare it as a StringBuilder and allocate enough buffer space for what you want to receive.
Default Marshaling for Strings
http://msdn.microsoft.com/en-us/library/s9ts558h.aspx

Categories

Resources