4

I have unmanaged dll in C++ which is working properly, I try to re-implement it by C# but I get following error:

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++ code is as follow:

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);

Then it's translated in C# as follow:

[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);

UPDATE:

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}");
        }
    }
}
dymanoid
  • 14,771
  • 4
  • 36
  • 64
Peyman
  • 3,068
  • 1
  • 18
  • 32
  • Why all the `sbyte`? The C++ version didn't use them and normally they're super rare – harold Sep 17 '19 at 09:27
  • So what's the best mapped object for const unsigned char* and unsigned char? @harold – Peyman Sep 17 '19 at 09:38
  • In C# bytes are usually represented with `byte` (and array of bytes, span of bytes, etc) – harold Sep 17 '19 at 09:40
  • Event with byte & byte[] I get the same error, so I guess should be something wrong in method parameter. So do you have any idea how can write those C++ codes in C#? – Peyman Sep 17 '19 at 09:45
  • It is the last argument that is the problem, the boxed struct can't be converted to anything that resembles a native type. A "context" type of argument is usually employed in a C interface to pass a pointer to a C++ object so it is easy to turn it into a member function call. You have no C++ object of course. Somewhat questionable that it gets used at all, no sign of any callback, but we can't see enough. So change from object to IntPtr in the delegate declaration and pass IntPtr.Zero. Kabloom if it does get used. – Hans Passant Sep 17 '19 at 09:46
  • Thanks @HansPassant, I did the changes but the application which load the dll is crashed. The problem that application is kind of black box, I don't have access to it's code. Just it works with C++ code properly – Peyman Sep 17 '19 at 09:59
  • Please show the content of `Demo.on_session_available(Int32 session_id)` – shingo Sep 20 '19 at 03:37
  • Thanks @shingo, I updated the question – Peyman Sep 20 '19 at 04:17
  • I agree with @HansPassant, your `object` parameter should be an `IntPtr`. In addition the struct definition is wrong: you cannot pass a `string` as `std::string` object (see [here](https://stackoverflow.com/questions/20752001/passing-strings-from-c-sharp-to-c-dll-and-back-minimal-example), please correct me if i'm wrong on that end). Maybe you should keep a little c++ layer between the c# and c++ dll that manages that kind of stuff. – Streamline Sep 20 '19 at 06:28
  • Thanks @Streamline, I agree structure is wrong, but I don't know what's the correct one. I want to rid off from C++ and write all in C#. Otherwise C++ works properly and no need to add extra code. – Peyman Sep 20 '19 at 11:10

2 Answers2

4

Some changes:

C++, std::string to unsigned char*, because it's hard to marshal it in C#.

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

C#, object context to IntPtr context, because send_operation is a struct, this will pass the boxed object instead of the struct data.

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

How to pass:

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);
}
shingo
  • 18,436
  • 5
  • 23
  • 42
  • Thanks for your code, I think should change the bytes to sbytes in method call, right? I did but when the session call the the app crashed. As I already explain it's third party app which I don't know how handle the send_data, just work as black box which is working fine with C++ code but broken in C# – Peyman Sep 20 '19 at 11:05
  • If you can't change the c++ library code, then you need to implement `std::string` in c# too, but you know, unfortunately the implemention of standard libraries are various... Byte and sbyte are same in binary, so as you choose. – shingo Sep 20 '19 at 14:28
  • I don't need to change the C++ lib, it works properly. I just want to translate it to C# – Peyman Sep 20 '19 at 15:22
  • 1
    As I said in the previous comment, then you also need to translate `std::string` to C#, but 1. it depends on which standard library you linked, 2. it's a complex class. – shingo Sep 21 '19 at 04:19
  • thanks @shingo, but that's my question is how can I translate std::string? – Peyman Sep 22 '19 at 07:26
  • For example in visual c++ `std::string` is about 2000 lines (OK, you don't need to translate them all, but you must read them first), will you really want to translate it? There is a function [marshal_as](https://learn.microsoft.com/en-us/cpp/dotnet/marshal-as?redirectedfrom=MSDN&view=vs-2019) in c++/cli support library that can do the job, but it requires you to write c++ codes. So in my opinion make some small changes in your c++ library is the best approach – shingo Sep 25 '19 at 07:27
  • Thanks @shingo, but I can't change the C++, cause as I already mentioned it's 3rd party app which I don;t have access it, just working as black box – Peyman Sep 27 '19 at 12:15
0

One of the things that has caught me out with this sort of work before is "packing"/word-alignment.

Confirm the exact size of send_operation.

In C# specify equivalent packing of the struct with StructLayout attribute so...

[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

or if necessary explicitly

[StructLayout(LayoutKind.Explicit)]

which requires the FieldOffset attribute on every member

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

When doing integration between C(++) and .Net, it is always a good idea to be explicit about this aspect of your struct types.

AlanK
  • 1,827
  • 13
  • 16