如何解决如何使用C#9.0 new委托将方法从C#9.0正确发送到C / C ++库?
注意:使用dotnet版本5.0.100-rc.2.20479.15
因此,正如标题所述,我试图找出如何正确地将函数从C#9.0发送到C / C ++代码库(例如glfw)并将该函数用作回调方法(即用于错误回调)。
在C#9.0中引入了委托* 语法后,我一直试图将一个委托作为IntPtr从C#发送到C / C ++中的一个函数,该函数在C#中可以看作一个委托* 。
在此示例中,我准备了一个测试项目,该项目模拟了我所得到的行为以及我试图在代码中完成的工作。我的C / C ++库中有两个功能(在本例中为GLFW代替)SetErrorCallbackFunction
和InvokeError
。
-
SetErrorCallbackFunction
:通过定义为typedef void (*ERRORcallback)(int error_code,const char* description);
的函数指针设置错误回调方法 -
InvokeError
:模拟由于代码中其他地方的错误而从SetErrorCallbackFunction
调用的错误回调函数集。
在C#代码中,我有一个名为Util
的库类,该类为我提供了来自C / C ++库的函数指针,作为delegate*<IntPtr,void>
的{{1}}。
在main方法中,我调用SetErrorCallback,该方法以SetErrorCallbackFunction
作为参数(调用errorcallback时要运行的方法)。此方法将一个委托Action<int,string>
设置为一个等于lambda函数的函数,该函数将byte *描述转换为字符串,并使用_ErrorCallback
和新生成的字符串调用Action<int,string> errorCallback
参数。然后,该lambda / delegate从error_code
转换为IntPtr,最终将其发送到C / C ++库。
虽然在运行代码时,我得到了输出:
Marshal.GetFunctionPointerForDelegate(_ErrorCallback)
当Set errorCallback
Invoking error
Fatal error. Invalid Program: attempted to call a UnmanagedCallersOnly method from managed code.
方法尝试调用InvokeError
时,将产生致命错误行。
这是我看到自己想要的行为的唯一方式。我不想在代码的最高级别中处理byte *(在main方法中,我宁愿为该函数提供一个带有int和一个字符串的lambda而不是一个int和一个byte *)。
代码中的errorCallback
是另一种实现方式,但仍然不理想,因为我必须像以前所说的那样使用字节*,我不想在“更高级别的代码”)。
SECOND EXAMPLE
代码输出如下:
SECOND EXAMPLE
我还为委托人*尝试了这些不同的参数类型:
Set errorCallback
Invoking error
Error 4 - Custom Description
private static delegate*<Delegate,void> SetErrorCallbackFunction = (delegate*<Delegate,void>)Util.StaticProcAddressPointer("SetErrorCallbackFunction");
但是他们都有相同的问题。
如果任何人对我可能做错的事情有任何想法,或者致命错误仅仅是C#9.0如何与委托*配合使用,请告诉我。如果您不理解我冗长的解释或需要澄清/更多详细信息,也请让我知道,我将用更多信息更新该帖子。
谢谢!
编辑:
Edit1:C#中包含一个Wrapper类,以更清楚地表明我希望从主代码中抽象出byte *到string的转换。
Edit2:包括我正在使用的dotnet版本。
Edit3:正如private static delegate*<ErrorCallbackDelegate,void> SetErrorCallbackFunction = (delegate*<ErrorCallbackDelegate,void>)Util.StaticProcAddressPointer("SetErrorCallbackFunction");
所述,我确实在madreflection
方法的声明中缺少了一个unmanaged
关键字。但是,这是故意的。添加InvokeError
关键字将使代码按预期工作,但是,例如,使用没有公开的unmanaged
方法的库,InvokeError
关键字将无法运行可以在任何地方使用。因此,为了使示例能够使用GLFW之类的库进行匹配,其中的errorCallback方法完全由非托管代码管理,unmanaged
方法声明中不包含InvokeError
关键字。
Edit4:GLFW示例如下:
输出: 致命错误。无效的程序:尝试从托管代码中调用UnmanagedCallersOnly方法。
GLFW示例:
unmanaged
using System;
using System.Text;
namespace Test
{
public static unsafe class Program
{
static void Main(string[] args)
{
// GLFW Example
{
GlfwWrapper.SetErrorCallback((error_code,description) =>
{
Console.WriteLine($"Error {error_code} - {description}");
});
GlfwWrapper.CreateWindow(1280,720,"Title",IntPtr.Zero,IntPtr.Zero);
}
}
}
}
代码:
C#9.0代码:
using System;
using System.Text;
namespace Test
{
public unsafe class GlfwWrapper
{
// GLFW example
public static delegate*<IntPtr,void> glfwSetErrorCallback = (delegate*<IntPtr,void>)GlfwUtil.StaticProcAddressPointer("glfwSetErrorCallback");
public static delegate*<int,int,byte*,IntPtr,IntPtr> glfwCreateWindow = (delegate*<int,IntPtr>)GlfwUtil.StaticProcAddressPointer("glfwCreateWindow");
// Delegate that matches the function signature in C/C++ library
public unsafe delegate void ErrorCallbackDelegate(int error_code,byte* description);
// Delegate stored so it doesn't get garbage collected.
public static ErrorCallbackDelegate _ErrorCallback;
public static void SetErrorCallback(IntPtr errorCallback){
_ErrorCallback = (error_code,description) =>
{
byte* walk = description;
while (*walk != 0) walk++;
errorCallback(error_code,Encoding.UTF8.GetString(description,(int)(walk - description)));
};
glfwSetErrorCallback(Marshal.GetFunctionPointerForDelegate(_ErrorCallback));
}
public static IntPtr CreateWindow(int width,int height,string title,IntPtr monitor,IntPtr share)
{
fixed (byte* ptr = Encoding.UTF8.GetBytes(title))
{
return glfwCreateWindow(width,height,ptr,monitor,share);
}
}
}
}
using System;
using System.Text;
namespace Test
{
public static unsafe class Program
{
static void Main(string[] args)
{
Wrapper.SetErrorCallback((error_code,description) =>
{
Console.WriteLine($"Error {error_code} - {description}");
});
// I want to avoid this (1/2)
// Wrapper.SetErrorCallback((error_code,description) =>
// {
// byte* walk = description;
// while (*walk != 0) walk++;
// Console.WriteLine($"Error {error_code} - {Encoding.UTF8.GetString(description,(int)(walk - description))}");
// });
Wrapper.InvokeError();
// SECOND EXAMPLE -----------------------------------------
// This will work but the issue with having to write a method each time with a byte* is not ideal.
Wrapper.SetErrorCallbackFunctionSECOND_EXAMPLE(&ErrorCallback);
Wrapper.InvokeError();
// SECOND EXAMPLE -----------------------------------------
}
// SECOND EXAMPLE
public static void ErrorCallback(int error_code,byte* description)
{
byte* walk = description;
while (*walk != 0) walk++;
Console.WriteLine($"Error {error_code} - {Encoding.UTF8.GetString(description,(int)(walk - description))}");
}
}
}
C库:
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Test
{
public unsafe class Wrapper
{
// delegate* to InvokeError C/C++ function
public static delegate*<void> InvokeError = (delegate*<void>)Util.StaticProcAddressPointer("InvokeError");
// delegate* to SetErrorCallbackFunction C/C++ function
public static delegate*<IntPtr,void> SetErrorCallbackFunction = (delegate*<IntPtr,void>)Util.StaticProcAddressPointer("SetErrorCallbackFunction");
// SECOND EXAMPLE --------------------------------------------------
public static delegate*<delegate*<int,void>,void> SetErrorCallbackFunctionSECOND_EXAMPLE = (delegate*<delegate*<int,void>)Util.StaticProcAddressPointer("SetErrorCallbackFunction");
// SECOND EXAMPLE --------------------------------------------------
// Delegate that matches the function signature in C/C++ library
public unsafe delegate void ErrorCallbackDelegate(int error_code,byte* description);
// Delegate stored so it doesn't get garbage collected.
public static ErrorCallbackDelegate _ErrorCallback;
// Method to set the error callback.
public static void SetErrorCallback(Action<int,string> errorCallback)
{
// By doing it here (2/2)
_ErrorCallback = (error_code,(int)(walk - description)));
};
SetErrorCallbackFunction(Marshal.GetFunctionPointerForDelegate(_ErrorCallback));
}
}
}
#include "library.h"
ERRORcallback errorCallback;
void SetErrorCallbackFunction(ERRORcallback callback)
{
errorCallback = callback;
std::cout << "Set errorCallback" << std::endl;
}
void InvokeError()
{
std::cout << "Invoking error" << std::endl;
errorCallback(4,"Custom Description");
}
解决方法
非常感谢@madreflection。将[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
添加到委托中并将unmanaged[Cdecl]
添加到所有委托*函数可以解决此问题。我没有意识到默认值为__stdcall
。
您还可以使用DllImport:
using System;
using System.Text;
using System.Runtime.InteropServices;
public unsafe class Wrapper
{
public unsafe delegate void dg(int x,byte* y);
[DllImport("glfw3.dll")]
public static extern IntPtr glfwCreateWindow(int width,int height,byte* title,IntPtr monitor,IntPtr share);
[DllImport("glfw3.dll")]
public static extern void glfwSetErrorCallback(System.IntPtr f);
// Method to set the error callback.
public static dg _ErrorCallback;
public static void SetErrorCallback(Action<int,string> errorCallback)
{
_ErrorCallback = (error_code,description) =>
{
byte* walk = description;
while (*walk != 0) walk++;
errorCallback(error_code,Encoding.UTF8.GetString(description,(int)(walk - description)));
};
glfwSetErrorCallback(Marshal.GetFunctionPointerForDelegate(_ErrorCallback));
}
public static IntPtr CreateWindow(int width,string title,IntPtr share)
{
fixed (byte* ptr = Encoding.UTF8.GetBytes(title))
{
return glfwCreateWindow(width,height,ptr,monitor,share);
}
}
}
public static unsafe class Program
{
static void Main(string[] args)
{
Wrapper.SetErrorCallback((error_code,description) =>
{
Console.WriteLine($"Error {error_code} - {description}");
});
Wrapper.CreateWindow(1280,720,"Title",IntPtr.Zero,IntPtr.Zero);
Console.ReadLine();
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。