Laden...

Übergabe mehrdimensionales Array an C-DLL

Erstellt von NixWissen vor 6 Jahren Letzter Beitrag vor 6 Jahren 1.912 Views
N
NixWissen Themenstarter:in
22 Beiträge seit 2016
vor 6 Jahren
Übergabe mehrdimensionales Array an C-DLL

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...

16.834 Beiträge seit 2008
vor 6 Jahren

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

N
NixWissen Themenstarter:in
22 Beiträge seit 2016
vor 6 Jahren

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?

16.834 Beiträge seit 2008
vor 6 Jahren

Multidimensionale Arrays lassen sich definitiv marshallen.
Ich nutze das für ushort[,] via UnmanagedType.LPArray

Wie lautet denn die genaue Exception?

N
NixWissen Themenstarter:in
22 Beiträge seit 2016
vor 6 Jahren

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.". ...

N
NixWissen Themenstarter:in
22 Beiträge seit 2016
vor 6 Jahren

Hier noch die Exception während Laufzeit:

"parameter #1" kann nicht gemarshallt werden: Für geschachtelte Arrays ist keine Marshallunterstützung vorhanden..

16.834 Beiträge seit 2008
vor 6 Jahren

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.

W
872 Beiträge seit 2005
vor 6 Jahren

Ein mehrdimensionales Array musst Du natürlich mit

byte [, ,]

definieren. Dein Beispiel benutzt

byte[][,]
N
NixWissen Themenstarter:in
22 Beiträge seit 2016
vor 6 Jahren

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


16.834 Beiträge seit 2008
vor 6 Jahren

Dass Du hier kein Multidimensionales, sondern ein Jagged-Array nutzt, ist mir auch erst jetzt aufgefallen.
Jagged Arrays können nicht direkt gemarshalled werden.