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

4

我有一个在C++中的未托管dll,它能够正常工作,现在我想用C#重新实现它,但是我遇到了以下错误:

System.ArgumentException: Value does not fall within the expected range

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

ExceptionMethod: 8
ConvertToNative
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.StubHelpers.ObjectMarshaler
Void ConvertToNative(System.Object, IntPtr)

HResult:-2147024809

Source: 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}");
        }
    }
}

那么对于const unsigned char*和unsigned char,最好的映射对象是什么?@harold - Peyman
在C#中,字节通常用byte(以及字节数组、字节跨度等)表示。 - harold
谢谢@HansPassant,我已经做了更改,但是加载dll的应用程序崩溃了。问题在于该应用程序是一种黑盒子,我无法访问其代码。只有使用C++代码才能正常工作。 - Peyman
感谢@shingo,我更新了问题。 - Peyman
感谢@Streamline,我同意结构是错误的,但我不知道正确的结构是什么。我想要摆脱C++并全部用C#编写。否则,C++可以正常工作,无需添加额外的代码。 - Peyman
显示剩余5条评论
2个回答

4

一些变化:

C++中使用unsigned char*代替std::string,因为在C#中难以进行调用。

struct send_operation
{
    int session_id;
    unsigned char channel_id;
    unsigned char* data;
};

C#中,将object context转换为IntPtr 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);
}

谢谢你的代码,我认为在方法调用中应该将字节更改为有符号字节,对吗?我已经这样做了,但当会话调用时,应用程序崩溃了。正如我已经解释过的那样,这是一个第三方应用程序,我不知道如何处理send_data,它只是作为黑盒子工作,在C++代码中运行良好,但在C#中出现问题。 - Peyman
如果您无法更改C++库代码,则需要在C#中实现std::string,但是您知道,不幸的是标准库的实现是各种各样的...Byte和sbyte在二进制中相同,因此由您选择。 - shingo
我不需要更改C++库,它可以正常工作。我只想将其翻译成C#。 - Peyman
1
正如我在之前的评论中所说,你还需要将std::string翻译成C#,但是1.这取决于你链接了哪个标准库,2.它是一个复杂的类。 - shingo
谢谢 @shingo,但我的问题是如何翻译 std::string? - Peyman
显示剩余2条评论

0

在这种工作中,让我遇到困难的一件事是"打包"/字对齐。

确认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之间进行集成时,明确您的结构类型方面总是一个好主意。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接