I have some problem with marshalling and PInvoke I have to develop some kind of driver for existing native application (Oracle Siebel CRM, call center integration interface). Sources of the application is black box from my point of view.
Here is the signature of two structures (as defined in API description) and API function which is working with they:
struct ISC_KeyValue /* Key-Value element */
{
ISC_STRING paramName;
ISC_STRING paramValue;
};
struct ISC_KVParamList /* List of Key-Value parameter */
{
struct ISC_KeyValue* dataItems;
long len;
};
ISCAPI ISC_RESULT CreateISCDriverInstance
/* in */(const ISC_STRING mediaTypeStr,
/* in */ const ISC_STRING languageCode,
/* in */ const ISC_STRING connectString,
/* in */ const struct ISC_KVParamList* datasetParams,
/* out */ ISC_DRIVER_HANDLE* handle);
ISC_STRING here is wide character C++ string(2 bytes Unicode). ISC_RESULT is integer value similar to HRESULT type.
I have a problem with marshaling of ISC_KVParamList* datasetParams in to CreateISCDriverInstance function and i need help with it.
Here is my vision on how I should marshal this:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct IscKeyValue
{
[MarshalAs(UnmanagedType.LPWStr)]
public string ParamName;
[MarshalAs(UnmanagedType.LPWStr)]
public string ParamValue;
}
[StructLayout(LayoutKind.Sequential)]
public struct IscKvParamList
{
public IntPtr DataItems;
[MarshalAs(UnmanagedType.I4)]
public int Len;
}
And the function itself:
[DllExport("CreateISCDriverInstance", CallingConvention = CallingConvention.StdCall)]
public static int CreateIscDriverInstance([MarshalAs(UnmanagedType.LPWStr)] string mediaTypeStr,
[In,MarshalAs(UnmanagedType.LPWStr)] string languageCode,
[In,MarshalAs(UnmanagedType.LPWStr)] string connectString,
[In, Out] ref IscKvParamList dataParams, [Out] IntPtr handle)
{
var datasetParams =(IscKvParamList)Marshal.PtrToStructure(dataParams,typeof(IscKvParamList));
// Some code here
}
When i'm trying to perform PtrToStructure conversion I get the exception which is saying that "The structure must not be a value class."
Question: How to improve marshalling of dataParams argument
p.s.
Additional details :
1. I'm sure that charset is Unicode
2. I do not know the size of array and I cannot marshal it by value.
3. Also, i have example implementation of such driver which was written a long time ago in Delphi:
TNamedParam = record
Name: WideString;
Value: WideString;
end;
TNamedParamList = record
Params: packed array of TNamedParam;
Count: Cardinal;
end;
function CreateISCDriverInstance(const AMediaTypeStr, ALanguageCode, AConnectString: PWideChar; const AParams: TISCNamedParamList; out ADriverHandle: THandle):
HRESULT; begin try ADriverHandle := icISCCommunicationDriver.CreateInstance(AParams).Handle;
Result := SC_EC_OK;
except Result := SC_EC_DRIVER_CREATION_ERR;
end;
end;
And parsing of AParams:
procedure JoinISCNamedParamList(const AParamList: TNamedParamList; var LParamList: TISCNamedParamList);
var i:Integer;
begin
LParamList.Count := AParamList.Count;
SetLength(LParamList.Params, LParamList.Count);
for I := 0 to Pred(AParamList.Count) do
begin
LParamList.Params[i].Name := PWideChar(AParamList.Params[i].Name);
LParamList.Params[i].Value := PWideChar(AParamList.Params[i].Value);
end;
end;
It's possible to say that Layout of structs is Sequential here.
The information in the question is incomplete. A websearch reveals code that says:
typedef wchar_t ISC_CHAR
typedef ISC_CHAR* ISC_STRING;
That tallies with what you indicate in the question. So, let's go with that. However, it would have been much better if you could have provided that information which can be found in one of your header files.
Naively your structs should be:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct IscKeyValue
{
public string ParamName;
public string ParamValue;
}
[StructLayout(LayoutKind.Sequential)]
public struct IscKvParamList
{
public IscKeyValue[] DataItems;
public int Len;
}
No need for the MarshalAs that you used because the default marshalling suffices.
But the marshaller just won't deal with the array inside the struct. So I think you'll need to do it like this:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct IscKeyValue
{
public IntPtr ParamName;
public IntPtr ParamValue;
}
[StructLayout(LayoutKind.Sequential)]
public struct IscKvParamList
{
public IntPtr DataItems;
public int Len;
}
As for the function, it translates as:
[DllExport("CreateISCDriverInstance", CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Unicode, ExactSpelling = true)]
public static int CreateIscDriverInstance(
string mediaTypeStr,
string languageCode,
string connectString,
[In] ref IscKvParamList dataParams,
out IntPtr handle
);
And now the fun. Preparing the IscKvParamList parameters.
Start with an array of IscKeyValue:
IscKeyValue[] KeyValueArr = new IscKeyValue[...];
for (int i = 0; i < KeyValueArr.Length; i++)
{
KeyValueArr[i].ParamName = Marshal.StringToCoTaskMemUni(...);
KeyValueArr[i].ParamValue = Marshal.StringToCoTaskMemUni(...);
}
Now the IscKvParamList. I think that I would pin the array. Like this:
GCHandle ArrHandle = GCHandle.Alloc(KeyValueArr, GCHandleType.Pinned);
try
{
IscKvParamList dataParams;
dataParams.DataItems = ArrHandle.AddrOfPinnedObject();
dataParams.Len = KeyValueArr.Length;
int retval = CreateIscDriverInstance(
mediaTypeStr,
languageCode,
connectString,
ref dataParams,
out handle
);
}
finally
{
ArrHandle.Free();
}
You must make sure that you remember to deallocate the unmanaged memory allocated in the calls to Marshal.StringToCoTaskMemUni.
Related
a simple Codesnippet should demonstrate the problem:
C:
typedef struct {
//unsigned short tno ;
unsigned long avf ;
}
MY_MAZ_TD;
__declspec(dllexport) void CMazGetAllToolData(MY_MAZ_TD* maz_td)
{
maz_td[0].avf = 4;
maz_td[1].avf = 5;
}
C#:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MY_MAZ_TD
{
//public ushort tno; /* Tool number */
public ulong avf;
}
[DllImport(TEST_DLL, CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern int CMazGetAllToolData([In, Out] MY_MAZ_TD[] maz_td);
MY_MAZ_TD[] my_maz_td = new MY_MAZ_TD[num];
CMazGetAllToolData(my_maz_td);
After calling CMazGetAllToolData:
my_maz_td[0].avf = 21474836484;
my_maz_td[1].avf = 0;
Actually I have much more values in the structure (like the commented tno). But even with only one variable in the struct, the values get scrambled. What is wrong here?
I have the following C-calls to libACL:
extern int acl_get_entry(acl_t acl, int entry_id, acl_entry_t *entry_p);
extern int acl_get_permset(acl_entry_t entry_d, acl_permset_t *permset_p);
And I've tracked the typedefs to
typedef struct __acl_permset_ext *acl_permset_t;
typedef struct __acl_entry_ext *acl_entry_t;
typedef struct __acl_ext *acl_t;
in /usr/include/acl/libacl.h and /usr/include/sys/acl.h
So unless I made an error, this means the above native calls are equivalent to:
extern int acl_get_entry(__acl_ext *acl, int entry_id, __acl_entry_ext **entry_p);
extern int acl_get_permset(__acl_ext *entry_d, __acl_permset_ext **permset_p);
Now I'm a bit at a loss about how to map these to C#...
I first thought I could just do this:
// extern int acl_get_entry(acl_t acl, int entry_id, acl_entry_t *entry_p);
[SuppressUnmanagedCodeSecurity]
[DllImport("acl", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl, EntryPoint = "acl_get_entry")]
internal static extern int acl_get_entry(__acl_ext* acl, AclEntryConstants entry_id, ref __acl_entry_ext entry_p); // Double pointer, correct ???
And that even works, at least apparently.
But when I do the same with acl_get_permset
// extern int acl_get_permset(acl_entry_t entry_d, acl_permset_t *permset_p);
[SuppressUnmanagedCodeSecurity]
[DllImport("acl", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl,
EntryPoint = "acl_get_permset")]
internal static extern int acl_get_permset(__acl_entry_ext* entry_d, __acl_permset_ext** permset_p); // double pointer ?
aka
internal static extern int acl_get_permset(__acl_entry_ext* entry_d, ref __acl_permset_ext permset_p); // double pointer ?
Then it doesn't work...
I have written the following C-code to check:
int main()
{
// Get all the entries
acl_entry_t acl_entry_;
acl_permset_t permission_set;
acl_tag_t acl_kind_tag;
const char* _filename = "/root/Desktop/CppSharp.txt";
acl_t acl_file = acl_get_file(_filename, ACL_TYPE_ACCESS);
int found = acl_get_entry(acl_file, ACL_FIRST_ENTRY, &acl_entry_);
int a = acl_get_permset(acl_entry_, &permission_set);
int b = acl_get_tag_type(acl_entry_, &acl_kind_tag);
printf("a: %d; b: %d\n", a, b);
acl_entry new_acl;
new_acl.reading = ACL_GET_PERM(permission_set, ACL_READ);
new_acl.writing = ACL_GET_PERM(permission_set, ACL_WRITE);
new_acl.execution = ACL_GET_PERM(permission_set, ACL_EXECUTE);
return 0;
}
and that returns a non -1 value for a and b.
But my C# code, which does exactly the same (or so I thought), gets to int found = 1 (just like C), but then it returns -1 for a and b...
static unsafe void ReadACL()
{
string fileName = "/root/Desktop/CppSharp.txt";
global::acl.__acl_ext* acl_file = NativeMethods.acl_get_file(fileName, global::acl.acl_type_t.ACL_TYPE_ACCESS);
global::acl.__acl_entry_ext acl_entry_ = new global::acl.__acl_entry_ext();
int found = NativeMethods.acl_get_entry(acl_file, global::acl.AclEntryConstants.ACL_FIRST_ENTRY, ref acl_entry_);
System.Console.WriteLine(found);
global::acl.__acl_permset_ext permission_set;
acl_tag_t acl_kind_tag = acl_tag_t.ACL_UNDEFINED_TAG;
int a = NativeMethods.acl_get_permset(&acl_entry_, &permission_set);
global::acl.acl_tag_t tag_type = acl_tag_t.ACL_UNDEFINED_TAG;
int b = NativeMethods.acl_get_tag_type(&acl_entry_, &tag_type);
System.Console.WriteLine($"{a} {b}");
Also, the strangest thing - I've searched the following header files:
/usr/include/acl/libacl.h
/usr/include/sys/acl.h
and the entire /usr/include folder for __acl_permset_ext and __acl_entry_ext, but I needed to google them, as they are nowhere defined... Does the C-Compiler just use pointers, without needing the structs at all ?
Also, prior to doing it manually, I've tried to create the bindings automatically with CppSharp, but these auto-generated bindings had the very same issue...
mono ./CppSharp.CLI.exe --arch=x64 --output=/home/username/RiderProjects/TestProject/TestProject/AUTOMAPPED/ /usr/include/acl/libacl.h /usr/include/sys/acl.h
And one more thing I noticed:
What sense does it make to pass a double-pointer ?
That's like
struct x;
function(ref &x)
which makes very little sense IMHO, as your passing the address by reference.
Are these perhaps arrays ?
Like
struct[] x;
function(ref x)
Here are the constants:
// #define ACL_UNDEFINED_ID ((id_t)-1)
// acl_check error codes
public enum acl_check_errors
: int
{
ACL_MULTI_ERROR = (0x1000), // multiple unique objects
ACL_DUPLICATE_ERROR = (0x2000), // duplicate Id's in entries
ACL_MISS_ERROR = (0x3000), // missing required entry
ACL_ENTRY_ERROR = (0x4000) // wrong entry type
}
// 23.2.2 acl_perm_t values
public enum acl_perm_t
: uint
{
ACL_READ = (0x04),
ACL_WRITE = (0x02),
ACL_EXECUTE = (0x01),
// ACL_ADD = (0x08),
// ACL_DELETE = (0x10),
}
// 23.2.5 acl_tag_t values
public enum acl_tag_t
: int
{
ACL_UNDEFINED_TAG = (0x00),
ACL_USER_OBJ = (0x01),
ACL_USER = (0x02),
ACL_GROUP_OBJ = (0x04),
ACL_GROUP = (0x08),
ACL_MASK = (0x10),
ACL_OTHER = (0x20)
}
public enum acl_type_t
: uint
{
ACL_TYPE_ACCESS = (0x8000),
ACL_TYPE_DEFAULT = (0x4000)
}
// 23.2.8 ACL Entry Constants
public enum AclEntryConstants
: int
{
ACL_FIRST_ENTRY = 0,
ACL_NEXT_ENTRY = 1,
}
And this are the structs that I googled together:
// https://kernel.googlesource.com/pub/scm/fs/ext2/xfstests-bld/+/301faaf37f99fc30105f261f23d44e2a0632ffc0/acl/libacl/libobj.h
// https://kernel.googlesource.com/pub/scm/fs/ext2/xfstests-bld/+/301faaf37f99fc30105f261f23d44e2a0632ffc0/acl/libacl/libobj.h
// https://kernel.googlesource.com/pub/scm/fs/ext2/xfstests-bld/+/301faaf37f99fc30105f261f23d44e2a0632ffc0/acl/libacl/libacl.h
// https://allstar.jhuapl.edu/repo/p1/amd64/acl/libacl.h
// https://kernel.googlesource.com/pub/scm/fs/ext2/xfstests-bld/+/301faaf37f99fc30105f261f23d44e2a0632ffc0/acl/libacl/libacl.h
// https://kernel.googlesource.com/pub/scm/fs/ext2/xfstests-bld/+/301faaf37f99fc30105f261f23d44e2a0632ffc0/acl/libacl/acl_get_fd.c
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public unsafe struct obj_prefix
{
public ulong p_magic;
public ulong p_flags;
}
// typedef struct __acl_permset_ext *acl_permset_t;
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public unsafe struct __acl_permset_ext
{
// permset_t s_perm; // typedef unsigned int permset_t;
public uint s_perm;
};
// typedef struct acl_permset_obj_tag acl_permset_obj;
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public unsafe struct acl_permset_obj_tag
{
public obj_prefix o_prefix;
public __acl_permset_ext i;
};
// #define __U32_TYPE unsigned int
// #define __ID_T_TYPE __U32_TYPE
// __STD_TYPE __ID_T_TYPE __id_t; /* General type for IDs. */
// typedef __id_t id_t;
/* qualifier object */
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public unsafe struct __qualifier_ext
{
//id_t q_id;
public uint q_id;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public unsafe struct qualifier_obj_tag
{
public obj_prefix o_prefix;
public __qualifier_ext i;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public unsafe struct acl_entry_obj_tag
{
public obj_prefix o_prefix;
public __acl_entry_ext i;
}
// typedef struct __acl_ext *acl_t;
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public unsafe struct __acl_ext
{
// typedef struct acl_entry_obj_tag acl_entry_obj;
// acl_entry_obj *a_prev, *a_next;
// acl_entry_obj *a_curr;
// acl_entry_obj *a_prealloc, *a_prealloc_end;
public acl_entry_obj_tag* a_prev;
public acl_entry_obj_tag* a_next;
public acl_entry_obj_tag* a_curr;
public acl_entry_obj_tag* a_prealloc;
public acl_entry_obj_tag* a_prealloc_end;
// size_t a_used; // typedef __SIZE_TYPE__ size_t;
public ulong a_used;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public unsafe struct acl_obj_tag
{
public obj_prefix o_prefix;
public __acl_ext i;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public unsafe struct __acl_entry
{
acl_tag_t e_tag;
// qualifier_obj e_id; // typedef struct qualifier_obj_tag qualifier_obj;
qualifier_obj_tag e_id;
// acl_permset_obj e_perm; //typedef struct acl_permset_obj_tag acl_permset_obj;
acl_permset_obj_tag e_perm;
}
// typedef struct __acl_entry_ext *acl_entry_t;
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public unsafe struct __acl_entry_ext
{
// acl_entry_obj *e_prev, *e_next; // typedef struct acl_entry_obj_tag acl_entry_obj;
public acl_entry_obj_tag* e_prev;
public acl_entry_obj_tag* e_next;
// acl_obj *e_container; // typedef struct acl_obj_tag acl_obj;
public acl_obj_tag* e_container;
public __acl_entry e_entry;
}
From looking here, those ACL types are defined as:
struct __acl_ext;
struct __acl_entry_ext;
struct __acl_permset_ext;
typedef struct __acl_ext *acl_t;
typedef struct __acl_entry_ext *acl_entry_t;
typedef struct __acl_permset_ext *acl_permset_t;
We've been told that the struct __acl_ext exists, but we don't get to see how it's defined: we don't know what fields it has. Obviously it's properly defined in another (private) header or source file, but we don't have visibility to those: they're private.
On the face of it this seems like a problem: how can we use these structs if we don't know how large they are, or how their fields are laid out? Look further, and you can see that we only ever interact with pointers to these structs: ACL functions will give us back a pointer, which we can then pass to other ACL functions. We're never expected to dereference the pointers ourselves. The ACL code of course knows what the pointers point to, but that's hidden from us.
This is called an opaque pointer.
(This can be a useful strategy. For example, it lets the ACL library change how the struct is defined without breaking consumers. It also stops us from changing fields in these structs directly, which might break the ACL library).
So. We shouldn't be trying to define C# types for these structs at all: we're very much not meant to be doing that. The C# type for an opaque pointer is IntPtr, so let's use that:
// extern int acl_get_entry(acl_t acl, int entry_id, acl_entry_t *entry_p);
[SuppressUnmanagedCodeSecurity]
[DllImport("acl", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acl_get_entry")]
internal static extern int acl_get_entry(IntPtr acl, AclEntryConstants entry_id, out IntPtr entry_p);
// extern int acl_get_permset(acl_entry_t entry_d, acl_permset_t *permset_p);
[SuppressUnmanagedCodeSecurity]
[DllImport("acl", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acl_get_permset")]
internal static extern int acl_get_permset(IntPtr entry_d, out IntPtr permset_p);
We could use ref or out for the IntPtr. Reading the docs, it looks like the C code never reads the value of the double pointer you pass in: it just uses it as a way of passing a pointer back out. Therefore we use out.
I got a function like this defined in a C-DLL:
int AcquireArchive (char* archiveName, Password* password)
The struct Password is defined in the DLL as:
typedef struct password {
unsigned char* pPwd;
unsigned long length;
} Password;
What I am doing is trying to wrap this function in C# using:
[DllImport("name.dll", CharSet = CharSet.Ansi, EntryPoint = "_AcquireArchive#16",
CallingConvention = CallingConvention.StdCall)]
public static extern int AcquireArchive(
[MarshalAs(UnmanagedType.LPStr)] string archiveName,
ref Password password
);
and:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Password
{
public string pwd;
public ulong length;
}
I call this function by:
Password password;
password.pwd = "password";
password.length = (ulong)password.pwd.Length;
int code = AcquireArchive("Archive", ref password);
Now the problem is that the returned code signals INVALID PASSWORD, indicating (according to the documentation) a password-length of 0.
Is it possible the value of password.pwd and/or password.length get lost in the invocation/marshalling process?
Sadly I don't have access to the source-code of the dll.
EDIT: CharSet is now Ansi, TYPO "length", removed [UnmanagedType.BStr]
at least one problem I see:
in your C Code (length is 32 or 64 bit depending to the DLL):
typedef struct {
unsigned char* pPwd;
unsigned long length;
} Password;
and in your C# code(length is 64 bit):
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Password
{
public string pwd;
public ulong length;
}
if it is 64 bit , it is OK.
but if it is 32 bit, you should change your C# ulong to uint like this:
public struct Password
{
public string pwd;
public uint length;
}
I hope this helps.
I have an unmanaged interface I'm trying to marshal and use in C#.
And there is a function I'm not sure how to marshal correctly:
IDataInfo :
public IUnknown {
...
STDMETHOD_(BOOL, GetDataPackInfo) (UINT packIndex, void* pPackExtendedInfo) = 0;
...
}
The void* can be one of two different structures:
struct DataExtendedInfoArchive {
WORD Size;
BOOL Archived;
UINT SignalLength;
BYTE Captured;
};
struct DataExtendedInfoStorage {
WORD Size;
FLOAT SignalFreq;
UINT SignalLength;
CHAR Code[4];
};
I implement those in C# like this:
[StructLayout(LayoutKind.Sequential)]
public struct TrackExtendedInfoAudio
{
int Size;
[MarshalAs(UnmanagedType.Bool)]
bool Archived;
uint SignalLength;
byte Captured;
}
[StructLayout(LayoutKind.Sequential)]
public struct TrackExtendedInfoVideo
{
public int Size;
public double SignalFreq;
public uint SignalLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public StringBuilder Code;
}
The problem is I don't exactly understand what I'm going to get in void* pPackExtendedInfo and how to handle it and therefore don't know how to write a correct marshaling signature for this function.
The managed function signature (minus attributes and decorations) should be:
// make sure that the return is marshalled as UnmanagedTypes.Boolean.
bool GetDataPackInfo(uint packIndex, IntPtr pPackExtendedInfo);
To unpack the struct, first you need to determine which one you're working with. Fortunately the first member of each is a size parameter, which will give a clue as to the size of the struct. To read that size, then unpack the structure:
IntPtr ptr; // this is the pointer passed to your callback.
int cbSize = Marshal.ReadInt32(ptr, 0);
if (cbSize = Marshal.SizeOf(TrackExtendedInfoAudio))
{
TrackExtendedInfoAudio s = Marshal.PtrToStructure(ptr, typeof(TrackExtendedInfoAudio))
as TrackExtendedInfoAudio;
// Processing...
}
else if (cbSize == Marshal.SizeOf(TrackExtendedInfoVideo))
{
TrackExtendedInfoVideo s = Marshal.PtrToStructure(ptr, typeof(TrackExtendedInfoVideo))
as TrackExtendedInfoVideo;
// Processing...
}
else
{
// unknown struct
}
You could marshal void* as IntPtr:
private static extern bool GetDataPackInfo(uint packIndex, [In,Out] IntPtr pPackExtendedInfo);
And copy structure using one of the Marshal.PtrToStructure (and Marshal.StructureToPtr) methods:
IntPtr p = IntPtr.Zero;
GetDataPackInfo(..., p);
TrackExtendedInfoAudio audioInfo = Marshal.PtrToStructure<TrackExtendedInfoAudio>(p);
or
TrackExtendedInfoVideo videoInfo = Marshal.PtrToStructure<TrackExtendedInfoVideo>(p);
PS. And I'm not sure the StringBuilder is an appropriate marshaling for the CHAR Code[4];.
Note: The final working solution is after the edit!
I hope someone can help me with a problem I've been trying to solve for the last few days.
I am trying to pass a struct from a unmanaged C++ DLL to a C# script. This is what I have so far:
C++
EXPORT_API uchar *detectMarkers(...) {
struct markerStruct {
int id;
} MarkerInfo;
uchar *bytePtr = (uchar*) &MarkerInfo;
...
MarkerInfo.id = 3;
return bytePtr;
}
C#
[DllImport ("UnmanagedDll")]
public static extern byte[] detectMarkers(...);
...
[StructLayout(LayoutKind.Explicit, Size = 16, Pack = 1)]
public struct markerStruct
{
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(0)]
public int Id;
}
...
markerStruct ByteArrayToNewStuff(byte[] bytes){
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
markerStruct stuff = (markerStruct)Marshal.PtrToStructure(
handle.AddrOfPinnedObject(), typeof(markerStruct));
handle.Free();
return stuff;
}
...
print(ByteArrayToNewStuff (detectMarkers(d, W, H, d.Length) ).Id);
The problem is that this works, but the value printed is completely off (sometimes it prints around 400, sometimes max int value).
I'm guessing that there's something wrong with how I marshalled the struct in C#. Any ideas?
Edit:
This is the working solution using ref:
C++
struct markerStruct {
int id;
};
...
EXPORT_API void detectMarkers( ... , markerStruct *MarkerInfo) {
MarkerInfo->id = 3;
return;
}
C#
[DllImport ("ArucoUnity")]
public static extern void detectMarkers( ... ,
[MarshalAs(UnmanagedType.Struct)] ref MarkerStruct markerStruct);
...
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct MarkerStruct
{
public int Id;
}
...
detectMarkers (d, W, H, d.Length, ref markerInfo);
print( markerInfo.Id );
You're returning a pointer to a local variable which has already been destroyed before .NET can read it. That's a bad idea in pure C++ and a bad idea with p/invoke.
Instead, have C# pass a pointer to a structure (just use the ref keyword) and the C++ code just fill it in.
The MarkerInfo variable is local and goes out of scope when the function returns.
Don't return pointers to local variables, the objects they point to won't exist anymore.
Going to give this a whirl... thx for the post...
// new struct and generic return for items to
struct _itemStruct
{
unsigned int id; // 0 by default, so all lists should start at 1, 0 means unassigned
wchar_t *Name;
};
// for DLL lib precede void with the following...
// EXPORT_API
void getItems(std::vector<_itemStruct *> *items)
{
// set item list values here
//unsigned char *bytePtr = (unsigned char*)&items; // manual pointer return
return;
};
/* // In theory c# code will be...
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct _itemStruct
{
public unsigned int Id;
public string Name;
}
[DllImport ("ListOfItems")] // for ListOfItems.DLL
public static extern void getItems(
[MarshalAs(UnmanagedType.Struct)] ref List<_itemStruct> items);
// */