C++ DLL LPCTSTR to C# string - c#

I am trying to get string from C++ DLL to C#.
It outputs incorrect symbols - {栠搂珯獲긋ݳݳ贈琹玴ɡݳ⻜}
Here is my code:
C++ DLL
_declspec(dllexport) int __stdcall myClass(LPCTSTR& z)
{
z = _T("Test String");
return 0;
}
My C# code reading C++ DLL:
[DllImport("ecrClassDll.dll", CharSet = CharSet.Unicode)]
static extern void myClass(StringBuilder z);
static void Main(string[] args)
{
StringBuilder z = new StringBuilder();
myClass(z);
}

First, make sure you defined the UNICODE macro in C++, so that _T outputs wchar_t data, and LPCTSTR means const wchar_t*. That's what CharSet.Unicode expects. By the way, if you don't intend to support an ANSI version too, I wouldn't bother with all this _T stuff and just use Unicode everywhere, the code will be simpler.
Also, your C++ function returns an int, but your C# function expects a void. You have a mismatch there (unless you intended to set PreserveSig to false).
On the C# side, when you provide a StringBuilder, it means you provide a buffer to the C++ side, and you expect it to fill that buffer. The correct usage would be something like this:
_declspec(dllexport) int __stdcall myClass(LPCTSTR z, int zSize)
{
_tcscpy_s(z, zSize, _T("Test String"));
return 0;
}
[DllImport("ecrClassDll.dll", CharSet = CharSet.Unicode)]
static extern int myClass(StringBuilder z, int zSize);
static void Main(string[] args)
{
StringBuilder z = new StringBuilder(256);
myClass(z, z.Capacity);
}
But your code returns a pointer to a static string, which the marshaller doesn't expect here.
If you'd like to keep your C++ code as-is, you could try this instead:
[DllImport("ecrClassDll.dll", CharSet = CharSet.Unicode)]
static extern int myClass(out string z);
static void Main(string[] args)
{
string z;
myClass(out z);
}
I admit I didn't test it, but it should work as this C# signature matches the C++ signature.
If everything fails, you could try to marshal the data by yourself:
[DllImport("ecrClassDll.dll")]
static extern unsafe int myClass(void** z);
static unsafe void Main(string[] args)
{
void* z;
myClass(&z);
var str = Marshal.PtrToStringUni(new IntPtr(z));
}

Related

Unable to get result of std::string function in C++ to C# Interop

What I am trying to achieve: I am trying to access a C++ application's functions through its DLL in a C# (Interop).
Issue which I am facing right now: When i create a std::string return type function in C++ and calls it through its DLL in C# code there is now output at all.
Code which I have written for C++ APP
extern "C"
{
__declspec(dllexport) int SM_Interop_API_Add(int a, int b)
{
return a + b;
}
__declspec(dllexport) std::string SM_Interop_API_getLanguage()
{
return "This is Test String";
//return getLanguage();
}
}
Code which I have written for C# APP
class Program
{
[DllImport(#"CPPSample.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi,EntryPoint = "SM_Interop_API_Add")]
public static extern int SM_Interop_API_Add(int a, int b);
[DllImport(#"CPPSample.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "SM_Interop_API_getLanguage")]
public static extern string SM_Interop_API_getLanguage();
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
int sum = SM_Interop_API_Add(10, 10);
Console.WriteLine($"Result: {sum}");
string result = SM_Interop_API_getLanguage();
Console.WriteLine($"Sm Language: {result}");
Console.WriteLine("----EOP----");
}
}
In the above code for C++, SM_Interop_API_getLanguage is supposed to return a string, and when I calls it through its DLL in C# code it does not return any result at all, first, I tried by returning the actual output of getLanguage which did not work then I tried to return some hard codded string but that is also coming as output.
Also to mention the function with int return type works perfectly only std::string return type is not working here in my case.
Kindly guide me if I am doing anything wrong or missing anything. Any help is appreciated.
std::string will never work here as the marshaller does not know how to free it.
Instead, you need to pass in a buffer from the C# side, like this
class Program
{
[DllImport(#"CPPSample.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi,EntryPoint = "SM_Interop_API_Add")]
public static extern int SM_Interop_API_Add(int a, int b);
[DllImport(#"CPPSample.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "SM_Interop_API_getLanguage")]
public static extern void SM_Interop_API_getLanguage(StringBuilder buffer)
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
int sum = SM_Interop_API_Add(10, 10);
Console.WriteLine($"Result: {sum}");
var result = new StringBuilder(200);
SM_Interop_API_getLanguage(result);
Console.WriteLine($"Sm Language: {result}");
Console.WriteLine("----EOP----");
}
}
Then on the C++ side, you simply copy it in (not great with C++, guessing a bit)
__declspec(dllexport) void SM_Interop_API_getLanguage(char* buffer)
{
strcpy(buffer, "This is Test String");
//return getLanguage();
}
Passing a buffer size and checking that it's large enough would be wise also.

Calling Fortran DLL in C# with String arguments

I am trying to pass a string variable from c# to fortran dll.
My C# code;
namespace fortrandll
{
class Program
{
[DllImport(#"csbdll10.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sub")]
public static extern void sub(ref StringBuilder dbname, ref int n);
static void Main(string[] args)
{
StringBuilder dbname = new StringBuilder(5);
dbname.Append("dbname");
int n = 5;
sub(ref dbname, ref n);
Console.ReadLine();
}
}
}
Fortran code;
subroutine sub(dbname,leng) bind(C,name="sub")
use iso_c_binding
!DEC$ ATTRIBUTES reference :: dbname
!DEC$ ATTRIBUTES VALUE :: len
implicit none
integer, intent(in)::leng
character, intent(in):: dbname(leng)
write(*,*) dbname
return
end
Both the programs are compiled successfully.
But when I run the C# code getting some ascii code as result of fortran call rather intended output.
When I tried without ref keyword in C# getting Access Violation Exception.
Other things tried are Unmanaged datatypes and char bytes.
Thanks for the help.
Update: After removing 'ref' keyword for Stringbuilding and in functional call, getting the expected output.
Update Code:
class Program
{
[DllImport(#"csbdll10.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sub")]
public static extern void sub(StringBuilder dbname, ref int n);
static void Main(string[] args)
{
StringBuilder dbname = new StringBuilder();
dbname.Append("dbname name is here");
int n = dbname.Length;
sub(dbname, ref n);
Console.ReadLine();
}
}

Sending string from C# to C++ dll file

I have a function in socket.dll library as below:
__declspec(dllexport) string GetPib(const wchar_t* pib_key_chars)
{
wstring ws(pib_key_chars);
std::string pib_key(ws.begin(), ws.end());
cout << "In GetPib" << " and pib_key in string=" << pib_key << endl;
Pib pb;
return std::to_string(pb.Get(phyFskTxPacket, 0));
}
When i use "dumpbin /exports socket.dll" to check the GetPib function of socket.dll signature it returns
1 0 00011370 ?GetPib##YA?AV?$basic_string#DU?$char_traits#D#std##V?
$allocator#D#2##std##V12##Z = #ILT+875(?GetPib##YA?AV?$basic_string#DU?
$char_traits#D#std##V?$allocator#D#2##std##V12##Z)
I am using the same signature in the C# project(WindowsFormsApp1) to call/invoke GetPib function.
Below is the C# source code to invoke GetPib function:
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
[DllImport("socket.dll", EntryPoint = "?GetPib##YA?AV?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std##PB_W#Z", CallingConvention = CallingConvention.Cdecl)]
public static extern string GetPib([MarshalAs(UnmanagedType.LPStr)] string pib_key);
public Form1()
{
InitializeComponent();
}
private void GetPib_button_Click(object sender, EventArgs e)
{
String str = pib_id_tbox.Text;
pib_uvalue_tbox.Text = GetPib(str);
}
}
When I invoke GetPib function like GetPib(0x1004xxx4), it invokes the socket.dll's GetPib function but the value is different with special characters
str=0x10040004 tr=268697604-----> in C# file
In GetPib and pib_key in string=h䤰„------> in C++ .dll file
How to fix this issue.
First of all, wrap the unmanaged function declaration in extern "C" to eliminate the mangling and rewrite the unmanaged function to something more sensible for interop with managed code.
extern "C" {
__declspec(dllexport) int GetPib(const char* pib_key_chars, char *result, int len) {
cout << "In GetPib" << " and pib_key in string=" << pib_key_chars << endl;
Pib pb;
string packet = std:to_string(pb.Get(phyFskTxPacket, 0);
int n = (int)packet.length;
if (n < len) {
packet.copy(result, n, 0);
result[n] = '\0';
}
return n + 1;
}
}
In this rewrite, we are passing the managed string, an allocated buffer to hold the result, and the length of that buffer. This version handles char* strings. If you need to use wchar_t* wide strings, it should be trivial to convert it. The main thing to note is that we are not using any C++ types as parameters to the function, since the interop marshaller has no way of knowing how to deal with those types. We are only using language primitives.
The managed P/Invoke signature would look like this:
[DllImport("socket.dll", EntryPoint="GetPib")]
public static extern int GetPib(([MarshalAs(UnmanagedType.LPStr)] string keyChars, IntPtr buffer, int len);
To call it:
private void GetPib_button_Click(object sender, EventArgs e)
{
int needed = GetPib(pib_id_tbox.Text, IntPtr.Zero, 0);
IntPtr p = Marshal.AllocHGlobal(needed);
GetPib(pib_id_tbox.Text, p, needed);
pib_uvalue_tbox.Text = Marshal.PtrToStringAuto(p);
Marshal.FreeHGlobal(p);
}
Here we are allocating unmanaged memory so that the GC won't move it around while we are interoperating with unmanaged code. The unmanaged function will fill the buffer with the resulting char*, which we convert back into a managed string with PtrToStringAuto(). Then we free the unmanaged memory.
I followed this link
https://answers.unity.com/questions/142150/passing-strings-to-and-from-a-c-dll.html
I modified GetPib api in C++
__declspec(dllexport) string GetPib(const wchar_t* pib_key_chars)
to
extern BSTR __declspec(dllexport) GetPib(BSTR pib_key_chars)
Modified C# file from
[DllImport("socket.dll", EntryPoint = "?GetPib##YA?AV?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std##PB_W#Z", CallingConvention = CallingConvention.Cdecl)]
public static extern string GetPib([MarshalAs(UnmanagedType.LPStr)] string pib_key);
to
[DllImport("socket.dll", EntryPoint = "?GetPib##YAPA_WPA_W#Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GetPib(string str);
and it solved my problem!!!!!!

C# delegate for C++ callback

I think I have basically understood how to write c# delegates for callbacks, but this one is confusing me.
The c++ definition is as follows:
typedef int (__stdcall* Callback)(
long lCode,
long lParamSize,
void* pParam
);
and my c# approach would be:
unsafe delegate int CallbackDelegate (int lCode, int lParamSize, IntPtr pParam);
Although this seems to be incorrect, because I get a PInvokeStackInbalance error, which means my definition of the delegate is wrong.
The rest of the parameters of the function are strings or ints, which means they cannot cause the error, and if I just pass a IntPtr.Zero instead of the delegate (which would mean I'm pointing to a non-existent callback function) I get an AccessViolation error, which makes sense aswell.
What am I doing wrong?
EDIT:
The full c++ function is:
int
__stdcall
_Initialize (
const char* FileName,
Callback cbFunction,
int Code,
const char* Name,
unsigned int Option,
unsigned int Option2
);
My c# version is:
[DllImport("MyDll.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int _Initialize (string FileName, CallbackDelegate cbFunction, int Code, string Name, uint Options, uint Options2);
The function is (for testing) just called inside of the main routine of a console application:
static void Main(string[] args)
{
CallbackDelegate del = new CallbackDelegate(onCallback);
Console.Write(_Initialize("SomeFile.dat", del, 1000, "", 0, 4));
Console.Read();
}
where onCallback is this:
static int onCallback(int lCode, int lParamSize, IntPtr pParam)
{
return 0;
}
I get the PInvokeStackInbalance error on the line where I call _Initialize, if I pass a IntPtr.Zero instead of the delegate, and change the definition of the function to IntPtr instead of CallbackDelegate then I get a AccessViolationException.
I added your code to my current project where I'm doing a lot of C#/C++ interop in VS2012. And while I hate to use the "it worked on my machine", it worked fine for me. The code as I ran it is listed out below just to illustrate that I didn't make and fundamental changes.
My suggestion is that you build a new native dll with a stub function for _Initialize such as below and see if it works when you can control both sides of the interface. If that works but the real dll doesn't, it comes down to either compiler settings on the native side or their is a bug on the native side and it is stomping on the stack.
extern "C" {
typedef int (__stdcall* Callback)(long lCode,long lParamSize,void* pParam );
TRADITIONALDLL_API int __stdcall _Initialize (const char* FileName,Callback cbFunction, int Code,
const char* Name,unsigned int Option,unsigned int Option2)
{
cbFunction(0, 0, nullptr);
return 0;
}
}
On the C# side, I added the declarations to my interface class:
public delegate int CallbackDelegate(int lCode, int lParamSize, IntPtr pParam);
[DllImport("XXX.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int _Initialize(string FileName, CallbackDelegate cbFunction, int Code, string Name, uint Options, uint Options2);
And then in my main:
private int onCallback(int lCode, int lParamSize, IntPtr pParam)
{
return 0;
}
XXXInterface.CallbackDelegate del = new XXXInterface.CallbackDelegate(onCallback);
Console.Write(XXXInterface._Initialize("SomeFile.dat", del, 1000, "", 0, 4));
A .NET long is 64bits. A C++ long may be just 32bits. Check with the C++ compiler which compiled that definition as to what size it's longs are.
[UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)]
unsafe delegate int CallbackDelegate (int lCode, int lParamSize, IntPtr pParam);
If .NET assumes cdecl instead of stdcall, your stack will most assuredly be hosed.

Array change in C++ function is not preserved in C#

I have a C# code calling a C++ function.
The C++ function should fill a buffer passed with a pointer. However, the array returns empty.
The import declaration is:
[DllImport("ProjectLogicInterface", EntryPoint = "FillArr", CallingConvention = CallingConvention.Cdecl)]
public static extern UInt32 FillArr(char[] arr);
The code, after simplifications and entering some hard coded values looks like that:
The code in C#:
char[] arr= new char[10];
ret = LogicInterface.FillArr(arr);
The C++ code:
bool FillArr(char* arr)
{
int length=10;
for(int i = 0; i < length; i++)
{
arr[i] = 3; //replaced with some hard coded value
}
return true;
}
However, the array remains empty.
Any suggestions?
I believe that you would have to pin the array before passing it to your native code. This prevents the GC from moving the managed array around in memory while you're accessing it in C++.
So you could use:
[DllImport("ProjectLogicInterface", EntryPoint = "FillArr", CallingConvention = CallingConvention.Cdecl)]
public static extern UInt32 FillArr( char* arr );
And then:
char[] arr = new char[ 10 ];
fixed (char* pinned = arr )
{
ret = LogicInterface.FillArr( pinned );
}
Note that I haven't actually compiled and run this but it should give you some ideas on how to proceed.
You don't need to pin the array as long as the code in C++ doesn't cache it before returning.
In your case, you need to pass the array by reference so it can be considered as an out argument.
[DllImport("ProjectLogicInterface", EntryPoint = "FillArr", CallingConvention = CallingConvention.Cdecl)]
public static extern UInt32 FillArr(ref char[] arr);`
Found this link highly usefull:
C#: calling C++ DLL with char** argument
The final code looks like:
[DllImport("ProjectLogicInterface", EntryPoint = "FillArr", CallingConvention = CallingConvention.Cdecl)]
static extern bool FillArr([MarshalAs(UnmanagedType.LPStr, ArraySubType = UnmanagedType.LPStr)] StringBuilder args);
static void Main(string[] args)
{
StringBuilder arr = new StringBuilder(9);
bool res = FillArr(arr);
}

Categories