Hi I am trying to access a c++ array of struct from c#. The struct itself also contains an array of strings and a String. The details are below. It's not working.. does not crash but does not transfer the data across (e.g. get nulls in the array, and random number in numberOfRows integer of struct/class). See my comments at the end of the code listing. Any suggestions?
c++ cppClassLib.cpp
// This is the main DLL file.
#include "stdafx.h"
#include <Objbase.h>
#include "cppClassLib.h"
#include <string.h>
//#include <malloc.h>
namespace cppClassLib {
/*
http://msdn.microsoft.com/en-us/magazine/cc164123.aspx
http://stackoverflow.com/questions/9093292/use-a-c-library-from-c-sharp-code
http://stackoverflow.com/questions/5671658/what-does-invalid-managed-unmanaged-type-combination-mean
http://stackoverflow.com/questions/2338146/returning-pointers-from-unmanaged-to-managed-code
CoTaskMemAlloc http://msdn.microsoft.com/en-us/library/windows/desktop/ms692727%28v=vs.85%29.aspx
*/
char *createStr(char *input)
{
int len = strlen(input)+1;
// can't use malloc because it needs to
// be accessible from another process.
// can't use CoTaskMemAlloc because get an
// error when trying to link, not found.
//char *newStr = (char *)CoTaskMemAlloc(len);
//char *newStr = (char *)malloc(len);
char *newStr = (char *)GlobalAlloc(GPTR,len);
//char* newStr = new char[len];
strcpy_s(newStr, len, input);
return newStr;
}
int Class1::getMatrixNumberOfRowsInColumnZero(int maxColumns, Class1::columnT *matrix)
{
if (maxColumns < 1) {
return 0;
}
return matrix[0].numberOfRows;
}
int Class1::getMatrix(int maxColumns, Class1::columnT *matrix)
{
if (maxColumns < 2) {
return 0;
}
int numberOfColumns = 2;
//Class1::columnT *column0 = (Class1::columnT *)GlobalAlloc(GPTR,sizeof(Class1::columnT));
Class1::columnT *column0 = &(matrix[0]);
column0->columnName = createStr("Col0");
int numRows = 2;
column0->numberOfRows = numRows;
char **rows = (char **)GlobalAlloc(GPTR,sizeof(char *)*numRows);
rows[0] = createStr("C0R0");
rows[1] = createStr("C0R1");
column0->rows = rows;
Class1::columnT *column1 = &(matrix[1]);
//Class1::columnT *column1 = (Class1::columnT *)GlobalAlloc(GPTR,sizeof(Class1::columnT));
column1->columnName = createStr("Col1");
numRows = 2;
column1->numberOfRows = numRows;
rows = (char **)GlobalAlloc(GPTR,sizeof(char *)*numRows);
rows[0] = createStr("C1R0");
rows[1] = createStr("C1R1");
column1->rows = rows;
//matrix[0]=column0;
//matrix[1]=column1;
//(matrix[0])->columnName = createStr("Test0");
return numberOfColumns; // 2
}
int Class1::getInt(void)
{
return 1234;
}
char* Class1::getHi(void)
{
//char *result = createStr("Hello");
//return result;
//return createStr("hello");
return createStr("hello");
}
char** Class1::getHeaderList(void)
{
char** list;
list = (char **)GlobalAlloc(GPTR,sizeof(char *)*2);
list[0]=createStr("test1");
list[1]="test2";
return list;
}
int Class1::getHeaderListTwo(int maxsize, char ***result)
{
char** list;
int len = 2;
if (maxsize < len) {
return NULL;
}
list = (char **)GlobalAlloc(GPTR,sizeof(char *)*maxsize);
list[0]=createStr("test01");
list[1]="test02";
for (int i=2; i<maxsize; ++i) {
list[i]="";
}
*result = list;
return len;
}
char* Class1::getHi2(void)
{
return "Hi";
}
char* Class1::getHi3(void)
{
return "Hi!";
}
void Class1::getData(int *totalColumns,
char** headers[2],
char** items[2][3])
{
*totalColumns = 2;
*headers[0]=createStr("Testing");
*headers[1]=createStr("Pets");
*items[0][0]=createStr("test1");
*items[0][1]=createStr("test2");
*items[0][2]=createStr("test3");
*items[1][0]=createStr("Cats");
*items[1][1]=createStr("Dogs");
*items[1][2]=createStr("Fish");
}
}
c++ cppClassLib.h
// cppClassLib.h
#pragma once
using namespace System;
#define DllExport __declspec( dllexport )
// http://msdn.microsoft.com/en-us/library/3y1sfaz2.aspx
namespace cppClassLib {
public class Class1 {
public:
struct columnT {
int numberOfRows;
char **rows;
char *columnName;
};
static DllExport int getMatrix(int maxColumns, columnT *matrix);
static DllExport int getMatrixNumberOfRowsInColumnZero(int maxColumns, columnT *matrix);
static DllExport void getData(int *totalColumns,
char** headers[2],
char** items[2][3]);
static DllExport char *getHi(void);
static DllExport char *getHi2(void);
static DllExport char *getHi3(void);
static DllExport int getInt(void);
static DllExport char** getHeaderList(void);
static DllExport int getHeaderListTwo(int maxsize, char ***result);
};
}
c# Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
/*
http://msdn.microsoft.com/en-us/library/aa288468%28v=vs.71%29.aspx
http://ondotnet.com/pub/a/dotnet/2002/03/18/customcontrols.html?page=2
http://www.codeproject.com/Articles/2995/The-Complete-Guide-to-C-Strings-Part-I-Win32-Chara
http://msdn.microsoft.com/en-us/library/z6cfh6e6.aspx
*/
namespace listViewFromC
{
public partial class Form1 : Form
{
/*
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getInt#Class1#cppClassLib##QAEHXZ")]
public static extern int getInt();
*/
// get EntryPoint using
// "DLL Export Viewer" software
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getHi2#Class1#cppClassLib##SAPADXZ")]
public static extern String getHi2();
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getHi#Class1#cppClassLib##SAPADXZ")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string getHi();
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getHeaderList#Class1#cppClassLib##SAPAPADXZ")]
[return: MarshalAs(UnmanagedType.LPArray,
ArraySubType=UnmanagedType.LPStr, SizeConst=2)]
public static extern String[] getHeaderList();
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getHeaderListTwo#Class1#cppClassLib##SAHHPAPAPAD#Z")]
public static extern int getHeaderListTwo(int maxsize,
[MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 0)]
ref String[] result
);
[StructLayout(LayoutKind.Sequential)]
public class columnType
{
public int numberOfRows;
[MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.LPStr)]
public String[] rows;
[MarshalAs(UnmanagedType.LPStr)]
public String columnName;
}
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getMatrix#Class1#cppClassLib##SAHHPAUcolumnT#12##Z")]
public static extern int getMatrix(int maxColumns,
[MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.Struct, SizeParamIndex = 0)]
columnType[] matrix);
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getMatrixNumberOfRowsInColumnZero#Class1#cppClassLib##SAHHPAUcolumnT#12##Z")]
public static extern int getMatrixNumberOfRowsInColumnZero(int maxColumns,
[MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.Struct, SizeParamIndex = 0)]
columnType[] matrix);
/*
[DllImport("kernel32.dll")]
static extern IntPtr GlobalAlloc(uint uFlags, uint dwBytes);
const uint GMEM_FIXED = 0x0000;
const uint GMEM_ZEROINIT = 0x0040;
const uint GPTR = GMEM_FIXED | GMEM_ZEROINIT;
*/
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//label1.Text = getInt().ToString();
label1.Text = getHi2();
listView1.Items.Clear();
listView1.Items.Add(label1.Text);
//listView1.
}
private void button2_Click(object sender, EventArgs e)
{
label1.Text = getHi();
}
private void button3_Click(object sender, EventArgs e)
{
const int maxsize = 2;
String[] headerList = new String[maxsize];
int len = getHeaderListTwo(maxsize, ref headerList);
MessageBox.Show("Got length " + headerList.Length+" ("+len+")");
label1.Text="";
for (int i = 0; i < headerList.Length; ++i)
{
if (headerList[i].Length>0)
{
label1.Text += headerList[i].ToString() + " // ";
}
else
{
label1.Text += " // ";
}
}
}
private void button4_Click(object sender, EventArgs e)
{
int maxColumns=5;
int numberOfColumns = 0;
columnType[] matrix = new columnType[maxColumns];
for (int i = 0; i < maxColumns; ++i)
{
matrix[i] = new columnType();
}
matrix[0].numberOfRows = 1; // pick something just to see if we can get it back
//uint sz = (uint)maxColumns*4;
//IntPtr matrixIP = GlobalAlloc(GPTR, sz);
//columnType[] matrix = matrixIP;
//IntPtr pointerArr = Marshal.AllocHGlobal(maxColumns*4);
//numberOfColumns = getMatrix(maxColumns, matrix);
label1.Text = getMatrixNumberOfRowsInColumnZero(maxColumns,matrix).ToString();
//label1.Text = matrix[0].columnName;
}
}
}
button1, button2 and button3 click events work fine so it shows that some of the c++ marshalling to c# is working. button4_click doesn't work.
label1.Text should return 1 since it's just returning matrix[0].numberOfRows
but in fact it returns some huge number.
Also the getMatrix call if uncommented, also doesn't work, it does run without crash but then all the elements of the array are empty (not filled with the data that getMatrix is supposed to fill them with).
Here is my solution. The only quirk with this solution is the need for fixed length arrays in the struct, which I would have preferred a variable length array but it wouldn't accept a Marshall of LPArray. Maybe it's not possible.
The main problem I had was that I had declared it as a class instead of a struct. The other issue was the array in the struct was an LPArray unmanaged marshall type, to try and have a variable length array but that didn't work since it needed to be a ByValArray (or SafeArray) for it to work.
cppClassLib.h
// cppClassLib.h
#pragma once
using namespace System;
#define DllExport __declspec( dllexport )
// http://msdn.microsoft.com/en-us/library/3y1sfaz2.aspx
#define maxRowsCpp 100
namespace cppClassLib {
public class Class1 {
public:
struct columnT {
int numberOfRows;
char *rows[maxRowsCpp];
char *columnName;
};
struct columnT2 {
int numberOfRows;
};
static DllExport int __thiscall getMatrix(int maxColumns, int maxRows, columnT *matrix[]);
static DllExport int __thiscall getMatrixNumberOfRowsInColumnZero(int maxColumns, columnT matrix[]);
static DllExport int __thiscall getMatrixNumberOfRowsInColumnZero2(int maxColumns, columnT2 matrix[]);
static DllExport void getData(int *totalColumns,
char** headers[2],
char** items[2][3]);
static DllExport char *getHi(void);
static DllExport char *getHi2(void);
static DllExport char *getHi3(void);
static DllExport int getInt(void);
static DllExport char** getHeaderList(void);
static DllExport int getHeaderListTwo(int maxsize, char ***result);
};
}
cppClassLib.cpp
// This is the main DLL file.
#include "stdafx.h"
#include <Objbase.h>
#include "cppClassLib.h"
#include <string.h>
//#include <malloc.h>
namespace cppClassLib {
/*
http://msdn.microsoft.com/en-us/magazine/cc164123.aspx
http://stackoverflow.com/questions/9093292/use-a-c-library-from-c-sharp-code
http://stackoverflow.com/questions/5671658/what-does-invalid-managed-unmanaged-type-combination-mean
http://stackoverflow.com/questions/2338146/returning-pointers-from-unmanaged-to-managed-code
CoTaskMemAlloc http://msdn.microsoft.com/en-us/library/windows/desktop/ms692727%28v=vs.85%29.aspx
*/
char *createStr(char *input)
{
int len = strlen(input)+1;
// can't use malloc because it needs to
// be accessible from another process.
// can't use CoTaskMemAlloc because get an
// error when trying to link, not found.
//char *newStr = (char *)CoTaskMemAlloc(len);
//char *newStr = (char *)malloc(len);
char *newStr = (char *)GlobalAlloc(GPTR,len);
//char* newStr = new char[len];
strcpy_s(newStr, len, input);
return newStr;
}
int Class1::getMatrixNumberOfRowsInColumnZero(int maxColumns, Class1::columnT matrix[])
{
if (maxColumns < 1) {
return 0;
}
return (matrix[0]).numberOfRows;
}
int Class1::getMatrixNumberOfRowsInColumnZero2(int maxColumns, Class1::columnT2 matrix[])
{
if (maxColumns < 1) {
return 0;
}
return (matrix[0]).numberOfRows;
}
int Class1::getMatrix(int maxColumns, int maxRows, Class1::columnT *matrix[])
{
// require at least able to have 2 rows and 2 columns
if ((maxColumns < 2) || (maxRows < 2)) {
return 0;
}
int numberOfColumns = 2;
int numberOfRows = 2;
//return matrix[0].columnName[0];
//Class1::columnT *column0 = (Class1::columnT *)GlobalAlloc(GPTR,sizeof(Class1::columnT));
Class1::columnT *column0 = &(*matrix[0]);
column0->columnName = createStr("Col0");
column0->numberOfRows = numberOfRows;
//char **rows = (char **)GlobalAlloc(GPTR,sizeof(char *)*numRows);
column0->rows[0] = createStr("C0R0");
column0->rows[1] = createStr("C0R1");
Class1::columnT *column1 = &(*matrix[1]);
//Class1::columnT *column1 = (Class1::columnT *)GlobalAlloc(GPTR,sizeof(Class1::columnT));
column1->columnName = createStr("Col1");
column1->numberOfRows = numberOfRows;
//rows = (char **)GlobalAlloc(GPTR,sizeof(char *)*numRows);
column0->rows[0] = createStr("C1R0");
column0->rows[1] = createStr("C1R1");
return numberOfColumns;
}
int Class1::getInt(void)
{
return 1234;
}
char* Class1::getHi(void)
{
//char *result = createStr("Hello");
//return result;
//return createStr("hello");
return createStr("hello");
}
char** Class1::getHeaderList(void)
{
char** list;
list = (char **)GlobalAlloc(GPTR,sizeof(char *)*2);
list[0]=createStr("test1");
list[1]="test2";
return list;
}
int Class1::getHeaderListTwo(int maxsize, char ***result)
{
char** list;
int len = 2;
if (maxsize < len) {
return NULL;
}
list = (char **)GlobalAlloc(GPTR,sizeof(char *)*maxsize);
list[0]=createStr("test01");
list[1]="test02";
for (int i=2; i<maxsize; ++i) {
list[i]="";
}
*result = list;
return len;
}
char* Class1::getHi2(void)
{
return "Hi";
}
char* Class1::getHi3(void)
{
return "Hi!";
}
void Class1::getData(int *totalColumns,
char** headers[2],
char** items[2][3])
{
*totalColumns = 2;
*headers[0]=createStr("Testing");
*headers[1]=createStr("Pets");
*items[0][0]=createStr("test1");
*items[0][1]=createStr("test2");
*items[0][2]=createStr("test3");
*items[1][0]=createStr("Cats");
*items[1][1]=createStr("Dogs");
*items[1][2]=createStr("Fish");
}
}
c# Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
/*
http://msdn.microsoft.com/en-us/library/aa288468%28v=vs.71%29.aspx
http://ondotnet.com/pub/a/dotnet/2002/03/18/customcontrols.html?page=2
http://www.codeproject.com/Articles/2995/The-Complete-Guide-to-C-Strings-Part-I-Win32-Chara
http://msdn.microsoft.com/en-us/library/z6cfh6e6.aspx
*/
namespace listViewFromC
{
public partial class Form1 : Form
{
const int maxRows = 100;
/*
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getInt#Class1#cppClassLib##QAEHXZ")]
public static extern int getInt();
*/
// get EntryPoint using
// "DLL Export Viewer" software
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getHi2#Class1#cppClassLib##SAPADXZ")]
public static extern String getHi2();
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getHi#Class1#cppClassLib##SAPADXZ")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string getHi();
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getHeaderList#Class1#cppClassLib##SAPAPADXZ")]
[return: MarshalAs(UnmanagedType.LPArray,
ArraySubType=UnmanagedType.LPStr, SizeConst=2)]
public static extern String[] getHeaderList();
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?getHeaderListTwo#Class1#cppClassLib##SAHHPAPAPAD#Z")]
public static extern int getHeaderListTwo(int maxsize,
[MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 0)]
ref String[] result
);
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct columnType
{
public int numberOfRows;
// note: can't marshal rows field as LPArray must be ByValArray or SafeArray
// for maximum of maxRows rows
[MarshalAs(UnmanagedType.ByValArray,
ArraySubType = UnmanagedType.LPStr,
SizeConst=maxRows)]
public String[] rows;
[MarshalAs(UnmanagedType.LPStr)]
public String columnName;
}
[StructLayout(LayoutKind.Sequential)]
public struct columnType2
{
public int numberOfRows;
}
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.ThisCall,
EntryPoint = "?getMatrix#Class1#cppClassLib##SEHHHQAPAUcolumnT#12##Z")]
public static extern int getMatrix(int maxColumns,
int maxRows,
[MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.Struct, SizeParamIndex = 0)]
ref columnType[] matrix);
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.ThisCall,
EntryPoint = "?getMatrixNumberOfRowsInColumnZero#Class1#cppClassLib##SEHHQAUcolumnT#12##Z")]
public static extern int getMatrixNumberOfRowsInColumnZero(int maxColumns,
[MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.Struct,
SizeParamIndex = 0)]
columnType[] matrix);
[DllImport("cppClassLib.dll",
CallingConvention = CallingConvention.ThisCall,
EntryPoint = "?getMatrixNumberOfRowsInColumnZero2#Class1#cppClassLib##SEHHQAUcolumnT2#12##Z")]
public static extern int getMatrixNumberOfRowsInColumnZero2(int maxColumns,
[MarshalAs(UnmanagedType.LPArray,
SizeParamIndex = 0,
ArraySubType = UnmanagedType.Struct)]
columnType2[] matrix);
/*
[DllImport("kernel32.dll")]
static extern IntPtr GlobalAlloc(uint uFlags, uint dwBytes);
const uint GMEM_FIXED = 0x0000;
const uint GMEM_ZEROINIT = 0x0040;
const uint GPTR = GMEM_FIXED | GMEM_ZEROINIT;
*/
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//label1.Text = getInt().ToString();
label1.Text = getHi2();
listView1.Items.Clear();
listView1.Items.Add(label1.Text);
//listView1.
}
private void button2_Click(object sender, EventArgs e)
{
label1.Text = getHi();
}
private void button3_Click(object sender, EventArgs e)
{
const int maxsize = 2;
String[] headerList = new String[maxsize];
int len = getHeaderListTwo(maxsize, ref headerList);
MessageBox.Show("Got length " + headerList.Length+" ("+len+")");
label1.Text="";
for (int i = 0; i < headerList.Length; ++i)
{
if (headerList[i].Length>0)
{
label1.Text += headerList[i].ToString() + " // ";
}
else
{
label1.Text += " // ";
}
}
}
private void button4_Click(object sender, EventArgs e)
{
int maxColumns=5;
int numberOfColumns = 0;
columnType[] matrix = new columnType[maxColumns];
for (int i = 0; i < maxColumns; ++i)
{
matrix[i] = new columnType();
}
matrix[0].numberOfRows = 1; // pick something just to see if we can get it back
matrix[0].columnName = "ABC";
// numberOfRows must be less than or equal to maxRows
columnType2[] matrix2 = new columnType2[maxColumns];
for (int i = 0; i < maxColumns; ++i)
{
matrix2[i] = new columnType2();
}
matrix2[0].numberOfRows = 1; // pick something just to see if we can get it back
//uint sz = (uint)maxColumns*4;
//IntPtr matrixIP = GlobalAlloc(GPTR, sz);
//columnType[] matrix = matrixIP;
//IntPtr pointerArr = Marshal.AllocHGlobal(maxColumns*4);
//int result = getMatrixNumberOfRowsInColumnZero(maxColumns,matrix);
//label1.Text = result.ToString();
numberOfColumns = getMatrix(maxColumns, maxRows, ref matrix);
label1.Text = matrix[0].columnName;
}
}
}
Related
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
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>
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;
}
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.
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
**