I'm trying to pass a C# method to Rust to be used as a callback.
I've managed to pass a static function and it works fine (see below).
Now I'd like to call an instance method, which means that the trigger function below should also receive an opaque (libc::c_void) this pointer.
How can I get the IntPtr which Rust should pass as the instance pointer to the callback function?
C#
class Program
{
delegate void PrintFn(int x);
public static void Main(string[] args)
{
PrintFn fn = Print;
var ptr = Marshal.GetFunctionPointerForDelegate(fn);
IntPtr handle = IntPtr.Zero;
create(ptr, out handle);
trigger(handle, 3);
}
public static void Print(int x)
{
Console.WriteLine($"C#: {x}");
}
[DllImport(#"/path/to/rust/dll")]
public static extern int create(IntPtr readFn, out IntPtr callback);
[DllImport(#"/path/to/rust/dll")]
public static extern int trigger(IntPtr handle, int x);
}
Rust:
use std::boxed::Box;
pub struct RustCallback {
callback: extern "stdcall" fn(i32),
}
impl RustCallback {
fn new(callback: extern "stdcall" fn(i32)) -> RustCallback {
RustCallback {
callback: read_fn,
}
}
}
pub fn set_output_arg<T>(out: *mut T, value: T) {
unsafe { *out.as_mut().unwrap() = value };
}
#[no_mangle]
pub extern "C" fn create(callback: extern "stdcall" fn(i32), sc: *mut *mut RustCallback) -> u32 {
set_output_arg(sc, Box::into_raw(Box::new(RustCallback::new(callback))));
0
}
#[no_mangle]
pub extern "C" fn trigger(sc: *mut RustCallback, x: i32) -> u32 {
let sc = unsafe { sc.as_mut().unwrap() };
let f = sc.read_fn;
f(x);
0
}
I was dealing with the same code. I found a simpler way than the one you expose, look:
fn operation(number: i32, f: &dyn Fn(i32) -> i32) {
f(number);
}
#[no_mangle]
fn print(number: i32) {
println!("Rust: {}", number);
}
#[no_mangle]
pub extern "C" fn c_print(number: i32, callback: unsafe extern "C" fn(i32) -> i32) {
operation(number, &|n| unsafe { callback(n) });
}
Then, in C#:
delegate void PrintFn(int number);
public static void Run()
{
int number = 1234;
// passing a C# function as parameter of Rust function
c_print(number, Print);
// passing a Rust function as callback inside the same Rust function
print(number);
}
static void Print(int number)
{
Console.WriteLine($"C#: {number}\n");
}
[DllImport(RLIB)] static extern void c_print(int number, PrintFn fn);
[DllImport(RLIB)] static extern void print(int number);
When I started to deal with this, look for: «How to use from C# a Rust function that has another function as a parameter?»
Related
My C++ DLL name is MyDLL.dll, which is communicating with TestDLL.dll
Now, I want to call functions from MyDLL.dll in my C# project
Visualization
----------- communicates ------------- communicates ---------------
- C# Code - --------------> - MyDLL.dll - --------------> - TestDLL.dll -
----------- ------------- ---------------
MyDLL.dll is written in C++
TestDLL.dll is written in C
Here is the Code
MyDLL.h
#define MyDLL_API __declspec(dllexport)
extern "C" {
MyDLL_API int sum(int a, int b);
MyDLL_API int do();
}
MyDLL.cpp
#include "MyDLL.h"
#include <Windows.h>
extern "C" {
typedef int (CALLBACK* MYFUNC1)(int, int);
typedef short* (CALLBACK* MYFUNC2)(const char*);
int MyDLL_API sum(int a, int b) // This function works fine
{
return a + b;
}
int MyDLL_API do()
{
int t = -1;
HINSTANCE test_dll;
MYFUNC1 myFunc1;
MYFUNC2 myFunc1;
test_dll = LoadLibraryA("c:\\TestDLL.dll");
if (!test_dll) {
t=0;
}
myFunc1 = (MYFUNC1)GetProcAddress(test_dll, "TestFunc");
if (!myFunc1) {
t=1;
}
myFunc2 = (MYFUNC2)GetProcAddress(test_dll, "AuthFunc");
if (!myFunc2) {
t=2;
}
// If I put 'return t' here, it works fine!
(*myFunc2)("Hello World"); // <-- This line causing the error
return t;
}
}
C# Main Program
class Program
{
public class MyClass
{
[DllImport(#"c:\MyDLL.dll", EntryPoint = "sum")]
public static extern int Sum(int a, int b);
[DllImport(#"c:\MyDLL.dll", EntryPoint = "do")]
public static extern int Do();
}
static void Main(string[] args)
{
int summation = MyClass.Sum(30, 20); // This runs fine.
Console.WriteLine("Sum is: " + summation);
int resultCode = MyClass.Do(); // This causing the ERROR.
Console.WriteLine("Result code is: " + resultCode);
Console.ReadLine();
}
}
Exception Hint
System.AccessViolationException: 'Attempted to read or write protected
memory. This is often an indication that other memory is corrupt.'
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 want to hook a C# method to a C++ event written in the C++ DLL
C++ side
#include
extern "C"
{
typedef void (__stdcall *PFN_MYCALLBACK)();
int __stdcall MyUnmanagedApi(PFN_MYCALLBACK callback);
}
C# side
public delegate void MyCallback();
[DllImport("data_acquisition_sys.dll")]
public static extern void MyUnmanagedApi(MyCallback callback);
static void Main(string[] args) {
MyUnmanagedApi(
delegate()
{
Console.WriteLine("Called back by unmanaged side");
}
);
}
}
I followed the http://blogs.msdn.com/b/davidnotario/archive/2006/01/13/512436.aspx
Error
Unhandled Exception: System.EntryPointNotFoundException: Unable to find an entry point named 'MyUnmanagedApi' in DLL 'data_acquisition_sys.dll'. at affect_detection_sys.Program.MyUnmanagedApi(MyCallback callback) at affect_detection_sys.Program.Main(String[] args) in C:\Users\Public\Docume
For all interested parties, here is a working solution to the problem.
C++ side
extern "C"
{
typedef void (*callback_function)();
callback_function gCBF;
__declspec(dllexport) void StartAcquisition(callback_function callback) {
gCBF = callback;
cout << "Acquisition started" << endl;
}
void DoWork() {
gCBF()
}
}
C# side
[DllImport("data_acquisition_sys.dll", EntryPoint = "StartAcquisition")]
public static extern void StartAcquisition(MyCallback callback);
StartAcquisition(delegate()
{
Console.WriteLine("Called back by unmanaged side ");
}
);
Note that the callback_function is an empty method (), since returning and accepting back ANY data results in a runtime crash. This has been reported in other threads, but the answer hasn't been given.
MyUnmanagedApi returns int and you have declared void. Try this:
public delegate void MyCallback();
[DllImport("data_acquisition_sys.dll")]
public static extern int MyUnmanagedApi(MyCallback callback);
static void Main(string[] args) {
MyUnmanagedApi(
delegate()
{
Console.WriteLine("Called back by unmanaged side");
}
);
}
}
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!
I have simple C++ dll function:
__declspec(dllexport) int tst1(int a);
int tst1(int a)
{
return a + 1;
}
I have C# application that calls it:
[DllImport("Project1.dll")]
public static extern int tst1(int i);
static void Main(string[] args)
{
Console.WriteLine( tst1(1) );
Console.ReadLine();
}
}
Got EntryPointNotFoundException error:
What I do wrong?
The name is getting mangled by C++ decoration. Add extern "C" to prevent the mangling of the name:
extern "C" __declspec(dllexport) int tst1(int a);