Moin!
Folgendes Problem: Habe ein mehrdimensionales Array welches zur Summenberechnung an externe C-Funktion übergeben wird:
// Externe C-DLL:
public static extern unsafe void cTest(byte[,] arrList, int arrLen, out int sum);
// Mein Array von Arrays:
byte[][,] arrList = new byte[100][,];
for(int i=0; i<100; i++)
{
// Liste füllen:
arrList[i] = new byte[,] { irgendwelche Werte... };
}
// Aufruf der C-Funktion in der DLL mit Zeiger auf erstes Array vom Typ byte[,]:
cTest(arrList[0], 100*arrList[0].Length, out output);
In der C-DLL die Funktionsdeklaration (berechnet Summe aller Elemente):
extern "C" __declspec(dllexport) void cTest(unsigned char *ptrArrList, int arrLen, int *output)
{
...
}
Kommt nur Murx heraus.
Bei nur einem Array vom Typ byte[,] ist alles ok, denn
hier liegen alle bytes beider Dimensionen hintereinander.
Bei 3 Dimensionen also Array vom Typ byte[][,] liegen dann alle Elemente nicht hintereinander?
Sieht jedenfalls so aus...
Ich bin mir ziemlich sicher, dass Du hier via MarashalAs eine Deklaration mitgeben musst, damit überhaupt das Sizing des Pointers stimmt.
Also sowas wie
public static extern unsafe void cTest(
[MarshalAs(UnmanagedType.ByValArray)]
byte[,] arrList,
int arrLen,
out int sum);
Und es könnte sogar sein, dass man bei UnmanagedType.ByValArray
zusätzlich den SizeConst
-Parameter gefüllt haben will, damit die Gegenseite überhaupt weiß, von welcher Dimension die Rede ist.
In Deinem Beispiel also
public static extern unsafe void cTest(
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)}
byte[,] arrList,
int arrLen,
out int sum);
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Beim Marshalling kam ne Exception. Sinngemäß liessen sich mehrdimensionale arrays nicht marshallen...
Zum anderen müssen die Array-Grössen unbedingt dynamisch bleiben.
Insoweit ich in der C-DLL nur das erste Array arrList[0][,] berechne, stimmt der Zeiger noch.
D.h. wohl, dass schon beim zweiten Array arrList[1][,] der Zeiger daneben liegt.
Demnach liegen alle bytes nicht hintereinander,oder?
Multidimensionale Arrays lassen sich definitiv marshallen.
Ich nutze das für ushort[,] via UnmanagedType.LPArray
Wie lautet denn die genaue Exception?
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Habe beide Varianten mit und ohne SizeConst ausprobiert:
public static extern unsafe void cTest( [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)} byte[,] arrList, int arrLen, out int sum);
Fehler 1 Fehler beim Ausgeben des System.Runtime.InteropServices.MarshalAsAttribute-Attributs -- "Der angegebene nicht verwaltete Typ ist nur in Feldern gültig.". ...
Hier noch die Exception während Laufzeit:
"parameter #1" kann nicht gemarshallt werden: Für geschachtelte Arrays ist keine Marshallunterstützung vorhanden..
Wenn ich jetzt nur Deine Funktionsdeklaration(Export) übersetze und den Rest ignoriere, dann müsste folgendes Äquivalent raus kommen:
public static extern void cTest(int count, IntPtr ptrArrList, int arrLen, ref int output)
Du empfängst das Array ja als Pointer und letzterer ist einfach ein ref.
Wieso Du im C# Code den code
Parameter gar nicht mappst, hab ich jetzt aktuell nicht verstanden.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Ein mehrdimensionales Array musst Du natürlich mit
byte [, ,]
definieren. Dein Beispiel benutzt
byte[][,]
Muß man nicht.
Habe nen eigenen Custom-Marshaler aus i-net code gebastelt, copy-paste-change.
Der kann das jetzt. Allerdings, Käse ist daß das Marshaling Ressourcen kostet.
Schneller wäre natürlich einfach umkopieren in 1-dimensionales Array, und fertig.
So what.
Sieht jetzt aber recht elegant aus da reihen und Spalten noch extra definiert sind:
public static extern void cTest(
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef =
typeof(MyByteArrayMarshaler ))] [In] byte[][,] arrList,
int count, int cols, int rows, out int sum);
und die Klasse:
public class MyByteArrayMarshaler : ICustomMarshaler
{
static MyByteArrayMarshaler marshaler;
private class MarshalInfo
{
// Temporäres IntPtr-Array (repräsentiert das jagged Array)
internal IntPtr[] InnerArrayPointers;
// Handle auf das temporäre IntPtr-Array
internal GCHandle InnerArrayPointersHandle;
// Handles auf die inneren Arrays
internal GCHandle[] InnerArrayHandles;
}
// Dictionary für die Zuordnung: nativer Pointer -> Information über gepinntes JaggedArray
readonly Dictionary<IntPtr, MarshalInfo> marshalMap = new Dictionary<IntPtr, MarshalInfo>();
public object MarshalNativeToManaged(IntPtr pNativeData)
{
throw new NotImplementedException();
}
public IntPtr MarshalManagedToNative(object managedObj)
{
// bei keinem Objekt, Nullpointer zurückgeben
if (managedObj == null)
return IntPtr.Zero;
if (!(managedObj is byte[][,]))
throw new MarshalDirectiveException("This custom marshaler must be used on a byte[][,].");
// Objekt Zurück-Casten
byte[][,] array = (byte[][,])managedObj;
MarshalInfo mi = new MarshalInfo();
// Da mehrdimensionale Arrays nicht gepinnt werden können, brauchen wir ein neues Pointer-Array.
mi.InnerArrayPointers = new IntPtr[array.Length];
// dieses pinnen
mi.InnerArrayPointersHandle = GCHandle.Alloc(mi.InnerArrayPointers, GCHandleType.Pinned);
// nun alle inneren Arrays pinnen und deren Pointer im Pointer-Array speichern
mi.InnerArrayHandles = new GCHandle[array.Length];
for (int i = 0; i < array.Length; i++)
{
mi.InnerArrayHandles[i] = GCHandle.Alloc(array[i], GCHandleType.Pinned);
mi.InnerArrayPointers[i] = mi.InnerArrayHandles[i].AddrOfPinnedObject();
}
// gepinnte Addresse des Pointer-Arrays ermitteln
IntPtr pointer = mi.InnerArrayPointersHandle.AddrOfPinnedObject();
// MarshalInfo im Dictionary vermerken
lock (marshalMap)
{
marshalMap.Add(pointer, mi);
}
// Addresse an nativen Code übergeben
return pointer;
}
public void CleanUpNativeData(IntPtr pNativeData)
{
// zugehörige MarshalInfo anhand des Pointers holen
MarshalInfo mi;
lock (marshalMap)
{
mi = marshalMap[pNativeData];
marshalMap.Remove(pNativeData);
}
// alle gepinnten inneren Arrays freigeben
for (int i = 0; i < mi.InnerArrayHandles.Length; i++)
{
mi.InnerArrayHandles[i].Free();
}
// zusätzliches Pointer-Array freigeben
mi.InnerArrayPointersHandle.Free();
}
public void CleanUpManagedData(object managedObj){}
public int GetNativeDataSize()
{
return -1;
}
public static ICustomMarshaler GetInstance(string cookie)
{
if (marshaler == null)
return marshaler = new MyByteArrayMarshaler();
return marshaler;
}
}
Dass Du hier kein Multidimensionales, sondern ein Jagged-Array nutzt, ist mir auch erst jetzt aufgefallen.
Jagged Arrays können nicht direkt gemarshalled werden.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code