Understanding C++ DLL mapping to C# with unions - c#

I have the following scenario. I have a struct with a union in a DLL written in C++ and I need to map this structure to C# code. Please, have in mind that I cannot change the code in the DLL. Here is a sample code to illustrate what is happening:
typedef struct {
int type;
union {
int val;
double val2;
char *name;
};
} BIZARRE;
__declspec(dllexport) void changeBizarre(BIZARRE *bizarre, int type, const char *v)
{
bizarre->type = type;
if(type == 0) {
bizarre->val = 5;
} else if(type == 1) {
bizarre->val2 = 2.0;
} else if(type == 2) {
strncpy(bizarre->name, "test", strlen("test"));
} else if(type == 3 && strcmp(v, "test") == 0) {
bizarre->val = 10;
}
}
And in the C# code, I've done the following:
[StructLayout(LayoutKind.Explicit)]
public unsafe struct BIZARRE
{
[FieldOffset(0)]
public int type;
[FieldOffset(8)]
public int val;
[FieldOffset(8)]
public double val2;
[FieldOffset(8)]
public char *name;
}
[DllImport("proj.dll", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern void changeBizarre(ref BIZARRE bizarre, int type, char *name);
unsafe static void Main()
{
char[] v = "test".ToCharArray();
bizarre.type = 0;
bizarre.val = 0;
bizarre.val2 = 0.0;
fixed (char* address = v)
{
bizarre.name = address;
changeBizarre(ref bizarre, 3, &bizarre.name);
Console.WriteLine("{0}", bizarre.val);
}
Console.ReadKey();
}
Now, if you run this code by passing type = 2, and try to print the bizarre.name, it will appear junk characters and if you pass type = 3, apparently the DLL is not being able to get the content pointed by bizarre.name, I do believe these two behaviors have the same cause, but I don't know what is it.

The char array is not a real char array in C, in fact C# will use wchar_t type (16 bit char).
Change your C code using wchar_t stuff:
typedef struct {
int type;
union {
int val;
double val2;
wchar_t *name;
};
} BIZARRE;
__declspec(dllexport) void changeBizarre(BIZARRE *bizarre, int type, const wchar_t *v)
{
bizarre->type = type;
if (type == 0) {
bizarre->val = 5;
}
else if (type == 1) {
bizarre->val2 = 2.0;
}
else if (type == 2) {
wcsncpy(bizarre->name, L"test", wcslen("test"));
}
else if (type == 3 && wcscmp(v, L"test") == 0) {
bizarre->val = 10;
}
}
Or
Change your C# code using the byte type instead of char (as mentioned #Hans Passant) :
[StructLayout(LayoutKind.Explicit)]
public unsafe struct BIZARRE
{
[FieldOffset(0)]
public int type;
[FieldOffset(8)]
public int val;
[FieldOffset(8)]
public double val2;
[FieldOffset(8)]
public byte* name;
}
class Program
{
[DllImport("UnionMapping_dll.dll", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern void changeBizarre(ref BIZARRE bizarre, int type, byte* name);
static unsafe void Main(string[] args)
{
BIZARRE bizarre;
byte[] v = Encoding.ASCII.GetBytes("test");
bizarre.type = 0;
bizarre.val = 0;
bizarre.val2 = 0.0;
fixed (byte* address = v)
{
bizarre.name = address;
changeBizarre(ref bizarre, 3, bizarre.name);
Console.WriteLine("{0}", bizarre.val);
}
Console.ReadKey();
}
}

The C++ code has a union within a struct - your C# equivalent for interop purposes should reflect that:
using System.Runtime.InteropServices;
public struct BIZARRE
{
public int type;
[StructLayout(LayoutKind.Explicit)]
public struct AnonymousStruct
{
[FieldOffset(0)]
public int val;
[FieldOffset(0)]
public double val2;
[FieldOffset(0)]
public string name;
}
}

Related

Marshalling an empty void* parameter that can take multiple structure and retrieving it in C#

I want to call a C function with an empty void* parameter and retrieve it in C# to print the struct values.
In this example:
The C code will randomly assign one of the two structures to the void parameter.
The C code return must be an int.
The C code structures can be modified
How to retrieve and print the C struct content in C#?
C
#include <time.h>
#include <stdlib.h>
void PrintKdesc(interTICKeyDescription* kdescr)
{
kdescr->nItems++;
printf("nItems = %d\n", kdescr->nItems);
}
void PrintKevent(interTICEvent* kevent)
{
kevent->nbr++;
printf("nbr = %d\n", kevent->nbr);
}
EXPORT int VoidPtrTest(void* test)
{
interTICKeyDescription kdesc = { 1, 0x250021, 0, 42 };
interTICEvent kevent = { 2, 8 };
srand(time(NULL));
int r = rand() % 2;
if (r == 1)
{
test = &kdesc;
PrintKdesc(test);
}
else
{
test = &kevent;
PrintKevent(test);
}
return (0);
}
typedef struct {
int id;
unsigned int networkId;
unsigned int implVersion;
int nItems;
} interTICKeyDescription;
typedef struct {
int id;
int nbr;
} interTICEvent;
C#
[StructLayout(LayoutKind.Sequential)]
public struct InterTICKeyDescription
{
public int id;
public UInt32 networkId;
public UInt32 implVersion;
public int nItems;
}
[StructLayout(LayoutKind.Sequential)]
public struct InterTICEvent
{
public int id;
public int nbr;
}
[DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static int VoidPtrTest(IntPtr test);
My attempt
static void Main()
{
InterTICKeyDescription kdesc = new InterTICKeyDescription();
InterTICEvent kevent = new InterTICEvent();
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(kdesc));
VoidPtrTest(ptr);
int id = Marshal.ReadInt32(ptr, 0);
if (id == 1)
{
kdesc = (InterTICKeyDescription)Marshal.PtrToStructure(ptr, typeof(InterTICKeyDescription));
Console.WriteLine("Id 1: " + kdesc.nItems);
}
else
{
kevent = (InterTICEvent)Marshal.PtrToStructure(ptr, typeof(InterTICEvent));
Console.WriteLine("Id 2: " + kevent.nbr);
}
Marshal.FreeHGlobal(ptr);
}

Passing C# struct to C++ unmanaged DLL returning incorrect result

I have a simple C++ win32 DLL developed in visual studio 2017 and compiled in 64 bit environment having the following code:
typedef struct sum {
struct {
int num1;
int num2;
} nums;
} sum1;
extern "C" {
__declspec(dllexport) int initialize(sum1 *summing)
{
int res;
res = summing->nums.num1 + summing->nums.num2;
return res;
}
}
The above code contains a method which returns the sum of two integers by taking a typedef struct as an argument.
I have a C# client application which consumes this Win32 C++ DLL using PInvoke. Following is the code of my C# client application:
[StructLayout(LayoutKind.Sequential)]
public struct nums
{
public int a;
public int b;
}
[StructLayout(LayoutKind.Sequential)]
public struct mydef
{
public IntPtr sum;
}
public class LibWrap
{
[DllImport("C++.dll", EntryPoint = "initialize")]
public static extern int Initialize(ref mydef mydef);
}
class Program
{
static void Main(string[] args)
{
mydef mydef = new mydef();
nums nums;
nums.a = 6;
nums.b = 6;
IntPtr buffer1 = Marshal.AllocCoTaskMem(Marshal.SizeOf(nums));
Marshal.StructureToPtr(nums, buffer1, false);
mydef.sum = buffer1;
int res = LibWrap.Initialize(ref mydef);
Console.WriteLine(res);
}
}
With the above code, I am expecting '12' as output, but instead I am getting '-1504178328' as output.
I am a C# developer with no experience in C++ at all. Please help me to solve this problem.
Use a simpler P/Invoke wrapper:
public static class LibWrap
{
[DllImport("C++.dll", EntryPoint = "initialize")]
public static extern int Initialize(ref Nums nums);
[StructLayout(LayoutKind.Sequential)]
public struct Nums
{
public int a;
public int b;
}
}
and use it like this:
void CSharpExample()
{
LibWrap.Nums nums;
nums.a = 6;
nums.b = 7;
int res = LibWrap.Initialize(ref nums);
Console.WriteLine(res);
}
In your example, you don't need any memory allocation and marshaling, because:
LibWrap.Nums is a struct, thus local variable nums in CSharpExample() is allocated completely on stack.
passing managed struct LibWrap.Nums by ref to LibWrap.Initialize will pass the pointer to local variable nums on stack.
LibWrap.Initialize is called synchronously, so that the pointer you pass to it isn't used anywhere after LibWrap.Initialize function exits. This is important because the pointer becomes invalid as soon as CSharpExample() exits.
On the C# side, you are not handling the nested struct correctly. Try this instead:
[StructLayout(LayoutKind.Sequential)]
public struct mynums {
public int num1;
public int num2;
}
[StructLayout(LayoutKind.Sequential)]
public struct sum1 {
public mynums nums;
}
public class LibWrap {
[DllImport("C++.dll", EntryPoint = "initialize")]
public static extern int Initialize(ref sum1 summing);
}
class Program {
static void Main(string[] args) {
sum1 mysum;
mysum.nums.num1 = 6;
mysum.nums.num2 = 6;
int res = LibWrap.Initialize(ref mysum);
Console.WriteLine(res);
}
}
That being said, having a struct whose sole data member is another struct is redundant and unnecessary. You should remove the outer struct altogether, eg:
struct nums {
int num1;
int num2;
};
extern "C" {
__declspec(dllexport) int initialize(nums *summing) {
return summing->num1 + summing->num2;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct nums {
public int num1;
public int num2;
}
public class LibWrap {
[DllImport("C++.dll", EntryPoint = "initialize")]
public static extern int Initialize(ref nums summing);
}
class Program {
static void Main(string[] args) {
nums mynums;
mynums.num1 = 6;
mynums.num2 = 6;
int res = LibWrap.Initialize(ref mynums);
Console.WriteLine(res);
}
}

marshaling variable arguments - __arglist or alternative

Best way to describe the problem I'm trying to solve is to talk in code. I see a lot of __arglist questions on this forum, but not a lot of helpful answers. I know _arglist should be avoided so I'm open to alternative methods
In one C++ module I have something like the following
void SomeFunction(LPCWSTR pszFormat, va_args args)
{
// this function is not exported...
// it is designed to take a format specifier and a list of variable
// arguments and "printf" it into a buffer. This code
// allocates buffer and uses _vsnwprintf_s to format the
// string.
// I really do not have much flexibility to rewrite this function
// so please steer away from scrutinizing this. it is what is
// and I need to call it from C#.
::_vsnwprintf_s(somebuff, buffsize, _TRUNCATE, pszFormat, args)
}
__declspec(dllexport) void __cdecl ExportedSomeFunction(LPCWSTR pszFormat, ...)
{
// the purpose of this method is to export SomeFunction to C# code.
// it handles any marshaling. I can change this however it makes sense
va_list args ;
va_start(args, pszFormat) ;
SomeFunction(pszFormat, args) ;
va_end(args) ;
}
in another C# module I have code that handles all marshalling to the C++ DLL.
The intent is to hide all complexity of Native APIs and marshalling from user code.
The ultimate goal being a C++ developer or C# developer make the SAME API calls, but the code is written once and exported to both
[DllImport("mymodule.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern void ExportedSomeFunction(
[MarshalAs(UnmanagedType.LPWStr)] out string strPath,
/* ? what goes here ? */);
void SomeFunction(string strFormat, /*? what goes here ?*/ )
{
// handles marshalling incoming data to what ever is needed by exported C function
ExportedSomeFunction(strFormat, /*? something ?*/ ) ;
}
Then the user code in some other module should look like this...
SomeFunction("my format: %ld, %s", 5, "Some Useless string") ;
That would be ideal, but am prepared to live with
SomeFunction("my format: %ld, %s", __arglist(5, "Some Useless string")) ;
I don't care how the data gets marshaled. If I use __arglist or some array, I don't care as long as I end up with a va_args
__arglist looks like the solution, and I can successfully call
ExportedSomeFunction(strFormat, __arglist(5, "Some Useless string")) ;
But I cannot figure out how to call the C# SomeFunction with variable arguments and pass a __arglist to the exported function.
SomeFunction("my format: %ld, %s", __arglist(5, "Some Useless string")) ;
I cannot get this to work....
[DllImport("mymodule.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern void ExportedSomeFunction(
[MarshalAs(UnmanagedType.LPWStr)] out string strPath,
__arglist);
void SomeFunction(string strFormat, __arglist )
{
ExportedSomeFunction(strFormat, __arglist) ; // error cannot convert from RuntimeArgumentHandle to __arglist
}
This compiles, but doesn't produce the desired results. The argument list received in C++ is wrong.
private static extern void ExportedSomeFunction(
[MarshalAs(UnmanagedType.LPWStr)] out string strPath,
RuntimeArgumentHandle args);
Here is my suggestion how to tackle this. Take a look at varargs.h which is part of VisualStudio. This gives you some insight what the va_list means. You can see: typedef char * va_list;. It's just a pointer.
Not only is __arglist undocumented, I don't think it works correctly on 64-bit processes.
You need to build the va_list dynamically on C# side. I believe that this is better solution than undocumented __arglist and it seems to be working nicely. For C#, you want to use params[], and on C++ receiving side, va_list. Every variadic function should have function starting with v..., such as vsprintf, that receives va_list, instead of fiddling with arguments in the stack.
Copy/paste this beauty to your solution:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
// Author: Chris Eelmaa
namespace ConsoleApplication1
{
#region VariableCombiner
class CombinedVariables : IDisposable
{
readonly IntPtr _ptr;
readonly IList<IDisposable> _disposables;
bool _disposed;
public CombinedVariables(VariableArgument[] args)
{
_disposables = new List<IDisposable>();
_ptr = Marshal.AllocHGlobal(args.Sum(arg => arg.GetSize()));
var curPtr = _ptr;
foreach (var arg in args)
{
_disposables.Add(arg.Write(curPtr));
curPtr += arg.GetSize();
}
}
public IntPtr GetPtr()
{
if(_disposed)
throw new InvalidOperationException("Disposed already.");
return _ptr;
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
foreach (var disposable in _disposables)
disposable.Dispose();
Marshal.FreeHGlobal(_ptr);
}
}
}
#endregion
#region VariableArgument
abstract class VariableArgument
{
#region SentinelDispose
protected static readonly IDisposable SentinelDisposable =
new SentinelDispose();
class SentinelDispose : IDisposable
{
public void Dispose()
{
}
}
#endregion
public abstract IDisposable Write(IntPtr buffer);
public virtual int GetSize()
{
return IntPtr.Size;
}
public static implicit operator VariableArgument(int input)
{
return new VariableIntegerArgument(input);
}
public static implicit operator VariableArgument(string input)
{
return new VariableStringArgument(input);
}
public static implicit operator VariableArgument(double input)
{
return new VariableDoubleArgument(input);
}
}
#endregion
#region VariableIntegerArgument
sealed class VariableIntegerArgument : VariableArgument
{
readonly int _value;
public VariableIntegerArgument(int value)
{
_value = value;
}
public override IDisposable Write(IntPtr buffer)
{
Marshal.Copy(new[] { _value }, 0, buffer, 1);
return SentinelDisposable;
}
}
#endregion
#region VariableDoubleArgument
sealed class VariableDoubleArgument : VariableArgument
{
readonly double _value;
public VariableDoubleArgument(double value)
{
_value = value;
}
public override int GetSize()
{
return 8;
}
public override IDisposable Write(IntPtr buffer)
{
Marshal.Copy(new[] { _value }, 0, buffer, 1);
return SentinelDisposable;
}
}
#endregion
#region VariableStringArgument
sealed class VariableStringArgument : VariableArgument
{
readonly string _value;
public VariableStringArgument(string value)
{
_value = value;
}
public override IDisposable Write(IntPtr buffer)
{
var ptr = Marshal.StringToHGlobalAnsi(_value);
Marshal.Copy(new[] {ptr}, 0, buffer, 1);
return new StringArgumentDisposable(ptr);
}
#region StringArgumentDisposable
class StringArgumentDisposable : IDisposable
{
IntPtr _ptr;
public StringArgumentDisposable(IntPtr ptr)
{
_ptr = ptr;
}
public void Dispose()
{
if (_ptr != IntPtr.Zero)
{
Marshal.FreeHGlobal(_ptr);
_ptr = IntPtr.Zero;
}
}
}
#endregion
}
#endregion
}
and the example of usage:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(
AmazingSPrintf("I am %s, %d years old, %f meters tall!",
"Chris",
24,
1.94));
}
static string AmazingSPrintf(string format, params VariableArgument[] args)
{
if (!args.Any())
return format;
using (var combinedVariables = new CombinedVariables(args))
{
var bufferCapacity = _vscprintf(format, combinedVariables.GetPtr());
var stringBuilder = new StringBuilder(bufferCapacity + 1);
vsprintf(stringBuilder, format, combinedVariables.GetPtr());
return stringBuilder.ToString();
}
}
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int vsprintf(
StringBuilder buffer,
string format,
IntPtr ptr);
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int _vscprintf(
string format,
IntPtr ptr);
}
}
The CombinedVariables class is used to build va_list, and then you can pass it to your C++ method void SomeFunction(LPCWSTR pszFormat, va_list args).
You need to take care of the VariableStringArgument as it works with ANSI currently. You're probably looking for Marshal.StringToHGlobalUni.
Note that there is a small difference between va_list and .... printf uses ..., while vprintf uses va_list. A va_list is often a pointer to the first element of the .... __arglist is for ....
For va_list you can use the code of #Erti, or my code:
public class VaList : IDisposable
{
protected readonly List<GCHandle> handles = new List<GCHandle>();
public VaList(bool unicode, params object[] args)
{
if (args == null)
{
throw new ArgumentNullException("args");
}
// The first handle is for the bytes array
handles.Add(default(GCHandle));
int total = 0;
var bytes = new PrimitiveToBytes[args.Length];
for (int i = 0; i < args.Length; i++)
{
int size = Convert(unicode, args[i], ref bytes[i]);
bytes[i].Size = size;
total += size;
}
// Instead of a byte[] we use a IntPtr[], so copying elements
// inside is faster (perhaps :-) )
var buffer = new IntPtr[total / IntPtr.Size];
handles[0] = GCHandle.Alloc(buffer, GCHandleType.Pinned);
for (int i = 0, j = 0; i < args.Length; i++)
{
buffer[j++] = bytes[i].IntPtr;
// long or double with IntPtr == 4
if (bytes[i].Size > IntPtr.Size)
{
buffer[j++] = (IntPtr)bytes[i].Int32High;
}
}
}
// Overload this to handle other types
protected virtual int Convert(bool unicode, object arg, ref PrimitiveToBytes primitiveToBytes)
{
int size;
if (arg == null)
{
primitiveToBytes.IntPtr = IntPtr.Zero;
size = IntPtr.Size;
}
else
{
Type type = arg.GetType();
TypeCode typeHandle = Type.GetTypeCode(type);
switch (typeHandle)
{
case TypeCode.Boolean:
// Boolean converted to Int32
primitiveToBytes.Int32 = (bool)arg ? 1 : 0;
size = IntPtr.Size;
break;
case TypeCode.SByte:
primitiveToBytes.SByte = (sbyte)arg;
size = IntPtr.Size;
break;
case TypeCode.Byte:
primitiveToBytes.Byte = (byte)arg;
size = IntPtr.Size;
break;
case TypeCode.Int16:
primitiveToBytes.Int16 = (short)arg;
size = IntPtr.Size;
break;
case TypeCode.UInt16:
primitiveToBytes.UInt16 = (ushort)arg;
size = IntPtr.Size;
break;
case TypeCode.Int32:
primitiveToBytes.Int32 = (int)arg;
size = IntPtr.Size;
break;
case TypeCode.UInt32:
primitiveToBytes.UInt32 = (uint)arg;
size = IntPtr.Size;
break;
case TypeCode.Int64:
primitiveToBytes.Int64 = (long)arg;
size = sizeof(long);
break;
case TypeCode.UInt64:
primitiveToBytes.UInt64 = (ulong)arg;
size = sizeof(ulong);
break;
case TypeCode.Single:
// Single converted to Double
primitiveToBytes.Double = (double)(float)arg;
size = sizeof(double);
break;
case TypeCode.Double:
primitiveToBytes.Double = (double)arg;
size = sizeof(double);
break;
case TypeCode.Char:
if (unicode)
{
primitiveToBytes.UInt16 = (char)arg;
}
else
{
byte[] bytes = Encoding.Default.GetBytes(new[] { (char)arg });
if (bytes.Length > 0)
{
primitiveToBytes.B0 = bytes[0];
if (bytes.Length > 1)
{
primitiveToBytes.B1 = bytes[1];
if (bytes.Length > 2)
{
primitiveToBytes.B2 = bytes[2];
if (bytes.Length > 3)
{
primitiveToBytes.B3 = bytes[3];
}
}
}
}
}
size = IntPtr.Size;
break;
case TypeCode.String:
{
string str = (string)arg;
GCHandle handle;
if (unicode)
{
handle = GCHandle.Alloc(str, GCHandleType.Pinned);
}
else
{
byte[] bytes = new byte[Encoding.Default.GetByteCount(str) + 1];
Encoding.Default.GetBytes(str, 0, str.Length, bytes, 0);
handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
}
handles.Add(handle);
primitiveToBytes.IntPtr = handle.AddrOfPinnedObject();
size = IntPtr.Size;
}
break;
case TypeCode.Object:
if (type == typeof(IntPtr))
{
primitiveToBytes.IntPtr = (IntPtr)arg;
size = IntPtr.Size;
}
else if (type == typeof(UIntPtr))
{
primitiveToBytes.UIntPtr = (UIntPtr)arg;
size = UIntPtr.Size;
}
else if (!type.IsValueType)
{
GCHandle handle = GCHandle.Alloc(arg, GCHandleType.Pinned);
primitiveToBytes.IntPtr = handle.AddrOfPinnedObject();
size = IntPtr.Size;
}
else
{
throw new NotSupportedException();
}
break;
default:
throw new NotSupportedException();
}
}
return size;
}
~VaList()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
for (int i = 0; i < handles.Count; i++)
{
if (handles[i].IsAllocated)
{
handles[i].Free();
}
}
handles.Clear();
}
public IntPtr AddrOfPinnedObject()
{
if (handles.Count == 0)
{
throw new ObjectDisposedException(GetType().Name);
}
return handles[0].AddrOfPinnedObject();
}
[StructLayout(LayoutKind.Explicit)]
protected struct PrimitiveToBytes
{
[FieldOffset(0)]
public byte B0;
[FieldOffset(1)]
public byte B1;
[FieldOffset(2)]
public byte B2;
[FieldOffset(3)]
public byte B3;
[FieldOffset(4)]
public byte B4;
[FieldOffset(5)]
public byte B5;
[FieldOffset(6)]
public byte B6;
[FieldOffset(7)]
public byte B7;
[FieldOffset(4)]
public int Int32High;
[FieldOffset(0)]
public byte Byte;
[FieldOffset(0)]
public sbyte SByte;
[FieldOffset(0)]
public short Int16;
[FieldOffset(0)]
public ushort UInt16;
[FieldOffset(0)]
public int Int32;
[FieldOffset(0)]
public uint UInt32;
[FieldOffset(0)]
public long Int64;
[FieldOffset(0)]
public ulong UInt64;
[FieldOffset(0)]
public float Single;
[FieldOffset(0)]
public double Double;
[FieldOffset(0)]
public IntPtr IntPtr;
[FieldOffset(0)]
public UIntPtr UIntPtr;
[FieldOffset(8)]
public int Size;
}
}
Example of use:
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int vprintf(string format, IntPtr ptr);
and
using (var list = new VaList(false, // Ansi encoding
true, // bool test
short.MinValue + 1, int.MinValue + 2, long.MinValue + 3, // signed
ushort.MaxValue - 4, uint.MaxValue - 5, ulong.MaxValue - 6, // unsigned
float.MaxValue, double.MaxValue, // float/double
'A', "Foo", Encoding.Default.GetBytes("Bar\0"), null, // char/string
IntPtr.Size == sizeof(int) ? (IntPtr)(int.MinValue + 7) : (IntPtr)(long.MinValue + 7), // signed ptr
UIntPtr.Size == sizeof(uint) ? (UIntPtr)(uint.MaxValue - 8) : (UIntPtr)(ulong.MaxValue - 8))) // unsigned ptr
{
vprintf("%d\n %hd\n %d\n %lld\n %hu\n %u\n %llu\n %f\n %f\n %c\n %s\n %s\n %s\n %p\n %p\n", list.AddrOfPinnedObject());
}
Note that this code is compatible only with Visual C++ for Intel x86/x64. ARM uses another format, and GCC still other formats.

Returning an array of structs with struct containing char[] with PInvoke

I have a array of structs returned from PInvoke, and it returns the array fine if the struct just contains int or float, but when i try to return an array of char it starts to get messy, i have tried with returning an IntPtr, but that has not been successfull. Any ideas how i can get this working?
C code
struct return_part {
int partid;
int numcomp;
int parttype;
char partname[100];
};
extern int return_parts(return_part ** array, int * arraySizeInElements) {
int partcount = 0;
struct list_part *currentnode;
currentnode = head;
struct section_list *section;
struct return_part *temppart;
while (currentnode != NULL) {
partcount++;
currentnode = currentnode->next;
}
currentnode = head;
*arraySizeInElements = partcount;
int bytesToAlloc = sizeof(return_part) * (*arraySizeInElements);
return_part * a = static_cast<return_part *>(CoTaskMemAlloc(bytesToAlloc));
*array = a;
int q = 0;
while (currentnode != NULL) {
struct return_part tmp;
tmp.partid = currentnode->partid;
tmp.numcomp = currentnode->numcomp;
strcpy(tmp.partname, currentnode->partname);
tmp.parttype = currentnode->parttype;
a[q] = tmp;
q++;
currentnode = currentnode->next;
}
return 0;
}
C# Code
[StructLayout(LayoutKind.Sequential)]
public struct return_part {
public int partid;
public int numcomp;
public int parttype;
public char partname;
};
internal static class UnsafeNativeMethods
{
[DllImport(_dllLocation, CallingConvention = CallingConvention.Cdecl)]
public static extern int return_parts([MarshalAs(UnmanagedType.LPArray,
SizeParamIndex = 1)] out return_part[] array, out int arraySizeInElements);
}
public static ReturnPoint[] getpoints(int partid) {
return_parts[] parts;
int size;
int result = UnsafeNativeMethods.return_parts(out parts, out size)
}
An array of characters, to unmanaged code, is just a string, so you have two options here. Which one you use depends on how you actually need to use the character data when it's back in C#-land:
Marshal it as an array. See this article for how arrays embedded into structures get marshalled (EDIT: this needs to be ByValArray, not LPArray as I originally indicated; Thanks #Hans):
[StructLayout(LayoutKind.Sequential)]
public struct return_part {
public int partid;
public int numcomp;
public int parttype;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=100)]
public char[] partname;
};
Marshal it as a string. See this article for how to marshal strings contained within structures.
[StructLayout(LayoutKind.Sequential)]
public struct return_part {
public int partid;
public int numcomp;
public int parttype;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=100)]
public string partname;
};
On the C# side, your structure has a single character as partname. Try using either:
char[] partname; //-- A character array
byte[] partname; //-- A byte array
string partname; //-- A string
I'd tend to prefer the string if that works. The byte might work because byte in C# lines up more logically with char in C. The character array might also work because the character in C# logically represents an actual single character (intended use), something C doesn't really have (short of unsigned integer types).

How to marshal an array of structure In C#?

I have to call a C++ dll in C#. And the header of the dll is as following(simplified):
//Header of C++
struct vector
{
float x;
float y;
vector()
{}
vector(float x0, float y0)
{
x = x0;
y = y0;
}
};
struct unmanaged_struct
{
int int_var;
float float_var;
char* chars_var;
vector vector_var;
unmanaged_struct(int i, float f, char* ch, float vec_x, float vec_y)
{
int_var = i;
float_var = f;
chars_var = ch;
vector_var = vector(vec_x, vec_y);
}
};
// this function is used to output all the variable values of the struct instance
extern "C" __declspec( dllexport ) void unmanagedstruct_summary(unmanaged_struct* us_list, int length);
And I defined following class in C#
//CSharp
[StructLayout(LayoutKind.Sequential)]
public class Vector
{
public float x;
public float y;
public Vector(float f1, float f2)
{
x = f1;
y = f2;
}
}
[StructLayout(LayoutKind.Sequential)]
public class UnmanagedStruct
{
public int int_var;
public float float_var;
public string char_var;
public Vector vector_var;
public UnmanagedStruct(int i, float f, string s, Vector vec)
{
this.int_var = i;
this.float_var = f;
this.char_var = s;
this.vector_var = vec;
}
}
class UnmanagedDllCallTest
{
[DllImport("unmanageddll.dll", EntryPoint = "unmanagedstruct_summary")]
public static extern void unmanagedstruct_summary([Out]UnmanagedStruct[] usList, int length);
static void Main(string[] args)
{
UnmanagedStruct[] usList = new UnmanagedStruct[1];
usList[0] = new UnmanagedStruct(1, 1.0f, "aa", new Vector(10, 1));
usList[1] = new UnmanagedStruct(2, 2.0f, "ba", new Vector(20, 2));
UnmanagedDllCallTest.unmanagedstruct_summary(usList, 2);
}
And the output is as following:
unmanaged_struct summary:
0
1.12104e-044
Unhandled Exception:
System.AccessViolationException:
Attempted to read or write protected
memory. This is often an indication
that other memory is corrupt. at
callunmanageddll.UnmanagedDllCallTest.unmanagedstruct_summary(UnmanagedStr
uct[] usList, Int32 length) at
callunmanageddll.Program.Main(String[]
args) in c:\users\dynaturtle\docume
nts\visual studio
2010\Projects\callunmanageddll\callunmanageddll\Program.cs:lin
e 68
The C++ dll is OK as I have written test in C++ and the function works well. I have read this thread but it seems the solution didn't work in my case. Any suggestions? Thanks in advance!
Use Marshal.PtrToStructure. There is a sample here.
So you would have to change the signature of the method from out structure array to out IntPtr. However, you need to know the size of the buffer being passed out.
public struct Vector
{
public float x;
public float y;
}
public struct UnmanagedStruct
{
public int int_var;
public float float_var;
public string char_var;
public Vector vector_var;
}
class UnmanagedDllCallTest
{
[DllImport("unmanageddll.dll", EntryPoint = "unmanagedstruct_summary")]
public static extern void unmanagedstruct_summary([Out] IntPtr ptr, int length);
static void Main(string[] args)
{
for(int i=0; i<length; i++)
{
UnmanagedStruc st;
Marshal.PtrToStructure(ptr, st);
// increment ptr and move forward
}
}
First: Vector and UnmanagedStruct should be structs, not classes.
JIC I'd share my approach. Perhaps this is not an expected answer by at one time I spend a time to resolve my issue.
I have the following structure to expose some data from DLL.
//C++ code
struct State
{
const wchar_t * name;
unsigned int state;
};
APIENTRY bool get_states(H_PRCSR, MacroState *, const int sz); //pay attention, function accepts already allocated array and size for it
To accept this data From C++ I can do this way
std::vector<State> states(desired_size);
get_states(hparser, &states[0], states.size());
To do the same on C# I used the following way
//C#
[StructLayout(LayoutKind.Sequential)]
public struct Status
{
public IntPtr name;
public uint state;
public string getName()
{
if (name == IntPtr.Zero) return "<no-value>";
return Marshal.PtrToStringUni(name);
}
}
//And import function...
[DllImport(dll, CallingConvention = CallingConvention.Winapi)]
private static extern bool get_states(IntPtr p, [Out]MacroFlag[] flags, int flags_size);
//And simple decoder
public static Status[] getAll(IntPtr p, int size)
{
var results = new Status[size];
get_states(p, results, size);
return results;
}
As, I saw the are different approaches to do this. This is one of them. And it works for me. Perhaps, this post will not resolve the issue but will be a good point to start

Categories

Resources