How to Pass Strings from C# to C++ (And from C++ to C#) Using Dllimport

How to pass strings from C# to C++ (and from C++ to C#) using DLLImport?

Passing string from C# to C++ should be straight forward. PInvoke will manage the conversion for you.

Geting string from C++ to C# can be done using a StringBuilder. You need to get the length of the string in order to create a buffer of the correct size.

Here are two examples of a well known Win32 API:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
public static string GetText(IntPtr hWnd)
{
// Allocate correct string length first
int length = GetWindowTextLength(hWnd);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(hWnd, sb, sb.Capacity);
return sb.ToString();
}

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool SetWindowText(IntPtr hwnd, String lpString);
SetWindowText(Process.GetCurrentProcess().MainWindowHandle, "Amazing!");

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

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);

Dllimport passing string from C# to C++

I suggest to use char*. Here a possible solution.

If you create another C# function ToUpper_2 as follows

C# side:

[DllImport("TestDll.dll"), CallingConvention = CallingConvention.Cdecl]
private static extern IntPtr ToUpper(string s);

public static string ToUpper_2(string s)
{
return Marshal.PtrToStringAnsi(ToUpper(string s));
}

C++ side:

#include <algorithm>
#include <string>

extern "C" __declspec(dllexport) const char* ToUpper(char* s)
{
string tmp(s);

// your code for a string applied to tmp

return tmp.c_str();
}

you are done!

How to use string as parameter in c# functions imported from c++ DLL?

On the C++ side, change from string to const char*. Leave the C# side be. That's what the default expectation is for P/Invoke when it sees a C# string on the declaration side.

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

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];
}

How to pass *& and **& parameter to C++ dll from C# code

I got the solution with below approach.

C# side code

DLL import, structure definition

[DllImport(@"C:\TestDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DoOperation(ref IntPtr all, ref IntPtr[] part);

[StructLayout(LayoutKind.Sequential)]
public struct DataStruct
{
public int count { get; set; }
public float CountData { get; set; }
public bool flg { get; set; }
public IntPtr data { get; set; }
public string info { get; set; }
}

[StructLayout(LayoutKind.Sequential)]
public struct DataStructInfo
{
public int count { get; set; }
public bool flg { get; set; }
public IntPtr data { get; set; }
}

// First parameter
DataStruct alls = new DataStruct(); // set all required parameter
IntPtr intPtrsAll = new IntPtr();
intPtrsAll = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(DataStruct)));
Marshal.StructureToPtr<DataStruct>(alls, intPtrsAll, false);

// Second parameter
DataStructInfo[] part= new DataStructInfo[3]; // set all required parameter
IntPtr[] intPtrsParts= new IntPtr[part.Length];

for (int i = 0; i < intPtrsParts.Length; i++)
{
intPtrsParts[i] = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(DataStructInfo)));
Marshal.StructureToPtr<DataStructInfo>(part[i], intPtrsParts[i], false);
}

int ret = DoOperation(ref intPtrsAll, ref intPtrsParts);

Note: To set byte[] data at C# side, I have used [IntPtr] with below approach

byte[] rowData = GetRawBytes(dataHandler); // function to read byte[] data
int size = Marshal.SizeOf(rowData[0]) * rowData.Length;
data = Marshal.AllocHGlobal(size); // IntPtr property of struct's
Marshal.Copy(rowData, 0, data, rowData.Length);

Passing String from C# to C++ DLL different Text Encoding on different return Types

Both versions of your code are wrong. You certainly can't pass CString as an interop type. You to use simple types for interop, not C++ classes.

The error when you use char* is more subtle. In that scenario there are two problems.

Firstly, with a string return type on the C# side, the pinvoke marshaller assumes that the returned string was allocated with CoTaskMemAlloc and will call CoTaskMemFree to deallocate it once it has been copied.

Secondly, although we can't see it, your C++ code almost certainly returns a pointer to a buffer owned by a local variable in the C++ function. Obviously this local variable goes out of scope when the function returns and so the pointer becomes invalid.

Some options:

  1. Have the caller locate the string buffer and let the callee populate it.
  2. Allocate the returned char* using CoTaskMemAlloc and so meet the expectations of the C# code.
  3. Use the COM BSTR type which the pinvoke marshaller does understand.


Related Topics



Leave a reply



Submit