Reputation: 3136
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}");
}
}
}
Upvotes: 4
Views: 690
Reputation: 1974
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.
Upvotes: 0
Reputation: 27432
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);
}
Upvotes: 4