如何将 C++ 非托管 DLL 转换为 C#

Posted

技术标签:

【中文标题】如何将 C++ 非托管 DLL 转换为 C#【英文标题】:How can I convert C++ unmanaged DLL to C# 【发布时间】:2019-09-17 09:24:09 【问题描述】:

我在 C++ 中有非托管 dll,它工作正常,我尝试用 C# 重新实现它,但出现以下错误:

System.ArgumentException:值不在预期范围内

at System.StubHelpers.ObjectMarshaler.ConvertToNative(Object objSrc, IntPtr pDstVariant)  
at Demo.on_session_available(Int32 session_id) in C:\\Users\\Peyma\\Source\\Repos\\FastViewerDataClient\\FastViewerDataClient\\Demo.cs:line 69

异常方法:8 转换为原生 mscorlib,版本=4.0.0.0,文化=中性,PublicKeyToken=b77a5c561934e089 System.StubHelpers.ObjectMarshaler void ConvertToNative(System.Object, IntPtr)

HResult:-2147024809

来源:mscorlib

C++代码如下:

typedef void(*func_ptr)(
int sId,
unsigned char cId,
const unsigned char* buf,
int len,
void* context);

struct configuration

  func_ptr send;
;

struct send_operation

  int session_id;
  unsigned char channel_id;
  std::string data;
;

 auto op = new send_operation();
 op->sId = sessionId;
 op->cId = channelId;
 op->data = "Some Text";

 configuration.send(
    sessionId,
    channelId,
    reinterpret_cast<const unsigned char*>(op->data.c_str()),
    op->data.length(),
    op);

然后用 C# 翻译如下:

[StructLayout(LayoutKind.Sequential)]
public struct Configuration

    public Send send  get; set; 


[StructLayout(LayoutKind.Sequential)]
public struct send_operation

    public int session_id  get; set; 
    public sbyte channel_id  get; set; 
    public string data  get; set; 
;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void Send(int sessionId, sbyte channelId, sbyte[] buffer, int len, object context);


 var op = new send_operation
        
            session_id = session_id,
            channel_id = 0,
            data = "This is a test message!!"
        ;

        var bytes = Encoding.UTF8.GetBytes(op.data);

        config.send(sessionId, 0, Array.ConvertAll(bytes, Convert.ToSByte), op.data.Length, op);

更新:

public static void on_session_available(int session_id)

    WriteOnFile($"Open session id:session_id");

    try
    
        var op = new send_operation
        
            session_id = session_id,
            channel_id = 0,
            data = "This is a test message!!"
        ;


        var bytes = Encoding.UTF8.GetBytes(op.data);

        config.send_data(session_id, op.channel_id, bytes, op.data.Length, op);
    
    catch (Exception e)
    
        WriteOnFile($"Error in sending data:JsonConvert.SerializeObject(e)");
        if (e.InnerException != null)
        
            WriteOnFile($"Error in inner sending data:e.InnerException.Message");
        
    

【问题讨论】:

为什么都是sbyte? C++ 版本没有使用它们,通常它们非常罕见 那么 const unsigned char* 和 unsigned char 的最佳映射对象是什么? @哈罗德 在 C# 中,字节通常用 byte 表示(以及字节数组、字节范围等) Event with byte & byte[] 我得到了同样的错误,所以我想应该是方法参数有问题。那么你知道如何用 C# 编写这些 C++ 代码吗? 这是问题的最后一个参数,装箱的结构无法转换为任何类似于本机类型的东西。 “上下文”类型的参数通常在 C 接口中用于传递指向 C++ 对象的指针,因此很容易将其转换为成员函数调用。你当然没有 C++ 对象。它完全被使用有点可疑,没有任何回调的迹象,但我们看不到足够的东西。因此,在委托声明中从 object 更改为 IntPtr 并传递 IntPtr.Zero。 Kabloom 如果它确实被使用了。 【参考方案1】:

一些变化:

C++,std::stringunsigned char*,因为很难在 C# 中编组它。

struct send_operation

    int session_id;
    unsigned char channel_id;
    unsigned char* data;
;

C#,object contextIntPtr context,因为send_operation 是一个结构体,这将传递装箱对象而不是结构体数据。

public delegate void Send(int sessionId, sbyte channelId,
    sbyte[] buffer, int len, IntPtr context);

如何通过:

IntPtr ptr = IntPtr.Zero;
try

    ptr = Marshal.AllocHGlobal(Marshal.SizeOf(op));
    Marshal.StructureToPtr(op, ptr, false);
    config.send_data(session_id, op.channel_id, bytes, op.data.Length, ptr);

finally

    Marshal.FreeHGlobal(ptr);

【讨论】:

感谢您的代码,我认为应该在方法调用中将字节更改为 sbytes,对吧?我做到了,但是当会话调用应用程序崩溃时。正如我已经解释的那样,它是第三方应用程序,我不知道如何处理 send_data,只是作为黑匣子工作,它可以在 C++ 代码中正常工作,但在 C# 中被破坏 如果不能修改c++库代码,那么你也需要在c#中实现std::string,但是你知道,不幸的是标准库的实现是多种多样的......字节和sbyte是二进制相同,任你选择。 我不需要更改 C++ 库,它可以正常工作。我只是想把它翻译成C# 正如我在上一条评论中所说的那样,您还需要将std::string 翻译成C#,但是1.这取决于您链接的标准库,2.它是一个复杂的类。 感谢@shingo,但我的问题是如何翻译 std::string?【参考方案2】:

之前让我对这类工作产生兴趣的一件事是“打包”/字对齐。

确认 send_operation 的确切大小。

在 C# 中,使用 StructLayout 属性指定结构的等效打包...

[StructLayout(LayoutKind.Sequential), Pack = 1] // for byte alignment
[StructLayout(LayoutKind.Sequential), Pack = 2] // for word (2 byte) alignment
[StructLayout(LayoutKind.Sequential), Pack = 4] // for native 32 bit (4 byte) alignment

或在必要时明确

[StructLayout(LayoutKind.Explicit)]

这要求每个成员都有FieldOffset 属性

[FieldOffset(0)]  // for 1st member
[FieldOffset(4)]  // 4 bytes from beginning of struct

在 C(++) 和 .Net 之间进行集成时,明确说明结构类型的这一方面总是一个好主意。

【讨论】:

以上是关于如何将 C++ 非托管 DLL 转换为 C#的主要内容,如果未能解决你的问题,请参考以下文章

如何在托管 (C#) 和非托管 (C++) 代码之间来回传递数组内容

将非托管 dll 的 Delphi 代码转换为 C#

编写用于 C# 的非托管 C++ DLL

如何从 C# 导入和使用非托管 C++ 类?

将 C# 字符串作为参数发送到非托管 C++ DLL 函数

从 C++ 非托管 dll 传递 C# 函数