I'm trying to pass a string from C# to C++, using platform invoke.
C++ code:
#include<string>
using namespace std;
extern "C"
{
double __declspec(dllexport) Add(double a, double b)
{
return a + b;
}
string __declspec(dllexport) ToUpper(string s)
{
string tmp = s;
for(string::iterator it = tmp.begin();it != tmp.end();it++)
(*it)-=32;
return tmp;
}
}
C# code:
[DllImport("TestDll.dll", CharSet = CharSet.Ansi, CallingConvention =CallingConvention.Cdecl)]
public static extern string ToUpper(string s);
static void Main(string[] args)
{
string s = "hello";
Console.WriteLine(Add(a,b));
Console.WriteLine(ToUpper(s));
}
I receive a SEHException. Is it impossible to use std::string like this? Should I use char* instead ?
Сorrect decision
C# side:
[DllImport("CppDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr GetString(string s);
public string GetString_(string s)
{
var ptr = GetString(s);
var answerStr = Marshal.PtrToStringAnsi(ptr);
return answerStr;
}
C++ side:
extern "C" __declspec(dllexport) const char* GetString(char* s)
{
string workStr(s);
int lenStr = workStr.length() + 1;
char* answer = new char[lenStr];
const char * constAnswer = new char[lenStr];
strcpy(answer, workStr.c_str());
constAnswer = answer;
return constAnswer;
}
And disable /sdl- in the settings of the cpp project.
One way of doing it without causing a memory leak is using a callback.
C# side:
private delegate bool DLLCallback(IntPtr message);
[DllImport(#"YourLibrary.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
private static extern void Receive(DLLCallback callback);
private static bool callback(IntPtr ptr)
{
string result = Marshal.PtrToStringAnsi(ptr);
Console.WriteLine(result);
// If the Heap is used
// Marshal.FreeHGlobal(ptr);
return true;
}
private static void Main(string[] args) {
Receive(callback);
}
C++ side:
extern "C" {
typedef BOOL(__stdcall* OutputCallback)(const char* str);
__declspec(dllexport) void Receive(OutputCallback callback)
{
char buffer[BUFFER_SIZE];
ZeroMemory(buffer, BUFFER_SIZE);
BOOL callbackResult = callback(buffer);
}
}
There are other options. This is a good article about passing strings between managed and unmanaged code: article
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!
Related
I need pass a string of IP "192.168.1.1" from C# code to the typedef char pointer in DLL which written by c++. and I declare the char *pcAddrs like
char *pcAddrs; //c++
[MarshalAs(UnmanagedType.LPStr)] public string ip //C#
and declare open function
//c++
int Open( COMMIF_INFO *pInfo )
//c#
[DllImport("Open-IF.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Open(COMMIFINFO info);
Now i get an error of wrong parameter when press button1 to trigger the Open function. It is the string and char pointer wrong?
Thanks in advance.
C++ DLL info
typedef struct CommIfInfo
{
char *pcAddrs;
long lPortNo;
long lRetry;
long lSendTimeOut;
long lCommSide;
long lMode;
long lKind;
} COMMIF_INFO;
//Function need to call.
int Open( COMMIF_INFO *pInfo )
Code in C#
// DLL import
[DllImport("Open-IF.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Open(COMMIFINFO info);
// Structure
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct COMMIFINFO
{
[MarshalAs(UnmanagedType.LPStr)] public string ip;
public int PortNo;
public int Retry;
public int SendTimeOut;
public int CommSide;
public int Mode;
public int Kind;
}
private void button1_Click(object sender, EventArgs e)
{
string _ip = "192.168.1.1";
COMMIFINFO info = new COMMIFINF();
info.ip = _ip;
info.Kind = 1;
int ErrCode = Open(info);
}
Program will get wrong parameter error if passing the COMMIFINFO using method below
[DllImport("Open-IF.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Open(COMMIFINFO info);
Found is because the parameter pass to open function need by reference. So program working if use "in" to pass parameter with reference.
[DllImport("Open-IF.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Open(in COMMIFINFO info);
C++ code:
__declspec(dllexport) const char* Get() {
return "hello word!";
}
C# code:
[DllImport("TestLink.dll")]
public static extern string Get();
The program crashes directly after calling
In any case, when you allocate something from C/C++/Native side, you must use the COM allocator which .NET understands. So there are many ways to return a string, for example:
C++:
extern "C" __declspec(dllexport) void* __stdcall GetBSTR() {
return SysAllocString(L"hello world"); // uses CoTaskMemAlloc underneath
}
extern "C" __declspec(dllexport) void* __stdcall GetLPSTR() {
const char* p = "hello world";
int size = lstrlenA(p) + 1;
void* lp = CoTaskMemAlloc(size);
if (lp)
{
CopyMemory(lp, p, size);
}
return lp;
}
extern "C" __declspec(dllexport) void* __stdcall GetLPWSTR() {
const wchar_t* p = L"hello world";
int size = (lstrlenW(p) + 1) * sizeof(wchar_t);
void* lp = CoTaskMemAlloc(size);
if (lp)
{
CopyMemory(lp, p, size);
}
return lp;
}
And C#
[DllImport("MyDll")]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GetBSTR();
[DllImport("MyDll")]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static extern string GetLPWSTR();
[DllImport("MyDll")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string GetLPSTR();
Is it possible to call a method in c# from c++ DLL ?
I have good communication c# -> C++ , but I would like to be able to initiate a call C++ -> c# to poll some data
my code looks something like this...
host.h
/*
define the exporter for the C API
*/
#ifdef DLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
class myClass
{
public:
myCLass(){
//do some initialising
}
~myCLass(){
//do some tidying
}
int getInt(int target) {
switch (target)
{
case 0:
return someIntValue;
case 1:
return someOtherIntValue;
default:
return 0;
}
}
std::string getString(int target) {
switch (target)
{
case 0:
return someStringValue;
case 1:
return someOtherStringValue;
default:
return "";
}
}
}
extern "C" {
DLL_EXPORT myClass* myClassConstructor();
DLL_EXPORT void DestroySpatialser(const myClass* _pContext);
DLL_EXPORT int getInt(myClass* _pContext, int target);
DLL_EXPORT void getString(myClass* _pContext, int target, __out BSTR* returnStr);
}
host.cpp
extern "C" {
myClass* myClassConstructor()
{
return new myClass();
}
void myClassDestructor(const myClass* _pContext)
{
if (_pContext != nullptr)
{
_pContext->~myClass();
delete _pContext;
}
}
//example
int getInt(myClass* _pContext, int target)
{
if (_pContext == nullptr)
{
return K_ERR_INT;
}
return _pContext->getInt(target);
}
void getString(myClass* _pContext, int target, __out BSTR* returnStr)
{
std::string str;
if (_pContext == nullptr)
{
str = K_ERR_CHAR;
}
else {
str = _pContext->getString(target);
}
const std::string stdStr = str;
_bstr_t bstrStr = stdStr.c_str();
*returnStr = bstrStr.copy();
}
}
csharp.cs
private const string dllname = "myhost";
[DllImport(dllname)]
private static extern IntPtr myClassConstructor();
[DllImport(dllname)]
private static extern void myClassDestructor(IntPtr _pContext);
[DllImport(dllname)]
private static extern int getInt(IntPtr _pContext, int target);
[DllImport(dllname)]
private static extern float getFloat(IntPtr _pContext, int target);
[DllImport(dllname, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static void getString(IntPtr _pContext, int target, [MarshalAs(UnmanagedType.BStr)] out String str);
private static IntPtr _pContext;
public static IntPtr Create()
{
_pContext = myClassConstructor();
Debug.Log("_pContextcreated");
return _pContext;
}
public static void Destroy()
{
myClassDestructor(_pContext );
}
private static int _getInt(int target)
{
return getInt(_pContext , target);
}
private static string _getString(int target)
{
String str;
getString(_pContext , target, out str);
return str;
}
This all works fine.
I'd like to add the ability to get a value from the .cs app (it will be a struct of 3 floats) which should be called from the instance of myClass.
I don't want to initiate this on c# side (i know how to do that).
Any advice?
EDIT
I implemented the solution found here
https://forum.unity.com/threads/communicating-c-with-c.89930/#post-586885
with two changes (see comments below for why)
public int whatever = 0;
public delegate int myCallbackDelegate( int n, int m );
private myCallbackDelegate myCallback; // <-- add this line
[DllImport ("DLLImport_CProj")]
private static extern int TakesCallback( myCallbackDelegate fp, int n, int m );
void Awake()
{
myCallback = new myCallbackDelegate( this.myCallback ) // <-- add this
int resp = TakesCallback(myCallback, 10, 20 ); // <-- change to this
Debug.Log( resp );
}
Well, One way I can think of is to pass a JSON string as char array from C++ to C# and then parse it on C# and get the data. it's a communication method both languages are familiar with.
Also, you will need to pass a callback from C# to C++ to allow this like explained in this question.
Let me know if this helps :)
Make your C# types COMVisible, which means you will be able to call them from COM aware C++ code, e.g., using ATL.
I have a WPF / c# application that uses Log4Net for logging. This application calls a few c++ dlls using:
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void TestFunction();
What I would like to do is have the dlls send logging messages back to the C# application, so that everything from both c++ and c# go to the same log. Is this possible?
If so, how can i go about it?
An example that redirect logs in c++ dill to c# callback:
c# side:
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
delegate void LogCallback([MarshalAs(UnmanagedType.LPWStr)] string info);
namespace WinApp
{
static class Wrapper
{
[DllImport("target.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern void SetLogCallback([MarshalAs(UnmanagedType.FunctionPtr)] LogCallback callbackPointer);
internal static void Init()
{
LogCallback log_cb = (info) =>
{
Console.Write(DateTime.Now.TimeOfDay.ToString() + " " + info);
};
SetLogCallback(log_cb);
}
}
c++ side( compiled in target.dll):
extern "C" {
typedef char* (__stdcall* LogCallback)(const wchar_t* info);
PEPARSER_API void SetLogCallback(LogCallback cb);
}
static LogCallback global_log_cb = nullptr;
void LogInfo(const wchar_t* info) {
if (global_log_cb) {
std::wstring buf = L"[PEParse]" + std::wstring(info) + L"\n";
global_log_cb(buf.c_str());
}
else {
std::cout << "global_log_cb not set\n";
}
}
void LogInfo(const char* info) {
const size_t cSize = strlen(info) + 1;
size_t t;
std::wstring wstr(cSize, L'#');
mbstowcs_s(&t, &wstr[0], cSize, info, cSize - 1);
LogInfo(&wstr[0]);
}
void SetLogCallback(LogCallback cb) {
global_log_cb = cb;
LogInfo("log init");
}
I have been using this interface for long times.
I have a huge C++ structure with a lot of validation code in C++, that I want to import in a C# project. I'm able to transfer all value except the CHAR* and CHAR[].
With a CHAR*, my string is full of chiness caracter BUT, if i go look in memory, my string is there, i can see "This is a test #1".
With a CHAR[x], I can see only the 1rst char, same in memory.
In the following test, i can extract the integer : value1 = data.Value1;
and value 1 is 123, but the the CHAR.
Question: What i miss, why i can't get the value with a char array.
Thank you
The C++ DLL
//This is the main DLL file.
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
extern "C"
{
public struct Data
{
int Value1;
char* pValue2;
char Value3[1024];
};
typedef int( *FN_CCP_INVOKE_NEW_BOARD_OPTIMIZER) (struct Data* data);
FN_CCP_INVOKE_NEW_BOARD_OPTIMIZER _pInvokeCallback;
int __declspec(dllexport) DLLTestCPlusPlus_Initialize()
{
return 0;
}
int __declspec(dllexport) DLLTestCPlusPlus_RegisterDllInvokeProcessCallback(void* fnInvokeCaller)
{
_pInvokeCallback = (FN_CCP_INVOKE_NEW_BOARD_OPTIMIZER)fnInvokeCaller;
struct Data data;
// INT
data.Value1 = 123;
// CHAR*
data.pValue2 = new char[1024];
sprintf(data.pValue2, "This is a test #1");
// CHAR [1024]
sprintf(data.Value3, "This is a test #2");
if (_pInvokeCallback)
{
_pInvokeCallback(&data);
}
return 0;
}
}
And here's the C# program that import the DLL.
using System;
using System.Runtime.InteropServices;
public unsafe struct Data
{
public int Value1;
public char* pValue2;
public fixed char Value3[1024];
}
public static class Interop
{
public delegate Int32 Callback([MarshalAs(UnmanagedType.Struct)] Data data);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetDllDirectory(string lpPathName);
[DllImport("C:\\DATA\\CODE\\ApplicationTestCSharp\\x64\\Debug\\DLLTestCPlusPlus.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 DLLTestCPlusPlus_Initialize();
[DllImport("C:\\DATA\\CODE\\ApplicationTestCSharp\\x64\\Debug\\DLLTestCPlusPlus.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 DLLTestCPlusPlus_RegisterDllInvokeProcessCallback([MarshalAs(UnmanagedType.FunctionPtr)] Callback handler);
}
public class MyTest
{
private Interop.Callback _callback = null;
public MyTest()
{
int returnCode = 0;
returnCode = Interop.DLLTestCPlusPlus_Initialize();
_callback = new Interop.Callback(CallbackHandler);
returnCode = Interop.DLLTestCPlusPlus_RegisterDllInvokeProcessCallback(_callback);
}
private Int32 CallbackHandler(Data data)
{
int value1 = 0;
string value2 = "";
string value3 = "";
unsafe
{
// INT
value1 = data.Value1;
// CHAR* - MUST BE "This is a test #1"
value2 = new string(data.pValue2);
// CHAR [1024] - "This is a test #2"
value3 = new string(data.Value3);
}
return 1;
}
}
class Program
{
static void Main(string[] args)
{
MyTest myTest = new MyTest();
}
}
Ok i found it, i made that change in the structure declaration
/*
public char* pValue2;
public fixed char Value3[1024];
*/
[MarshalAs(UnmanagedType.LPStr)] public String pValue2;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] public String pValue3;
And pull the data like that!
// CHAR* - "This is a test #1";
// value2 = new string(data.pValue2);
value2 = data.pValue2
// CHAR [1024] - "This is a test #2"
//value3 = new string(data.Value3);
value3 = data.pValue3;