EDIT: I oversimplified my example... In the real code I was assigning values to strMyStringx without correctly using wcscpy_s, so instead of assigning the values I was just passing the pointer, which was out of scope by the time the values were being marshaled into managed code...
I'm trying to marshal a struct with three string properties from C to C#, but I can't get the definition of the struct right in C#. All of the properties print as garbage. Am I marshaling wrong or do my properties have the wrong type?
My custom structure:
typedef struct _MY_STRUCT_STRING {
LPWSTR strMyString1;
LPWSTR strMyString2;
LPWSTR strMyString3;
}MY_STRUCT_STRING, *PMY_STRUCT_STRING;
My C function returns an array of pointers to this struct:
bool bEnumerateString(OUT LONG &i_arr_size, OUT PMY_STRUCT_STRING* &pArrStringStruct)
{
// [...] function simplified to demonstrate building a pointer to an array of struct*
long i_arr_size = 3
PMY_STRUCT_STRING *ptr_arr_string = (PMY_STRUCT_STRING *)malloc(sizeof(PMY_STRUCT_STRING)* i_arr_size);
for (int i = 0; i < i_arr_size; i++) {
ptr_arr_string[i] = (PMY_STRUCT_STRING)malloc(sizeof(MY_STRUCT_STRING));
ptr_arr_string[i]->strMyString1 = L"String 1"; // This would work. In the real code I was assigning values from another array and mistakenly passed the pointer rather than doing wcscpy_s
ptr_arr_string[i]->strMyString2 = L"String 2";
ptr_arr_string[i]->strMyString3 = L"String 3";
}
pArrStringStruct = ptr_arr_string;
return true;
}
C#:
//Import the DLL with my function
[DllImport("My.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "bEnumerateString")]
internal static extern bool bEnumerateString(out long count, out IntPtr pArrStringStruct);
// Define the C# equivalent of the C struct
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct MY_STRUCT_STRING
{
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 8)]
public string strMyString1;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 8)]
public string strMyString1;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 8)]
public string strMyString1;
}
[...]
// Code to marshal (try... catch etc removed for succinctness)
IntPtr pArrStruct = IntPtr.Zero;
long lCount = 0;
bool bResult = false;
bResult = bEnumerateString(out lCount, out pArrStruct);
if (!bResult)
{
// Marshal to deref pointer
IntPtr[] pArrStructList = new IntPtr[lCount];
for (ulong i = 0; i < (ulong)lCount; i++)
{
pArrStructList[i] = Marshal.ReadIntPtr(pArrStruct, Marshal.SizeOf(typeof(IntPtr)) * (int)i);
}
// Marshal pointers to struct
var lstMyStringStrct = new List<MY_STRUCT_STRING>(pArrStructList.Length);
foreach (IntPtr ptr in pArrStructList)
{
lstMyStringStrct.Add((MY_STRUCT_STRING)Marshal.PtrToStructure(ptr, typeof(MY_STRUCT_STRING)));
}
// Enumerate struct
foreach (MY_STRUCT_STRING myStr in lstMyStringStrct)
{
// All of these outputs are garbage
Console.WriteLine("strMyString1: " + myStr.strMyString1);
Console.WriteLine("strMyString2: " + myStr.strMyString2);
Console.WriteLine("strMyString3: " + myStr.strMyString3);
}
}
I see one problem. You're C++ structure uses LPWSTR (pointers) whereas you're C# code is expecting fixed size char arrays.
Change your strings from:
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 8)]
public string strMyString1;
which would be used when the C++ structure was defined like:
char strMyString1[8];
to:
[MarshalAsAttribute(UnmanagedType.LPWStr)]
public string strMyString1;
Related
Following my other question here, I have been able to share string from C++ to C# thanks to this community.
However, I need to go one level above and I need to share chained structures from C++ to C# using memory mapping.
In an example scenario:
My C++ structures:
struct STRUCT_2
{
char Name[260];
};
struct STRUCT_1
{
void Init()
{
this->Count = 0;
this->Index = 0;
}
DWORD Count;
DWORD Index;
STRUCT_2 Table[256];
};
And me trying to "transfer" it to C#:
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public unsafe struct STRUCT_2
{
[FieldOffset(0)]
public fixed char Name[260];
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public unsafe struct STRUCT_1
{
void Init()
{
this.Count = 0;
this.Index = 0;
}
[FieldOffset(0)]
public uint Count;
[FieldOffset(0)]
public uint Index;
[FieldOffset(100)]
[MarshalAs(UnmanagedType.LPStruct, SizeConst = 256)]
public STRUCT_2 Table;
}
This works partially, basically I can see the values from Count and Index, however I cannot see values or even get them in STRUCT_2.
I have tried to change:
public STRUCT_2[] Table;
But then the compiler tells me:
"The specified Type must be a struct containing no references."
So my question would be, how can you read structures within structures using MemoryMappedFile in C# ?
Advice, thoughts or examples are very welcomed.
Update:
Complete testable code in C#:
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public unsafe struct STRUCT_2
{
[FieldOffset(0)]
public fixed byte Name[260];
// Fix thanks to Ben Voigt
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public unsafe struct STRUCT_1
{
void Init()
{
this.Count = 0;
this.Index = 0;
}
[FieldOffset(0)]
public uint Count;
[FieldOffset(0)]
public uint Index;
[FieldOffset(100)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public STRUCT_2[] Table;
}
static void Main(string[] args)
{
MemoryMappedFileSecurity CustomSecurity = new MemoryMappedFileSecurity();
CustomSecurity.AddAccessRule(new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>("everyone", MemoryMappedFileRights.FullControl, System.Security.AccessControl.AccessControlType.Allow));
var mappedFile = MemoryMappedFile.CreateOrOpen("Local\\STRUCT_MAPPING", 1024, MemoryMappedFileAccess.ReadWriteExecute, MemoryMappedFileOptions.None, CustomSecurity, System.IO.HandleInheritability.Inheritable);
using (var accessor = mappedFile.CreateViewAccessor())
{
STRUCT_1 data;
accessor.Read<STRUCT_1>(0, out data); // ERROR !
//The specified Type must be a struct containing no references.
Console.WriteLine(data.Count);
Console.WriteLine(data.Index);
}
}
Check this out.
Tested on Visual Studio 2017, Windows 7 x64.
Write and Read data works find.
It's also good study for me.
Take time on this.
Godspeed!
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct STRUCT_2
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)]
public byte[] Name;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
public struct STRUCT_1
{
void Init()
{
this.Count = 0;
this.Index = 0;
}
public uint Count;
public uint Index;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] //! array size of 10.
public STRUCT_2 [] Table;
}
static void test3()
{
MemoryMappedFileSecurity CustomSecurity = new MemoryMappedFileSecurity();
CustomSecurity.AddAccessRule(new System.Security.AccessControl.AccessRule<MemoryMappedFileRights>
( "everyone"
, MemoryMappedFileRights.FullControl
, System.Security.AccessControl.AccessControlType.Allow));
using (var mappedFile = MemoryMappedFile.CreateOrOpen("Local\\STRUCT_MAPPING"
, 10 * 1024
, MemoryMappedFileAccess.ReadWriteExecute
, MemoryMappedFileOptions.None
, CustomSecurity
, System.IO.HandleInheritability.Inheritable))
{
using (var accessor = mappedFile.CreateViewAccessor())
{
//! test setting.
int table_count = 5;
//! write data.
STRUCT_1 write_data;
write_data.Index = 1;
write_data.Count = 2;
write_data.Table = new STRUCT_2[10];
for (int i = 0; i < 10; i++)
{
write_data.Table[i].Name = new byte[260];
write_data.Table[i].Name[0] = (byte)i;
}
//! ----------------------------
// Get size of struct
int size = Marshal.SizeOf(typeof(STRUCT_1));
byte[] data = new byte[size];
// Initialize unmanaged memory.
IntPtr p = Marshal.AllocHGlobal(size);
// Copy struct to unmanaged memory.
Marshal.StructureToPtr(write_data, p, false);
// Copy from unmanaged memory to byte array.
Marshal.Copy(p, data, 0, size);
// Write to memory mapped file.
accessor.WriteArray<byte>(0, data, 0, data.Length);
// Free unmanaged memory.
Marshal.FreeHGlobal(p);
p = IntPtr.Zero;
//! ----------------------------------------------
STRUCT_1 read_data;
size = Marshal.SizeOf(typeof(STRUCT_1));
data = new byte[size];
// Initialize unmanaged memory.
p = Marshal.AllocHGlobal(size);
// Read from memory mapped file.
accessor.ReadArray<byte>(0, data, 0, data.Length);
// Copy from byte array to unmanaged memory.
Marshal.Copy(data, 0, p, size);
// Copy unmanaged memory to struct.
read_data = (STRUCT_1)Marshal.PtrToStructure(p, typeof(STRUCT_1));
// Free unmanaged memory.
Marshal.FreeHGlobal(p);
p = IntPtr.Zero;
Console.WriteLine(read_data.Index);
Console.WriteLine(read_data.Count);
}
}
}
I'd like to call an unmanaged method that allocates memory, creates an array of LPWSTRs, and returns it to managed code. I'd like to avoid in/out parameters and writing code to manage memory and variable scopes as much as possible so I decided I would rely on using CoTaskMemAlloc and let the marshaller automagically clean up after me.
Here's what I have (a modified version of a p/invoke tutorial method on MSDN):
extern "C" DLL1_API LPWSTR *TestArrayOfStrings(_In_ int count)
{
STRSAFE_LPWSTR temp = NULL;
wchar_t * ppStrArray[10] = { NULL };
const size_t alloc_size = sizeof(wchar_t *) * 10;
for (int i = 0; i < 10; i++)
{
temp = (STRSAFE_LPWSTR)CoTaskMemAlloc(alloc_size);
if (i % 2 == 0)
StringCchCopy(temp, alloc_size, L"0123456789");
else
StringCchCopy(temp, alloc_size, L"9876543210");
CoTaskMemFree(ppStrArray[i]);
ppStrArray[i] = temp;
}
count = 10;
return ppStrArray;
}
and on the managed side:
[DllImport("Dll1.Windows.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0, ArraySubType = UnmanagedType.LPWStr)]
public static extern string[] TestArrayOfStrings(out int count);
As you can see I've tried to use additional attributes but the marshaller just doesn't seem to like it--I keep getting "Cannot marshal 'return value': Invalid managed/unmanaged type combination." I am trying to maintain typing as an array of LPWSTRs and would like to avoid SAFEARRAY, for which marshalling is marked obsolete.
Slightly modified code, but the signature was what was important. This method assigns a string value (the third parameter) to the first element of the uninitialized out array passed in.
[DllImport("Dll1.Windows.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern void TestArrayOfStrings(
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 1)] [Out] out string[] test,
out int size, string someString);
extern "C" DLL1_API void TestArrayOfStrings(wchar_t ***strings, int *size, wchar_t *someString){
const size_t alloc_size = 64;
STRSAFE_LPWSTR temp = (STRSAFE_LPWSTR)CoTaskMemAlloc(alloc_size);
StringCchCopy(temp, alloc_size, someString);
*strings = (wchar_t **)CoTaskMemAlloc(sizeof(wchar_t));
*strings[0] = temp;
*size = 1;
}
i use p/invoke to return an array of "DN_OPstruct"s from my unmanaged code:
struct DN_OPstruct {
const char* TargetNode_Identifier;
const char* Name;
int TargetNode_NamespaceIndex;
...
};
EXTERN_C UA_EXPORT_WRAPPER_IMPORT int getOpToArr(const char* _rootGuid, DN_OPstruct ** array, int * arraySizeInElements){
std::list<UA_Ref_and_TargetNode> uaList;
uaList = getLisT(...)
*arraySizeInElements = uaList.size();
int bytesToAlloc = sizeof(DN_OPstruct) * (*arraySizeInElements);
DN_OPstruct * a = static_cast<DN_OPstruct*>(CoTaskMemAlloc(bytesToAlloc));
*array = a;
for (UA_Ref_and_TargetNode &i: uaList){
DN_OPstruct iterOp;
iterOp = getOp(...);
opList.push_back(iterOp);
}
return 1;
}
My managed Code looks like this:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DN_OPstruct
{
private IntPtr TargetNode_Identifier;
private IntPtr NamePtr;
public string Guid
{
get { return Marshal.PtrToStringAnsi(TargetNode_Identifier); }
set { TargetNode_Identifier = Marshal.StringToHGlobalAnsi(value); }
}
public string Name
{
get { return Marshal.PtrToStringAnsi(NamePtr); }
set { NamePtr = Marshal.StringToHGlobalAnsi(value); }
}
public int TargetNode_NamespaceIndex;
...
};
[DllImport(#"...", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "getOpToArr",
ExactSpelling = true, CharSet = CharSet.Ansi)]
public static extern int getOpToArr([MarshalAs(UnmanagedType.LPStr)]string myNodeGuid,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] out DN_OPstruct[] array, out int arraySizeInElements);
If i'm trying to call the method, i will jump in the unmanaged code and can debug it through sucessfully and i get an array with my DN_OPstructs back. However, if i read out its fields like .Name or .Guid, i get this error:
First-chance exception at 0x000007fefd921757 in (...).exe: 0xC0000005:
Access violation reading location 0xffffffffffffffff.
If there is a handler for this exception, the program may be safely
continued.
I tried to add "ArraySubType = UnmanagedType.LPStruct" to my method declaration; it did not help.
public static extern int getOpToArr(
[MarshalAs(UnmanagedType.LPStr)]
string myNodeGuid,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
out DN_OPstruct[] array,
out int arraySizeInElements
);
The problem is the second parameter. The unmanaged code cannot synthesise a managed .net array. You need to declare the p/invoke like this:
public static extern int getOpToArr(
string myNodeGuid,
out IntPtr arrayPtr,
out int arrayLen
);
Then you will need to use Marshal.PtrToStructure to marshal the elements of the array to a managed array.
IntPtr arrayPtr;
int arrayLen;
int retval = getOpToArr(nodeGuid, out arrayPtr, out arrayLen);
// check retval
IntPtr ptr = arrayPtr;
DN_OPstruct[] arr = new DN_OPstruct[arrayLen];
for (int i = 0; i < arrayLen; i++)
{
arr[i] = (DN_OPstruct)Marshal.PtrToStructure(ptr, typeof(DN_OPstruct));
ptr += Marshal.SizeOf(typeof(DN_OPstruct));
}
I'm also a little sceptical of the properties in your struct. Why do you have setters as well as getters? It doesn't look like the data flows in that direction. And the unmanaged code that you use shows allocation with CoTaskMemAlloc which doesn't match StringToHGlobalAnsi. So even though I doubt that you should be writing settings and so perhaps should remove the calls to StringToHGlobalAnsi, I also suspect there is confusion over the allocator that you are using.
Do note that the code in your question gives no evidence of how you allocated the array which is returned to the caller. So, for all we know, there could be a problem in that part of the code.
My Strcuture is
struct Address{
char Name[4096];
char Address[4096];
char Description[1024];
} ;
and Typedef for the Strcuture
typedef struct Address* (*__cdecl pgetAdd) ();
I pass pass the value to type
pgetAdd getStat = (pgetAdd)GetProcAddress(HMODULE (hGetProcIDDLL),"getStat");
char *Argv[] = { "Karthik", "23", "Chennai", "04-04-1984"};
getStat = (pgetAdd) startTest(Argv);
Address *mydata;
printf("\n mydata\n %s ", mydata>Name);
printf("\n mydata\n %s ", mydata>Address);
printf("\n mydata\n %s ", mydata> Description);
And this contains the c++ dll
And I need to call the dll from c#
and I tried like
My Strcuture is
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Address
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4096)]
public string Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4096)]
public string Address;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
public string Description;
}
I used Typedef like this
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)]
public delegate IntPtr getStatDelegate();
i Import the dll like this
[DllImport(#"E:\\EmployeeData.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "startTest", CharSet = CharSet.Ansi)]
public unsafe static extern IntPtr startTest ([In] IntPtr argv);
My code is
IntPtr args= StringArrayToIntPtr<byte>(new string[] { "Karthik", "23", "Chennai", "04-04-1984" });
IntPtr ptr = startTest(args);
getStatDelegate getStat = (getStatDelegate)Marshal.GetDelegateForFunctionPointer(ptr, typeof(getStatDelegate));
IntPtr structPtr = new IntPtr();
structPtr= getStat();
Address data = (Address)Marshal.PtrToStructure(structPtr, typeof(Address));
richTextBox1.Text = data.Name;
richTextBox2.Text = data.Address;
richTextBox3.Text = data.Description;
public static IntPtr StringArrayToIntPtr<GenChar>(string[]
InputStrArray) where GenChar : struct
{
int size = InputStrArray.Length;
IntPtr[] InPointers = new IntPtr[size];
int dim = IntPtr.Size * size;
IntPtr rRoot = Marshal.AllocCoTaskMem(dim);
for (int i = 0; i < size; i++)
{
if (typeof(GenChar) == typeof(char))
{
InPointers[i] = Marshal.StringToCoTaskMemUni(InputStrArray[i]);
}
else if (typeof(GenChar) == typeof(byte))
{
InPointers[i] = Marshal.StringToCoTaskMemAnsi(InputStrArray[i]);
}
}
Marshal.Copy(InPointers, 0, rRoot, size);
return rRoot;
}
My Ouput is
richTextBox1.Text = data.Name= Karthik;
richTextBox2.Text = data.Address= chennai;
richTextBox3.Text = data.Description=Welcome to our team something in dll;
i didn't get the output please correct the code as well help to find the solutions
Thanks you genius in advance
**
I would like to marshal a C struct with a variable-length array back to C# but so far I can't get anything better than a pointer-to-struct representation and a pointer to float.
Unmanaged representation:
typedef float smpl_t;
typedef struct {
uint_t length; /**< length of buffer */
smpl_t *data; /**< data vector of length ::fvec_t.length */
} fvec_t;
Managed representation:
[StructLayout(LayoutKind.Sequential)]
public unsafe struct fvec_t1
{
public uint length;
public float* data;
}
[DllImport("libaubio-4.dll", EntryPoint = "new_fvec", PreserveSig = true, CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe fvec_t1* new_fvec1(uint length);
What I would like is to have a .NET style array, where data would be float[] but if I do change the struct to the form below I do get Cannot take the address of, get the size of, or declare a pointer to a managed type in the external function above.
[StructLayout(LayoutKind.Sequential)]
public unsafe struct fvec_t1
{
public uint length;
public float[] data;
}
Apparently, it is not possible to a have a variable-length array marshalled back as-is, is this correct or is it there still a way to achieve this ?
short answer
you can't marshal variable length array as an array , because Without knowing the size, the interop marshalling service cannot marshal the array elements
but if you know the size it will be like below:
int arr[15]
you will be able to marshal it like this:
[MarshalAs(UnmanagedType.LPArray, SizeConst=15)] int[] arr
if you don't know the length of the array and this is what you want
you can convert it to intprt and deal with inptr but first you need to create 2 structs
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct fvec_t1
{
public uint whatever;
public int[] data;
}
the other one like below:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct fvec_t2{
public uint whatever;
}
create a function to initialize the array like below
private static int[] ReturnIntArray()
{
int [] myInt = new int[30];
for (int i = 0; i < myInt.length; i++)
{
myInt[i] = i + 1;
}
return myInt;
}
instantiate the first struct
fvec_t1 instance = new fvec_t1();
instance.whatever=10;
instance.data= ReturnIntArray();
instantiate the second struct
fvec_t2 instance1 = new fvec_t2();
instance1.whatever = instance.whatever
dynamically allocate space for fvec_t2 struct with extended space for data array
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(fvec_t2)) + Instance.data.Length);
Transfer the existing field values of fvec_t2 to memory space pointed to by ptr
Marshal.StructureToPtr(instance1, ptr, true);
Calculate the offset of data array field which should be at the end of an fvec_t2
struct
int offset = Marshal.SizeOf(typeof(fvec_t2));
get memory address of data array field based on the offset.
IntPtr address = new IntPtr(ptr.ToInt32() + offset);
copy data to ptr
Marshal.Copy(instance.data, 0, address, instance.data.Length);
do the call
bool success = dllfunction(ptr);
Marshal.FreeHGlobal(ptr);
ptr = IntPtr.Zero;
In the above case I'd definitely use the SizeParamIndex.
[StructLayout(LayoutKind.Sequential)]
public struct fvec_t1
{
uint length;
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] float[] data;
}
Good luck.