Convert string[] to a double pointer in C# - c#

I'm trying to hand a string[] as byte** from C# to C++. I have to say, as a disclaimer, that I never really worked with C++ or pointers before. So I may get some things wrong. I encode and pin every string with a code like this:
var strings = new string[] { "Some string", "Another string" };
var handles = new GCHandle[string.Length];
var pointers = new byte*[string.Length];
for (int index = 0; index < strings.Length; index++)
{
var bytes = Encoding.UTF8.GetBytes(strings[index]);
var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
handles[index] = handle;
pointers[index] = (byte*)handle.AddrOfPinnedObject().ToPointer();
}
//Release the handles later...
The problem I'm currently having is, that I can't pin the array that contains all the pointers. I tried to use
GCHandle.Alloc(pointers, GCHandleType.Pinned);
which throws an exception and
fixed (byte** pointer = &pointers[0])
won't work, because the array is unfixed outside the fixed block. So I thought I could maybe do something like this
byte** pointer = (byte**)this.pointers[0]
to obtain a pointer, which results in an exception, because I'm trying to read/write protected memory.
I'm currently unsure if it's even possible to accomplish, or if my approach is just wrong. Does anybody has a idea or was in a similar situation?
Thanks for the help!

Related

How can I get pointers to a variable number of arrays in unsafe C# code

If I have an array of double[,] objects where the length of the array is known in advance, then I can setup an array of pointers to the double[,] objects like this:
int NumMatrices = 3;
double[][,] VectorOfMatrix = new double[NumMatrices][, ];
for (int i = 0; i < VectorOfMatrix.Length; i++) VectorOfMatrix[i] = new double[10, 10];
unsafe {
fixed (double* fpM0 = VectorOfMatrix[0], fpM1 = VectorOfMatrix[1], fpM2 = VectorOfMatrix[2]) {
double** ppMatrix = stackalloc double*[3];
ppMatrix[0] = fpM0;
ppMatrix[1] = fpM1;
ppMatrix[2] = fpM2;
...
}
}
But if the length of the array is not known in advance, how do the equivalent thing?
Ultimately, you can't. It is a marker on the "local" that defines something as being fixed from the perspective of the JIT, so you need a "local" per fixed element, and the number of locals is determined at compile time, not runtime.
I suspect you would need a locally scoped fixed at the call-site when you need it for that individual inner-array - but that's not very problematic: fixed is a remarkably cheap way of pinning (it is literally just a reference copy on the stack, with the JIT knowing from context that it means "pinned")
That said: I suspect you might also be able to use spans here, and bypass the entire need to use fixed or pointers.

Array of StringBuilders not working with P/Invoke

This was marked as a duplicate of Pass writeable StringBuilder array to C++ from C# but that does not at all address the issue of the use of StringBuilder[] and only comments on the incorrect use of wcsncpy and MarshalAs. I am not even using wcsncpy or MarshalAs anywhere in my question.
So, I'm trying to use one of my C++ functions in C# like this:
[DllImport("CPPDLLImport", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
static extern void StringBuilderArrayTest(StringBuilder[] text, int[] textLengths, int numberOfTextItems);
Now, normally this would work fine of course, but I'm don't think I'm getting the correct data in C++ from text.
So, in order to test this I set up a P/Invoke project.
I am calling the method in C# using this:
var stringBuilderArray = new StringBuilder[]
{
new StringBuilder("abc"),
new StringBuilder("def"),
new StringBuilder("ghi")
};
var stringBuilderLengths = new int[3] { 3, 3, 3 };
StringBuilderArrayTest(stringBuilderArray, stringBuilderLengths, 3);
And this is my method in C++ - to test it I'm just printing it to the screen, and to make sure it definitely wasn't printf, I am literally printing it character-by-character just to be sure:
EXPORT void StringBuilderArrayTest(wchar_t** text, int* textLengths, int numberOfTextItems) {
for (int i = 0; i < numberOfTextItems; i++)
for (int j = 0; j < textLengths[i]; j++)
printf("%c", text[i][j]);
}
And, well, the parameter text is definitely not right. It's just giving me "ÇPÿ" three times - definitely not the string "abc", "def" or "ghi".
Is it just not marshaling the StringBuilder array correctly? If so, how can I get this array of strings across?
UPDATE:
OK, I have decided to use a string[] for sending data to my C++ code, however, I now need to do the reverse - sending data from C++ into C#. So, what alternatives are there to using an array of StringBuilder?

Marshalling Array of Struct from IntPtr - Only First Value Marshals Correctly

I'm passing an address to an array of structures into a C# DLL from an external program.
I thought that I would first make a simple test, in order to see if the approach would work, by trying to marshal a pointer into an array of structs on the C# side.
Given the following struct:
[StructLayout(LayoutKind.Sequential)]
struct TestStruct
{
public int id;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string someString;
}
and the following code which attempts to read the struct array (the section up until the for loop is just to simulate the pointer being passed from the other program):
TestStruct[] testStructs = new TestStruct[3];
testStructs[0].id = 1;
testStructs[0].someString = "Value 1";
testStructs[1].id = 2;
testStructs[1].someString = "Value 2";
testStructs[2].id = 3;
testStructs[2].someString = "Value 3";
int size = Marshal.SizeOf(testStructs[0]);
IntPtr ptrFirst = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(testStructs[0], ptrFirst, true);
long ptrAddrLong = ptrFirst.ToInt64();
for (int i = 0; i < testStructs.Length; i++) {
IntPtr thisPtr = new IntPtr(ptrAddrLong);
TestStruct testStruct = Marshal.PtrToStructure<TestStruct>(thisPtr);
ptrAddrLong += size;
}
can anyone shed any light why, when I debug through the for loop, only the first testStruct item is marshalled correctly from the pointer? All subsequent items contain garbage in the fields, as it appears the pointer address is incorrect after the first iteration.
First iteration:
Second iteration:
Size is reported as 36, which seems to be correct.
I've tried using explicit layout, but this didn't make any difference.
Thanks
Why do you think that Marshal.StructureToPtr marshals anything more than you have told it to marshal?
Marshal.StructureToPtr(testStructs[0], ptrFirst, true); marshals a single TestStruct to the memory at ptrFirst.
It is basically just a smart memory copy that takes into account all those marshaling attributes.
P.S.: And take into account that memory at ptrFirst can hold at most Marshal.SizeOf(testStructs[0]). So you can't (at least without risking some nasty memory access problem) read a memory behind ptrFirst + size.

Marshalling C array in C# - Simple HelloWorld

Building off of my marshalling helloworld question, I'm running into issues marshalling an array allocated in C to C#. I've spent hours researching where I might be going wrong, but everything I've tried ends up with errors such as AccessViolationException.
The function that handles creating an array in C is below.
__declspec(dllexport) int __cdecl import_csv(char *path, struct human ***persons, int *numPersons)
{
int res;
FILE *csv;
char line[1024];
struct human **humans;
csv = fopen(path, "r");
if (csv == NULL) {
return errno;
}
*numPersons = 0; // init to sane value
/*
* All I'm trying to do for now is get more than one working.
* Starting with 2 seems reasonable. My test CSV file only has 2 lines.
*/
humans = calloc(2, sizeof(struct human *));
if (humans == NULL)
return ENOMEM;
while (fgets(line, 1024, csv)) {
char *tmp = strdup(line);
struct human *person;
humans[*numPersons] = calloc(1, sizeof(*person));
person = humans[*numPersons]; // easier to work with
if (person == NULL) {
return ENOMEM;
}
person->contact = calloc(1, sizeof(*(person->contact)));
if (person->contact == NULL) {
return ENOMEM;
}
res = parse_human(line, person);
if (res != 0) {
return res;
}
(*numPersons)++;
}
(*persons) = humans;
fclose(csv);
return 0;
}
The C# code:
IntPtr humansPtr = IntPtr.Zero;
int numHumans = 0;
HelloLibrary.import_csv(args[0], ref humansPtr, ref numHumans);
HelloLibrary.human[] humans = new HelloLibrary.human[numHumans];
IntPtr[] ptrs = new IntPtr[numHumans];
IntPtr aIndex = (IntPtr)Marshal.PtrToStructure(humansPtr, typeof(IntPtr));
// Populate the array of IntPtr
for (int i = 0; i < numHumans; i++)
{
ptrs[i] = new IntPtr(aIndex.ToInt64() +
(Marshal.SizeOf(typeof(IntPtr)) * i));
}
// Marshal the array of human structs
for (int i = 0; i < numHumans; i++)
{
humans[i] = (HelloLibrary.human)Marshal.PtrToStructure(
ptrs[i],
typeof(HelloLibrary.human));
}
// Use the marshalled data
foreach (HelloLibrary.human human in humans)
{
Console.WriteLine("first:'{0}'", human.first);
Console.WriteLine("last:'{0}'", human.last);
HelloLibrary.contact_info contact = (HelloLibrary.contact_info)Marshal.
PtrToStructure(human.contact, typeof(HelloLibrary.contact_info));
Console.WriteLine("cell:'{0}'", contact.cell);
Console.WriteLine("home:'{0}'", contact.home);
}
The first human struct gets marshalled fine. I get the access violation exceptions after the first one. I feel like I'm missing something with marshalling structs with struct pointers inside them. I hope I have some simple mistake I'm overlooking. Do you see anything wrong with this code?
See this GitHub gist for full source.
// Populate the array of IntPtr
This is where you went wrong. You are getting back a pointer to an array of pointers. You got the first one correct, actually reading the pointer value from the array. But then your for() loop got it wrong, just adding 4 (or 8) to the first pointer value. Instead of reading them from the array. Fix:
IntPtr[] ptrs = new IntPtr[numHumans];
// Populate the array of IntPtr
for (int i = 0; i < numHumans; i++)
{
ptrs[i] = (IntPtr)Marshal.PtrToStructure(humansPtr, typeof(IntPtr));
humansPtr = new IntPtr(humansPtr.ToInt64() + IntPtr.Size);
}
Or much more cleanly since marshaling arrays of simple types is already supported:
IntPtr[] ptrs = new IntPtr[numHumans];
Marshal.Copy(humansPtr, ptrs, 0, numHumans);
I found the bug by using the Debug + Windows + Memory + Memory 1. Put humansPtr in the Address field, switched to 4-byte integer view and observed that the C code was doing it correctly. Then quickly found out that ptrs[] did not contain the values I saw in the Memory window.
Not sure why you are writing code like this, other than as a mental exercise. It is not the correct way to go about it, you are for example completely ignoring the need to release the memory again. Which is very nontrivial. Parsing CSV files in C# is quite simple and just as fast as doing it in C, it is I/O bound, not execute-bound. You'll easily avoid these almost impossible to debug bugs and get lots of help from the .NET Framework.

Exception Passing SAFEARRAY in VARIANT to C# From C++ COM Server

I have spent the last day searching documentation, reviewing forum posts, and googling to try to do something that I am guessing could be easily done with the right information.
I have a very large existing C++ application that has an already defined COM server with many methods exposed. I am trying to use those COM methods in a C# application (I am experienced in C++, but a C# newbie).
So in my VS2010 C# application, I add the COM server as a reference. COM Methods are visible in the object browser, and passing single-valued strings, floats, and ints seems to work fine.
But I am stumped trying to read SAFEARRAY values passed out of the C++ COM server into the C# application. Eventually I need to pass string arrays from C++ server to the C# app, but in just testing passing an array of floats, I have code that builds but fails with the following exception when I try to cast the System.Object containing the array of floats to (float[]) ,
"exception {System.InvalidCastException: Unable to cast object of type 'System.Object[]' to type 'System.Single[]'."
With intellisence I can see that the Object contains the correct 8760 long array of floats, but I am unable to access that data in C#.
Here is the code on the C# side (d2RuleSet is an interface defined in the DOE2Com COM server). The exception above is thrown on the last line below.
DOE2ComLib.DOE2Com d2RuleSet;
d2RuleSet = new DOE2ComLib.DOE2Com();
System.Int32 i_Series =0;
System.Object pv_WeatherData;
float[] faWeatherData;
iOut = d2RuleSet.GetWeatherData(i_Series, out pv_WeatherData);
Type typeTest;
typeTest = pv_WeatherData.GetType();
int iArrayRank = typeTest.GetArrayRank();
Type typeElement = typeTest.GetElementType();
faWeatherData = (float[])pv_WeatherData;
Below is the section in the idl file defining the C++ COM method
HRESULT GetWeatherData( [in] int iSeries, [out] VARIANT* pvWeatherData, [out,retval] int * piErrorCode);
Below is the C++ code where the VARIANT data is loaded.
void CDOE2BaseClass::GetWeatherData( int iSeries, VARIANT* pvWeatherData, int* piErrorCode)
{
*piErrorCode = 0;
if (iSeries < 0 || iSeries >= D2CWS_NumSeries)
*piErrorCode = 1;
else if (m_faWeatherData[iSeries] == NULL)
*piErrorCode = 3;
else
{
SAFEARRAYBOUND rgsaBound;
rgsaBound.lLbound = 0;
rgsaBound.cElements = 8760;
// First lets create the SafeArrays (populated with VARIANTS to ensure compatibility with VB and Java)
SAFEARRAY* pSAData = SafeArrayCreate( VT_VARIANT, 1, &rgsaBound );
if( pSAData == NULL ) {
#ifndef _DOE2LIB
_com_issue_error( E_OUTOFMEMORY);
#else
//RW_TO_DO - Throw custom Lib-version exception
OurThrowDOE2LibException(-1,__FILE__,__LINE__,0,"OUT OF MEMORY");
#endif //_DOE2LIB
}
for (long hr=0; hr<8760; hr++)
{
COleVariant vHrResult( m_faWeatherData[iSeries][hr] );
SafeArrayPutElement( pSAData, &hr, vHrResult );
}
// Now that we have populated the SAFEARRAY, assign it to the VARIANT pointer that we are returning to the client.
V_VT( pvWeatherData ) = VT_ARRAY | VT_VARIANT;
V_ARRAY( pvWeatherData ) = pSAData;
}
}
Thanks in advance for help with this problem, I feel like I spent too much time on what should be a simple problem. Also please post up any links or books that that cover interop between native C++ and C# well (I think I have already ping-ponged through most of the Visual Studio/MSDN documentation, but maybe I missed something there too).
-----------------End Of Original Question------------------------------------------------------
I am editing to post the code from phoog's successful solution below, so others can read and use it.
int iOut = 0;
System.Int32 i_Series =0;
System.Object pv_WeatherData = null;
iOut = d2RuleSet.GetWeatherData(i_Series, out pv_WeatherData);
Type typeTest;
typeTest = pv_WeatherData.GetType();
int iArrayRank = typeTest.GetArrayRank();
Type typeElement = typeTest.GetElementType();
//float[] faWeatherData = (float[])pv_WeatherData;
float[] faWeatherData = ConvertTheArray((object[])pv_WeatherData);
....
float[] ConvertTheArray(object[] inputArray)
{
float[] result = new float[inputArray.Length];
for (var index = 0; index < result.Length; index++)
result[index] = (float)inputArray[index];
return result;
}
The marshalled SAFEARRAY is a System.Object[]; you can't reference-convert that to a System.Single[]. You have to cast the individual elements. This would work, assuming that all the elements of the argument array are in fact boxed floats:
float[] ConvertTheArray(object[] inputArray)
{
float[] result = new float[inputArray.Length];
for (var index = 0; index < result.Length; index++)
result[index] = (float)inputArray[index];
return result;
}
You could do that with a lot less typing using Linq, but as you're a C# newbie I thought a more basic solution might be better.
EDIT
Since you indicated in your comment that your object[] is referenced as an object, here's the usage example:
object obj = GetMarshalledArray();
float[] floats = ConvertTheArray((object[])obj);
EDIT 2
A shorter solution using Linq:
float[] ConvertTheArray(object[] inputArray)
{
return inputArray.Cast<float>().ToArray();
}

Categories