C++ Struct in C# - c#

I'm using a DLL written in C++ in my C# project by using DllImport and one of the functions I'm using looks like this:
[DllImport("dds.dll", CharSet = CharSet.Auto)]
private static extern int Par(
ddTableResults2 tableResult,
ref parResults ParResult,
int vul
);
The parResults struct is defined in C++ like this:
struct parResults {
/* index = 0 is NS view and index = 1
is EW view. By 'view' is here meant
which side that starts the bidding. */
char parScore[2][16];
char parContractsString[2][128];
};
The start of the C++ function
int STDCALL Par(struct ddTableResults * tablep, struct parResults *presp,
int vulnerable)
How should I define the above struct in C# to able to send that struct as en reference into the DLL function?
This is what I have tried but don't work at all and I just get a Access Violation Error
[StructLayout(LayoutKind.Sequential)]
public struct parResults
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[,] parScore;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public char[,] parContractsString;
public parResults(int x)
{
parScore = new char[2,16];
parContractsString = new char[2,128];
}
}

This is quite a tricky struct to marshal in C#. There are various ways to attempt it, but I think that it will be cleanest to represent the character arrays as byte arrays and marshal to and from strings by hand. Here is a demonstration of what I mean:
C++
#include <cstring>
struct parResults {
char parScore[2][16];
char parContractsString[2][128];
};
extern "C"
{
__declspec(dllexport) void foo(struct parResults *res)
{
strcpy(res->parScore[0], res->parContractsString[0]);
strcpy(res->parScore[1], res->parContractsString[1]);
}
}
C#
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[StructLayout(LayoutKind.Sequential)]
class parResults
{
private const int parScoreCount = 2;
private const int parScoreLen = 16;
private const int parContractsStringCount = 2;
private const int parContractsStringLen = 128;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = parScoreCount * parScoreLen)]
private byte[] parScoreBuff;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = parContractsStringCount * parContractsStringLen)]
private byte[] parContractsStringBuff;
public string getParScore(int index)
{
string str = Encoding.Default.GetString(parScoreBuff,
index * parScoreLen, parScoreLen);
int len = str.IndexOf('\0');
if (len != -1)
str = str.Substring(0, len);
return str;
}
public void setParScore(int index, string value)
{
byte[] bytes = Encoding.Default.GetBytes(value);
int len = Math.Min(bytes.Length, parScoreLen);
Array.Copy(bytes, 0, parScoreBuff, index * parScoreLen, len);
Array.Clear(parScoreBuff, index * parScoreLen + len,
parScoreLen - len);
}
public string parContractsString(int index)
{
string str = Encoding.Default.GetString(parContractsStringBuff,
index * parContractsStringLen, parContractsStringLen);
int len = str.IndexOf('\0');
if (len != -1)
str = str.Substring(0, len);
return str;
}
public void setParContractsString(int index, string value)
{
byte[] bytes = Encoding.Default.GetBytes(value);
int len = Math.Min(bytes.Length, parContractsStringLen);
Array.Copy(bytes, 0, parContractsStringBuff,
index * parContractsStringLen, len);
Array.Clear(parContractsStringBuff,
index * parContractsStringLen + len,
parContractsStringLen - len);
}
public parResults()
{
parScoreBuff = new byte[parScoreCount * parScoreLen];
parContractsStringBuff =
new byte[parContractsStringCount * parContractsStringLen];
}
};
[DllImport(#"...", CallingConvention = CallingConvention.Cdecl)]
static extern void foo([In,Out] parResults res);
static void Main(string[] args)
{
parResults res = new parResults();
res.setParContractsString(0, "foo");
res.setParContractsString(1, "bar");
foo(res);
Console.WriteLine(res.getParScore(0));
Console.WriteLine(res.getParScore(1));
Console.ReadLine();
}
}
}
Here I've used a class to represent the struct. Since a class in C# is a reference, we don't declare the parameters of that type with ref. I've also used __cdecl for convenience to avoid having to work out what the decorated name of the function would be. But your library used __stdcall and so you need to stick to that.
The class demonstrates sending data in both directions. You could probably simplify the code if the data flow was more restricted.

Related

Sharing chained structure with memory mapped file from C++ to C#

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

How to DllImport char*

I have a c++ function like this:
myExport.h
extern "C" { __declspec(dllexport) const int Run(char *input, char *output, int *length); }
myExport.cpp
const int Run(char *input, char *output, int *length) {
std::ostringstream value;
value
<< "FOO" << "|"
<< "BAR" << "|";
auto str = value.str();
auto i = stdext::checked_array_iterator<char*>(output, str.length());
std::copy(str.begin(), str.end(), i);
output[str.length()] = '\0';
return 1;
}
And in C# I have:
myImport.cs
[DllImport("MyExport.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
private static extern int Run(
[MarshalAs(UnmanagedType.LPStr)]string input,
StringBuilder output,
ref int length);
public static string Execute(string input)
{
var length = 1024;
var output = new StringBuilder(1024);
var result = Run(input, output, ref length);
return output.ToString();
}
However, the output buffer is always empty. What am I doing wrong?
Since the type is char * for the second parameter, and the DLL function will fill in the buffer that's passed, the C# declaration should be as follows:
[MarshalAs(UnmanagedType.LPStr)]System.Text.StringBuilder output

Passing an array of strings from C++ to C#

I've already written this piece of code which works fine:
C++ code
extern "C"
{
const MYLIBRARY_EXPORT char* giefStrPlx(char* addon)
{
return addon;
}
}
C# code
[DllImport("ClassLibrary1")]
private static extern IntPtr giefStrPlx(string x);
void Start()
{
IntPtr stringPtr = giefStrPlx("Huntsman");
string huntsman = Marshal.PtrToStringAnsi(echoedStringPtr);
}
After this huntsman contains "Huntsman".
My problem is the step of doing something similar for an array of strings. I wrote the following function
extern "C"
{
const MYLIBRARY_EXPORT bool fillStrArray(char** lizt, int* length)
{
char* one = "one";
char* two = "two";
char* three = "three";
lizt[0] = one;
lizt[1] = two;
lizt[2] = three;
*length = 3;
}
}
I then tried to write the following piece of code in C#
[DllImport("ClassLibrary1")]
private static extern bool fillStrArray(ref IntPtr array, ref int length);
void Start()
{
IntPtr charArray = IntPtr.Zero;
int charArraySize = 0;
fillStrArray(ref charArray, ref charArraySize);
IntPtr[] results = new IntPtr[charArraySize];
Marshal.Copy(charArray, results, 0, charArraySize);
foreach (IntPtr ptr in results)
{
string str = Marshal.PtrToStringAnsi(ptr);
}
}
Which does not work. So now I'm a bit lost on how to accomplish this.
Here are the two helper functions I have from CLR to std::string and from std::string to string CLR
std::string CLROperations::ClrStringToStdString(String^ str)
{
if (String::IsNullOrEmpty(str))
return "";
std::string outStr;
IntPtr ansiStr = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(str);
outStr = (const char*)ansiStr.ToPointer();
System::Runtime::InteropServices::Marshal::FreeHGlobal(ansiStr);
return outStr;
}
String ^ CLROperations::StdStringToClr(std::string str)
{
return gcnew String(str.c_str());
}
for using a List of strings you will need to use List<String^>^ mind the capital String. for a list of std::string use std::vector<std::string>

Unmanaged Exports, passing an array of strings from C++ to C#

I'm using Robert Giesecke's Unmanaged Exports package to be able to call from C++ to C#.
This has to use the C interface from within C++. I have managed to get most things working, by scouring the web and picking up bits here and there....
extern "C"
{
// Simple
__declspec(dllimport) int IntTest(int input);
__declspec(dllimport) double DoubleTest(double input);
// Array of simple types in
__declspec(dllimport) int passArray(int t[], int i, int xx);
// String in and out
__declspec(dllimport) int PassStringIn(wchar_t* str);
__declspec(dllimport) int PassStringOut(wchar_t** str);
__declspec(dllimport) wchar_t* PassStringInOut(wchar_t* str);
// Array of strings types in
//__declspec(dllimport) int passArrayStrings(char** t, int i);
}
....
// Int in and out
int aa = IntTest(4);
// Double in and out
double bb = DoubleTest(4.3);
// Pass array in
int arr[4] = { 1,2,3,4 };
int cc = passArray(arr, 4, 0);
// String in
wchar_t* a_str = L"input string from C++";
int dd = PassStringIn(a_str);
// String out
wchar_t* b_str = L"not used";
int ee = PassStringOut(&b_str);
// String in & out
wchar_t* d_str = L"bob";
wchar_t* result = PassStringInOut(d_str);
corresponding C#
[DllExport( CallingConvention = CallingConvention.Cdecl)]
static int IntTest(int input)
{
return input + 1;
}
[DllExport(CallingConvention = CallingConvention.Cdecl)]
static double DoubleTest(double input)
{
return input + 1;
}
[DllExport(CallingConvention = CallingConvention.Cdecl)]
public static int passArray([In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] tab, int i, int x)
{
return tab[x];
}
[DllExport(CallingConvention = CallingConvention.Cdecl)]
public static int PassStringIn( [MarshalAs(UnmanagedType.LPWStr)] string inputString)
{
Console.WriteLine("Hi, the string passed in was :" + inputString);
return 1;
}
[DllExport(CallingConvention = CallingConvention.Cdecl)]
static int PassStringOut([MarshalAs(UnmanagedType.BStr)] out string outputString)
{
Console.WriteLine("Hi, I will return the time from c#");
outputString = DateTime.Now.ToLongTimeString();
return 0; // indicates success
}
[DllExport(CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPTStr)]
public static string PassStringInOut([MarshalAs(UnmanagedType.LPTStr)]string name)
{
return string.Format("Hello from .NET assembly, {0}!", name);
}
Which was nice! Anyway would anybody be able to help with passing arrays of strings in and out. I am pretty sure the C# section should look like this:
[DllExport(CallingConvention = CallingConvention.Cdecl)]
public static int passArrayStrings( [In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)] string[] tab, int i)
{
return 1;
}
I need some help on the C++(C) side on how to structure the array of strings in, such that they can be marshaled correctly. The mixed mode assembly created has both C# and and a C interface. As it is C and not C++ the arguments types of the exposed functions are not visible.
Thanks
You can use an IntPtr parameter.
You'll have to allocate unmanaged memory and copy the array into that blob anyway. Otherwise the GC will eat your array at some point.
Unmanaged Exports with Arrays
ok so after a lot of messing about I came to a solution:
// Array of strings types in
__declspec(dllimport) int passArrayStrings(BSTR* bstrArray, int i);
BSTR bstrArray[10] = { 0 };
for (int i = 0; i < 10; i++)
{
bstrArray[i] = ::SysAllocString(L"My String.");
}
int ff = passArrayStrings(bstrArray, 10);
for (int i = 0; i < 10; i++)
{
::SysFreeString(bstrArray[i]);
}
and on the c# side:
[DllExport(CallingConvention = CallingConvention.Cdecl)]
public static int passArrayStrings([In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.BStr, SizeParamIndex = 1)] string[] tab, int iSize)
{
return 1;
}

c# interop with ghostscript

I'm trying to access some Ghostscript functions like so:
[DllImport(#"C:\Program Files\GPLGS\gsdll32.dll", EntryPoint = "gsapi_revision")]
public static extern int Foo(gsapi_revision_t x, int len);
public struct gsapi_revision_t
{
[MarshalAs(UnmanagedType.LPTStr)]
string product;
[MarshalAs(UnmanagedType.LPTStr)]
string copyright;
long revision;
long revisiondate;
}
public static void Main()
{
gsapi_revision_t foo = new gsapi_revision_t();
Foo(foo, Marshal.SizeOf(foo));
This corresponds with these definitions from the iapi.h header from ghostscript:
typedef struct gsapi_revision_s {
const char *product;
const char *copyright;
long revision;
long revisiondate;
} gsapi_revision_t;
GSDLLEXPORT int GSDLLAPI
gsapi_revision(gsapi_revision_t *pr, int len);
But my code is reading nothing into the string fields. If I add 'ref' to the function, it reads gibberish. However, the following code reads in the data just fine:
public struct gsapi_revision_t
{
IntPtr product;
IntPtr copyright;
long revision;
long revisiondate;
}
public static void Main()
{
gsapi_revision_t foo = new gsapi_revision_t();
IntPtr x = Marshal.AllocHGlobal(20);
for (int i = 0; i < 20; i++)
Marshal.WriteInt32(x, i, 0);
int result = Foo(x, 20);
IntPtr productNamePtr = Marshal.ReadIntPtr(x);
IntPtr copyrightPtr = Marshal.ReadIntPtr(x, 4);
long revision = Marshal.ReadInt64(x, 8);
long revisionDate = Marshal.ReadInt64(x, 12);
byte[] dest = new byte[1000];
Marshal.Copy(productNamePtr, dest, 0, 1000);
string name = Read(productNamePtr);
string copyright = Read(copyrightPtr);
}
public static string Read(IntPtr p)
{
List<byte> bits = new List<byte>();
int i = 0;
while (true)
{
byte b = Marshal.ReadByte(new IntPtr(p.ToInt64() + i));
if (b == 0)
break;
bits.Add(b);
i++;
}
return Encoding.ASCII.GetString(bits.ToArray());
}
So what am I doing wrong with marshaling?
UnmanagedType.LPTStr is platform dependent (ANSI on Win98, Unicode on NT/XP). Your C++ structure uses the char * type, so you probably want UnmanagedType.LPStr instead.
Also, a long in C# is 64 bits while a long in C++ is 32 bits. You probably want to use int in your C# code.

Categories

Resources