Trouble with marshal c# class to c++ structure - c#

i have C++ structure
struct DressKey
{
int keyId;
GENDER gender;
DRESS_CLASS dressClass;
unsigned int descriptor[KEY_SIZE];
float confidence;
short keyLength;
short metric;
DressKey():
keyId(0),
gender(GENDER_UNDEFINED),
dressClass(CLASS_UNDEFINED),
confidence(0.0f),
keyLength(KEY_SIZE),
metric(0)
{
descriptor[0] = 0;
}
};
Type of GENDER and DRESS_CLASS is enum
And i try pass it in method:
virtual ERROR_CODE ExtractDressKey(
const unsigned char *frame,
int width,
int height,
int lineLength,
int format,
DressKey *dressKey);
I have wrap this method and structure in c# at this way:
[DllImport("DSE.dll", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?ExtractDressKey#DSE#dse##UEAA?AW4ERROR_CODE#2#PEBEHHHHPEAUDressKey#2##Z")]
private static extern ERROR_CODE ExtractDressKey(IntPtr self, IntPtr frame, int width, int height, int lineLength, int format, ref DressKey dressKey);
[StructLayout(LayoutKind.Sequential)]
public class DressKey
{
public const int KEY_SIZE = 5768;
public int keyId; //!< serialization key id
public GENDER gender; //!< gender
public DRESS_CLASS dressClass; //!< dress category
[MarshalAs(UnmanagedType.ByValArray, SizeConst = KEY_SIZE)]
public uint[] descriptor = new uint[KEY_SIZE]; //!< comparable part
public double confidence; //!< key confidence
public short keyLength; //!< actual numer of elements in descriptor
public short metric; //!< ID of a metric revision
}
I got AccessViolationException. What is wrong in my wrapper?
UPD:
usage:
var imgPath = #"C:\1.jpg";
using (var dse = new DSE())
{
using (var img = CV.LoadImageM(imgPath, LoadImageFlags.AnyColor))
{
var subImg = img;
using (subImg)
{
var dk = new DressKey();
var type = subImg.Channels == 1 ? 0 : 1;
var res = dse.ExtractDressKey(subImg.Data, subImg.Cols, subImg.Rows, subImg.Cols, type, ref dk);
if (res == ERROR_CODE.ERROR_OK)
Console.WriteLine("id={0}&key={1}", 123, JsonConvert.SerializeObject(dk));
else
{
Console.WriteLine( res.ToString());
}
}
}
}

You cannot expect to p/invoke C++ instance methods. P/invoke is used to call non-member functions. You need to do one of the following:
Wrap the C++ library in a mixed mode C++/CLI assembly, and have the C# code consume it as a managed reference.
Expose the C++ library using COM.
Wrap the C++ library in a C style procedural interface and access that using p/invoke.

Related

Passing string as blittable type for UnmanagedCallersOnly native method

Trying to use the UnmanagedCallersOnly attribute but I'm stuck on some string fields in the structure. Is it possible to model this structure that is usable with the new UnmanagedCallersOnly attribute?
C++ structure:
struct PluginInfo {
int nStructSize;
int nType;
int nVersion;
char szName[ 64 ];
char szVendor[ 64 ];
};
I'm getting stuck on converting the szName and szVendor to c#.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct PluginInfo
{
public int StructSize;
[MarshalAs(UnmanagedType.I4)]
public PluginType Type; // PluginType is an enumeration
public int Version;
[MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] //[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 64)]
public string Name;
[MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] //[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 64)]
public string Vendor;
}
public class Plugin
{
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
public static void GetPluginInfo(ref PluginInfo pluginInfo)
{
pluginInfo.Name = "myPluginName";
pluginInfo.Vendor = "myVendorName";
pluginInfo.Type = PluginType.Data;
pluginInfo.StructSize = Marshal.SizeOf((PluginInfo)pluginInfo);
}
}
The error is "Cannot use 'PluginInfo' as a parameter type on a method attributed with 'UnmanagedCallersOnly'"

How can I convert C++ unmanaged DLL to C#

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

C# struct serialization in different applications (WPF and Unity) returns byte array of different sizes

I have a dll that has structs definitions and also few functions to serialize and de-serialize them. I wanted to put these in a dll so the exact same structs could be shared among different apps.
The dll is shared by a WPF application (that sends the serialized data via MQTT) and a HoloLens app (developed with Unity and VS), which receives that data and attempts to de-serialize it by using the same dll.
The problem is that while the WPF app serializes the struct, Marshal.SizeOf(str) returns a size of 12. Whereas the exact same dll function in the Unity C# scripts app returns a size of 24. Furthermore, the last 12 bytes in the array are all 0.
Therefore, there is an out of range exception when trying to deserialize objects that come from the WPF app because they are half the size expected in the Unity scripts.
This is an example of the struct I am serializing:
[Serializable]
public struct SimulationVariableModel
{
public string Name { get; set; }
public string Category { get; set; }
public string ObjectId { get; set; }
}
Here is the function to serialize the Struct to a byte array:
public static byte[] StrucToByteArray<T>(T str) where T : struct
{
// In Unity: size = 24
// In standalone WPF and UWP application: size = 12
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(str, ptr, false);
Marshal.Copy(ptr, arr, 0, size);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return arr;
}
And the function to convert the byte array into the original struct:
public static T ByteArrayToStruct<T>(byte[] arr) where T : struct
{
T str = default;
int size = Marshal.SizeOf(str);
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.Copy(arr, 0, ptr, size);
str = (T)Marshal.PtrToStructure(ptr, str.GetType());
} finally
{
Marshal.FreeHGlobal(ptr);
}
return str;
}
This is the event handler that receives the serialized data in Unity and tries to de-serialize it to the original struct:
private void Client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
if (e.Topic == NewSimulationVariableTopic) {
// Dummy item to test that the byte Array length is not due to extra data in e.Message.
SimulationVariableModel test = new SimulationVariableModel { Name = "Bla", Category = "Bla" };
// testByteArray.Length = 24
byte[] testByteArray = StrucToByteArray(test);
// De-serialization is successful
test = ByteArrayToStruct<SimulationVariableModel>(testByteArray);
// e.Message is the result from serialization
// using StructToByteArray<SimulationVariableModel>(str) in standalone WPF and UWP app.
// De -serialization fails because e.Message.Length = 12
SimulationVariableModel simVar = ByteArrayToStruct<SimulationVariableModel>(e.Message);
// Logic with SimulationVariableModel
}
}
I am thinking this might be caused by the Unity environment? I tested a UWP standalone app, which serializes the Structs in 12 bytes and not 24.
Does anyone have an idea of what could be happening and how to fix this issue?
Thanks so much.
As suggested by a few comments, the strings were being treated differently in the two platforms. I solved this specific problem by specifying how the structs are serialized in their definition. Example:
[Serializable]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct SimulationVariableModel
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public readonly string Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public readonly string Category;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public readonly string Id;
public SimulationVariableModel(string name, string category, string objectId)
{
Name = name;
Category = category;
Id = objectId;
}
}
However, for a more robust solution I might be better off using an actual serializer as suggested by Mark Gravell and bassfader.
Cheers!

C# PInvoke issue

I have been trying to replicate the following C++ function in my C# application but I am getting an AccessViolationException at the time the method "ObjSearchObject2" is invoked. Pretty sure I am doing a lot of things wrong. The C++ function and the corresponding headers looks like this,
bool DocumentSearch(char *pFileId)
{
const int NUM_CONDITIONS = 1;
const int NUM_TYPE_DEFNS = 1;
ObjSearchObjectParm rSearchObject;
ObjSearchCondition rSearchCondition;
ObjObjectResults rSearchResults;
char *pTypeDefnIdArray[NUM_TYPE_DEFNS];
bool bReturnValue = false;
memset(&rSearchObject, 0, sizeof(ObjSearchObjectParm));
memset(&rSearchCondition, 0, sizeof(ObjSearchCondition));
memset(&rSearchResults, 0, sizeof(ObjObjectResults));
pTypeDefnIdArray[0] = "dotdA9";//Documents
rSearchObject.obj_defn_ids = pTypeDefnIdArray;
rSearchObject.obj_defn_count = NUM_TYPE_DEFNS;
rSearchObject.num_conditions = NUM_CONDITIONS;
rSearchObject.max_results = 5000;
rSearchObject.search_cond = &rSearchCondition;
rSearchCondition.field = "ancestor";
rSearchCondition.op = "is";
rSearchCondition.value = pFileId;
if (ObjSearchObject2(&rSearchObject, &rSearchResults))
{
printf("ERROR: ObjSearchObject2 returned: %s \n", ObjGetErrorMsg());
}
else
{
printf("INFO : Number of returned results = %d \n", rSearchResults.num_results);
if (rSearchResults.num_results > 0)
{
for (int iIndex = 0; iIndex < rSearchResults.num_results; iIndex++)
{
if (rSearchResults.object_handle[iIndex])
{
printf("INFO : Object Id returned: %s (name=%s, updated=%s, type=%s, state=%s) \n",
ObjGetObjectAttr(rSearchResults.object_handle[iIndex], "id_object"),
ObjGetObjectAttr(rSearchResults.object_handle[iIndex], "name"),
ObjGetObjectAttr(rSearchResults.object_handle[iIndex], "date_update"),
ObjGetObjectAttr(rSearchResults.object_handle[iIndex], "id_type_definition"),
ObjGetObjectAttr(rSearchResults.object_handle[iIndex], "num_state")
);
ObjFreeObjectHdl(rSearchResults.object_handle[iIndex]);
}
}
bReturnValue = true;
}
}
return bReturnValue;
}
int ObjSearchObject2(ObjSearchObjectParm *, ObjObjectResults *);
char * ObjGetObjectAttr(ObjApiObjectHdl objectHdl, char *attr_name);
char * ObjGetErrorMsg();
void ObjFreeObjectHdl(ObjApiObjectHdl objectHdl);
typedef struct _ObjSearchObjectParm {
int obj_defn_count; // mandatory
char **obj_defn_ids; // mandatory
char *text_server_alias; // optional.
char *query_string; // optional text search string
ObjApiBoolean include_deleted; // defaults to OBJAPI_FALSE
int max_results; // default = 200
int num_conditions; // mandatory
ObjSearchCondition *search_cond; // mandatory
ObjApiBoolean include_content; // mandatory for COMPLEX searches, translated to Y/N in API
ObjApiBoolean include_metadata; // mandatory for COMPLEX searches, translated to Y/N in API
} ObjSearchObjectParm;
enum ObjApiSearchOp
{
OBJAPI_MATCH_ALL=1,
OBJAPI_MATCH_ANY=2
};
enum ObjApiBoolean
{
OBJAPI_FALSE,
OBJAPI_TRUE
};
typedef struct _ObjSearchCondition {
ObjApiSearchOp boolop; // only for complex searches
char *join_relation; // only for complex searches
int num_opening_brackets; // only for complex searches
int num_closing_brackets; // only for complex searches
char *field;
char *op;
char *value;
} ObjSearchCondition;
typedef void* ObjApiObjectHdl;
typedef struct _ObjObjectResults {
ObjApiObjectHdl *object_handle;
int num_results;
} ObjObjectResults;
My take on this is as follows, (UPDATE: Code updated)
public class ObjectiveNativeAPI
{
private const string dllPath = #"objapi.dll";
public enum ObjApiBoolean
{
OBJAPI_FALSE,
OBJAPI_TRUE
};
public enum ObjApiSearchOp
{
OBJAPI_MATCH_ALL = 1,
OBJAPI_MATCH_ANY = 2
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct ObjSearchObjectParm
{
public int obj_defn_count;
public string[] obj_defn_ids;
public string text_server_alias;
public string query_string;
public ObjApiBoolean include_deleted;
public int max_results;
public int num_conditions;
public IntPtr search_cond;
public ObjApiBoolean include_content;
public ObjApiBoolean include_metadata;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct ObjSearchCondition
{
public ObjApiSearchOp boolop;
public string join_relation;
public int num_opening_brackets;
public int num_closing_brackets;
public string field;
public string op;
public string value;
}
[StructLayout(LayoutKind.Sequential)]
public struct ObjObjectResults
{
public IntPtr object_handle;
public int num_results;
}
[DllImport(dllPath, EntryPoint = "ObjSearchObject2")]
public static extern int ObjSearchObject2(ref ObjSearchObjectParm objSearchObjectParm, ref ObjObjectResults objObjectResults);
[DllImport(dllPath, EntryPoint = "ObjGetObjectAttr")]
public static extern string ObjGetObjectAttr(IntPtr object_handle, string attr_name);
[DllImport(dllPath, EntryPoint = "ObjFreeObjectHdl")]
public static extern void ObjFreeObjectHdl(IntPtr object_handle);
}
public void Run()
{
ObjectiveNativeAPI.ObjSearchObjectParm rSearchObject = new ObjectiveNativeAPI.ObjSearchObjectParm();
ObjectiveNativeAPI.ObjSearchCondition rSearchCondition = new ObjectiveNativeAPI.ObjSearchCondition();
ObjectiveNativeAPI.ObjObjectResults rSearchResults = new ObjectiveNativeAPI.ObjObjectResults();
rSearchCondition.field = "ancestor";
rSearchCondition.op = "is";
rSearchCondition.value = txtCotainerId.Text;
rSearchObject.obj_defn_ids = new[] {"dotdA9"};
rSearchObject.obj_defn_count = 1;
rSearchObject.num_conditions = 1;
rSearchObject.max_results = 5000;
IntPtr search_cond = Marshal.AllocCoTaskMem(Marshal.SizeOf(rSearchCondition));
Marshal.StructureToPtr(rSearchCondition, search_cond, false);
rSearchObject.search_cond = search_cond;
int result = ObjectiveNativeAPI.ObjSearchObject2(ref rSearchObject, ref rSearchResults);
MessageBox.Show(string.Format("FunctionResult: {0}", result));
Marshal.FreeCoTaskMem(search_cond);
}
I am a bit lost on this. I have managed to translate some other segments of this project but this is causing me grief. Any help around this would be appreciated.

Marshalling C++ char[] members results in empty string

Im attempting to marshal a C++ struct containing char[] arrays to C# but on the unmanaged side I see the char arrays as empty and not sure what I'm missing
C# structure
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1), Serializable]
public struct MParams
{
public int action;
public int response;
public long serial_no;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 251)]
public string address; // public char[] address;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 81)]
public string description; // public char[] description;
}
C++ Structure
typedef struct
{
int action;
int response;
LONG serial_no;
char address[251];
char description[81];
}PARAMS;
C++ Exported function
__declspec(dllexport) (int) test_Email(void *params)
{
PARAMS *l_params = (PARAMS *)params;
// params->address // always empty expected abc#abc.com
}
C# Usage
[DllImport(#"test.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint ="?test_Email##YAHPAX#Z")]
public static extern int EMailExists(IntPtr valParams);
MParams mParams = new MParams {
action = 3,
serial_no = 0,
address = abc#abc.com,
description = string.Empty
};
IntPtr pAddr = Marshal.AllocHGlobal(Marshal.SizeOf(mParams));
Marshal.StructureToPtr(mParams, pAddr, false);
EMailExists(pAddr);
mParams = (MParams)Marshal.PtrToStructure(pAddr, typeof(MParams));
I've attempted using char[] to marshall but that too did not help. I reckon I'm missing on something very obvious and help to understand where I'm wrong is much appreciated.

Categories

Resources