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 128char
s. You need to usenew char[128]
instead for that.memset(getRbtData, 0, strlen(getRbtData));
getRbtData
is not a pointer to a null-terminated string, sostrlen(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()
intogetRbtData
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 ofchar
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 copiedchar
s,memcpy()
intogetRbtData
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 withdelete
(delete[]
), which you are not doing. So you are leaking the memory.Marshal.PtrToStringAnsi()
on the C# side will not (and cannot) freenew
'ed memory for you. So your C# code will need to pass the pointer back to the DLL so it candelete
the memory properly.Otherwise, you will need to allocate the memory using the Win32 API
LocalAlloc()
orCoTaskMemAlloc()
function so that theMarshal
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:
- Have the caller locate the string buffer and let the callee populate it.
- Allocate the returned
char*
usingCoTaskMemAlloc
and so meet the expectations of the C# code. - Use the COM
BSTR
type which the pinvoke marshaller does understand.
Related Topics
Is Tls 1.1 and Tls 1.2 Enabled by Default for .Net 4.5 and .Net 4.5.1
Encryption Compatible Between Android and C#
Wcf Service Attribute to Log Method Calls and Exceptions
How to Get Float Value with SQLdatareader
Should I Use Return/Continue Statement Instead of If-Else
How to Stop an Application from Opening
Attach Debugger to Iis Instance
Entity Framework Specification Pattern Implementation
How to Get Device Token in iOS 13 with Xamarin
Soap Authentication Fails When Running a C# App on a Linux Box
The Call Stack Does Not Say "Where You Came From", But "Where You Are Going Next"
Android Emulator Not Connecting to Localhost API
How to Programmatically Apply a CSS Class to an ASP.NET Control