Laden...

[gelöst] DllImport: Array mit bestimmter Größe in Funktions-Deklaration?

Erstellt von Olle vor 10 Jahren Letzter Beitrag vor 10 Jahren 4.637 Views
O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren
[gelöst] DllImport: Array mit bestimmter Größe in Funktions-Deklaration?

Hallo,
ich möchte ein Gerät ansteuern, dazu verwende ich Funktionen aus einer C-dll. Soweit klappt auch alles ganz gut, bis auf Folgendes:

Das Gerät liest kontinuierlich Positionswerte auf 3 Achsen. Jetzt gibt es eine Callback-Funktion, mit der ich eine Funktion definiere, die in bestimmten Zeitintervallen vom Gerät aufgerufen wird, wenn neue Werte gelesen wurden. Die hab ich so der dll entnommen bzw umgeschrieben:

public static extern int setPositionCallback(uint devNo,  FPS_PositionCallback callback, uint lbSmpTime);

Die FPS_PositionCallback-Funktion macht mir aber Schwierigkeiten, in dem C-Beispielprogramm sieht die so aus:

static void positionCallback( unsigned int devNo, unsigned int count, unsigned int index,
                              const double * const pos[3] )

und ich habe sie folgendermaßen adaptiert:


public delegate void FPS_PositionCallback(uint devNo, uint length, uint index, ref double[] position);

Wenn ich jetzt aber eine Funktion an die setPositionCallback-Funktion übergebe, wird diese genau einmal ausgeführt, dann verabschiedet sich das Programm mit der Meldung "vshost32.exe funktioniert nicht mehr". Ausserdem steht in dem position-Array nur ein Wert, obwohl 3 hätten übergeben werden sollen.
Meine Vermutung ist, dass nicht wirklich ein Array an die Funktion übergeben wird, sondern irgendwie versucht wird auf das noch nicht initialisierte Array der Funktion zuzugreifen und dabei was schief geht.
Daher die Frage: Kann ich irgendwie erreichen, dass beim Aufruf der Funktion schon ein Array mit 3 Einträgen zur Verfügung steht? Oder ist das Problem vielleicht an ganz anderer Stelle?

16.835 Beiträge seit 2008
vor 10 Jahren
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] position) 

sollte es sein (ohne Gewähr).

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Das klappt leider nicht. Wenn ich das nur in der Deklaration der aufzurufenden Funktion einfüge, bleibt der Fehler bestehen, wenn ich das noch in die Deklaration von FPS_CallBack schreibe, erhalte ich:> Fehlermeldung:

MarshalDirectiveException wurde nicht behandelt.
Der Steuerparameterindex für die Arraygröße wird nicht unterstützt.

Für SizeParamIndex =1 bekomme ich übrigends auch die alte Fehlermeldung, für =2 beendet sich das Programm ohne einen Mucks zu machen.
Gibts noch andere Möglichkeiten, was ich versuchen könnte? Oder sieht das für euch eher so aus, als wär ich auf dem Holzweg?

16.835 Beiträge seit 2008
vor 10 Jahren

Mal mit IntPtr und Marshalling probiert? Is länger her, dass ich das mal aktiv machen musste.

1.361 Beiträge seit 2007
vor 10 Jahren
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] double[] position)   

sollte es sein (ohne Gewähr).

Hi, es sollte SizeConst=3 heißen und nicht SizeParamIndex.

Beste Grüße
zommi

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Hey,
das geht schonmal besser, jetzt bekomme ich beim ersten Aufruf zumindest ein Array mit Länge 3 zurück gegeben (wobei die Werte darin Quatsch sind). Vor dem zweiten Aufruf der Funktion wird das Programm wieder mit der Fehlermeldung "vshost32 funktioniert nicht mehr" beendet.
Was ist denn die Sache mit dem IntPtr und Marshalling? Ich hab keine Ahnung davon und nur mal kurz gegoogled... Habt ihr das was gutes an der Hand, was ich mir angucken kann um das zu verstehen?

4.939 Beiträge seit 2008
vor 10 Jahren

Hallo zusammen,

der C-Code


const double * const pos[3]

stelt einen (konstanten) Zeiger auf ein (konstantes) Array dar (und die Größenangabe ist dabei sogar irrelevant, d.h. es ist äquivalent zu "const double * const pos[]" und dies ist wieder das selbe wie "const double * const * pos").

Der P/Invoke Interop Assistant (s.a. Typumwandlung Struct C in C#: Zeigervariablen und Zeiger auf Funktionen) erzeugt daher daraus (wenn man die Zahl 3 aus der obigen C-Signatur löscht!):


public static extern  void positionCallback(uint devNo, uint count, uint index, ref System.IntPtr pos);

Um dann in C# an die Array-Daten zu kommen, kann man die Marshal.Copy-Methode benutzen.

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Hmm... das bringt leider auch nicht mehr Erfolg.
Ich poste hier nochmal meinen Code und den Beispielcode, vllt mache ich auch was anderes kapital falsch...

Mein Code:

public delegate void FPS_PositionCallback(uint devNo, uint length, uint index, ref System.IntPtr position);

[DllImport(@"..\..\fps3010.dll", EntryPoint = "FPS_setPositionCallback")]
public extern static int setPositionCallback(uint devNo, FPS_PositionCallback callback, uint lbSmpTime);


public void getPos(uint devNo, uint length, uint index, ref System.IntPtr position)
{
	double[] pos= new double[3];
	Marshal.Copy(position, pos, 0, 3);		
}

private void button1_Click(object sender, EventArgs e)
{
	FPS_PositionCallback cB = getPos;
	setPositionCallback(device, cB, Convert.ToUInt32(splTime));
}

Der Beispielcode:

typedef void (* FPS_PositionCallback) ( unsigned int   devNo,
                                        unsigned int   length,
                                        unsigned int   index,
                                        const double * const positions[3] );

FPS_API int WINCC FPS_setPositionCallback( unsigned int         devNo,
                                           FPS_PositionCallback callback,
                                           unsigned int         lbSmpTime );

static void positionCallback( unsigned int devNo, unsigned int count, unsigned int index,
                              const double * const pos[3] )
{
  unsigned int i;

  for ( i = 0; i < count; ++i ) {
    printf( "%12.1f ms: %12.3f nm %12.3f nm %12.3f nm\n",
            timestamp * 1000, pos[0][i], pos[1][i], pos[2][i] );
  }
}

FPS_setPositionCallback( devNo, positionCallback, sp ->sampleExp );
4.939 Beiträge seit 2008
vor 10 Jahren

Hallo Olle,

du hast ja auch ein 2-dimensionales Array in deinem C-Code, d.h. du mußt in C# auch ein jagged-Array anlegen:


double[][] pos= new double[count][3];

Wie man jetzt die Daten 'marshallt', mußt du jedoch selber herausfinden (bzw. suchen).

Einfacher wäre es jedoch, wenn man die C-Funktion so umschreiben könnte, daß sie ein eindimensionales Array erzeugt und zurückgibt:


struct Pos
{
   double values[3];
};

struct Pos *positions = malloc(count * sizeof(Pos));

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Hey,
wenn das das einzige Problem wäre, wäre ich schon sehr glücklich 😉
Aber auch wenn ich die getPos-Funktion leer lasse, stürzt das ganze nach dem ersten Aufruf mit der Meldung "vshost32 funktioniert nicht mehr" ab...

4.939 Beiträge seit 2008
vor 10 Jahren

Dann debugge doch mal deinen Code, s. [Artikel] Debugger: Wie verwende ich den von Visual Studio?
Kommt das Programm überhaupt in die getPos()-Methode hinein oder stürzt es vorher schon ab - und wie sieht der Callstack aus?

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Das hab ich versucht. Er geht in die getPos-Methode und verlässt sie auch wieder ohne zu meckern. Danach kommt die Fehlermeldung.
Gerade hab ich auch festgestellt, dass ich die Callback-Funktion wieder löschen kann. Aber auch wenn ich am Ende der getPos-Methode, diese als Callback wieder lösche, tritt der Fehler nach Methodenende auf. Scheint also nichts mit einem erneuten Aufruf zu tun zu haben, sondern vorher zu passieren.
In der Aufrufliste steht vor dem Absturz auch nur die getPos-Methode. Oder meintest du was anderes mit dem Callstack?

W
872 Beiträge seit 2005
vor 10 Jahren

Die Referenz für den Callback/delegate liegt zurzeit nur im Unmanaged Code. Ergo räumt nach einer Weile der GC den Speicher für das Delegate weg, da der GC nur den Managed Bereich untersucht.
Du musst eine zusätzliche Referenz für den GC im Managed Bereich halten, damit das nicht passiert, z.B. durch ein statischtes Feld in einer Klasse. Du übergibst dann das Feld an den unmanaged Code.
Habe selber mal so ein ähnliches Problem gehabt und auch tagelang gesucht.

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Hey,
hab des gerade mal versucht, aber wenn ich mir eine Klasse mache mit:

	public static class Callbacks
	{
		public static FPS_PositionCallback CBFct;
	}

und in meinem Programm

Callbacks.CBFct = getPos;
FPS.setPositionCallback(device, Callbacks.CBFct, Convert.ToUInt32(splTime));

zuweise (FPS ist die Klasse in der alle Funktionen zu dem Gerät stehen), stürzt er zwar nicht ab, aber die getPos-Methode wird auch leider nie aufgerufen...

W
872 Beiträge seit 2005
vor 10 Jahren

Ich würde das statische Feld in Deine FPS Klasse packen, denn da gehört es ja logisch hin. Ansonsten sehe ich keinen Grund, warum das nicht funktionieren sollte, da Du ja nur eine zusätzliche Referenz hinzufügst.
Auch die Conversion from IntPtr zu Managed gehört dahin.

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Ok, das hab ich gemacht, da funktioniert der Aufruf auch wieder. Aber jetzt ist alles wie gehabt und er verabschiedet sich nach dem ersten Durchlauf der getPos-Methode...

W
872 Beiträge seit 2005
vor 10 Jahren

Erstmal würde ich Release ausprobieren, damit Du hoffentlich eine echte Exception bekommst.
Dann würde ich auf jeden Fall erstmal nur mit ner leeren getpos arbeiten, die nur loggt, wenn Deine Funktion aufgerufen wird.
Bist Du Dir sicher was die DLL-Aufrufkonventionen anbetrifft?

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Hey,

Release bringt keine andere Fehlermeldung. Die getPos-Methode ist im Moment auch leer. Was meinst du mit den DLL-Aufrufkonventionen?

4.221 Beiträge seit 2005
vor 10 Jahren

Was meinst du mit den DLL-Aufrufkonventionen?

CallingConvention-Enumeration

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

U
1.688 Beiträge seit 2007
vor 10 Jahren

der C-Code

  
const double * const pos[3]  
  

stelt einen (konstanten) Zeiger auf ein (konstantes) Array dar

Ist es nicht eher ein Array von Zeigern (auf double) (die const mal weggelassen)?

Das wäre dann sicherlich auch die Ursache der Problematik hier.

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Habe die gerade mal alle CallingConvention-Möglichkeiten durchprobiert, da gehen nur StdCall und Winapi, bei den anderen behauptet er den Einstiegspunkt nicht zu finden. Der Fehler bleibt aber bestehen. Oder muss man da mehr beachtet, als das beim DLL-Import als Argument mitzugeben?

Zu dem Zeiger-Array: Wie müsste ich das realisieren? In meiner kleinen Welt dachte ich, das könnte ich durch dire Übergabe von "System.IntPtr[] position" realisieren, brachte aber auch nix 😦

W
872 Beiträge seit 2005
vor 10 Jahren

Du solltest das mit der Calling Convention genau prüfen, denn das bestimmt, wer die Daten auf dem Stack aufräumen soll.
IntPtr sollte auf jeden Fall ok sein, egal, was jetzt genau dahinter steckt.
Du hast mehrere offene Punkte. Ich würde erst mal schauen, dass der Callback mehrfach hintereinander funktioniert.
Dann würde ich mich an die Callback Funktion selber machen. Der C-Code legt aber nahe, dass es ein zwei-Dimensionales Array ist, in dem die eine Dimension immer 3 Lange ist und die andere ne flexible Länge hat.
Edit - Hast Du mal den Return Wert von dem SetCallnback überprüft?

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Die Set-Methode gibt "Kein Fehler" zurück.
Was bedeutet denn, die Calling Convention zu prüfen? Hast du da ggf. was an der Hand, was ich mir angucken kann, um zu verstehen, was ich da tun muss? Aus der msdn-Beschreibung werd ich nicht schlau...

S
248 Beiträge seit 2008
vor 10 Jahren

Warum definierst du nicht das Array als ganz simples eindimensionales Array (double*) mit der Länge (n*3). Damit ist das Marshalling super einfach. Dann kannst du den Parmater als IntPtr definieren, in ein C# Array kopieren und nach der Bearbeitung wieder zurück (falls nötig).

In einem C++ Projekt (ich gehe davon aus, dass du die C++ Dll geschrieben hast) ist die Standard Aufrufkonvention cdecl. Dies kannst du global im Projekt umstellen (da du nicht weisst was dies ist, gehe ich davon aus, dass du es nicht gemacht hast). Ansonsten kannst du für jede Methode einzeln die Konvention festlegen.

Poste doch bitte die komplette .h Datei welche die exportierten Methoden beinhaltet. Normalerweise heisst diese <Projektname>.h

Für dich zum Nachlesen:
Aufrufkonvention

Grüße
spooky

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Hab die Header Datei, aus der ich abgeschrieben hab, angehangen.
Alle anderen Methoden, die ich analog wie oben gepostet importiert hab, funktionieren einwandfrei...

S
248 Beiträge seit 2008
vor 10 Jahren

Mein Vorschlag wäre Folgender:

Erstelle in C++ eine Struct mit 3 Werten und verwende diese anstatt eines mehrdimensionalen Arrays. Zusätzlich solltest du die Aufrufkonvention für die Callback-Funktion angeben (WINCC):

struct PositionData
{
	double v0;
	double v1;
	double v2;
};

typedef void (WINCC* FPS_PositionCallback) ( unsigned int   devNo,
                                        unsigned int   length,
                                        unsigned int   index,
					PositionData * positions);

Der 'length' Parameter gibt die Länge des Arrays an.

In C# definierst du die Struct nochmal genau so und änderst Definition der Callback-Methode:

public struct Positions
{
    public double v0;
    public double v1;
    public double v2;
}

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public unsafe delegate void FPS_PositionCallback(uint devNo, uint length, uint index, Positions* position);

[DllImport("TestDll.dll")]
public static extern int setPositionCallback(uint devNo, FPS_PositionCallback callback, uint lbSmpTime);

Damit sollte es funktionieren.

Solltest du kein Unsafe verwenden wollen, so kannst du im C# den Parameter auch als IntPtr definieren und so verwenden:

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void FPS_PositionCallback(uint devNo, uint length, uint index, IntPtr position);

[DllImport("TestDll.dll")]
public static extern int setPositionCallback(uint devNo, FPS_PositionCallback callback, uint lbSmpTime);

static void Test(uint devNo, uint length, uint index, IntPtr position)
{
    // Speicher anlegen
    double[] data = new double[length * 3];
    // Daten in C# Array kopieren
    Marshal.Copy(position, data, 0, data.Length);

    // Was tolles machen
    for (int i = 0; i < data.Length; i++)
    {
        data[i] = i;
    }

    // Daten zurück kopieren
    Marshal.Copy(data, 0, position, data.Length);
}

Grüße
spooky

4.939 Beiträge seit 2008
vor 10 Jahren

Hallo Spook,

das hatte ich ja auch gestern schon vorgeschlagen, aber anscheinend will oder kann der OP es nicht...

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Hab beide Möglichkeiten ausprobiert, aber es ändert leider immer noch nichts, nach dem ersten Durchlauf der getPos-Methode crasht vshost32.exe... 😦
Der Absturz passiert anscheinend auch einfach nach beenden der Methode, noch bevor sie erneut aufgerufen wird...

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Nee, der Code klappt leider auch nicht. Aber wie es auch vorher schonmal jemand gesagt hat, glaube ich auch, dass es garnicht so wichtig ist, wie ich die Daten, die übergeben werden letztendlich verarbeite, das bekomm ich dann schon irgendwie hin.
Mein größeres Problem ist ja, dass das Programm sich verabschiedet, nachdem die als Callback übergebene Funktion das erste mal durchgelaufen ist, noch bevor es überhaupt zu einem zweiten Aufruf kommt.

S
248 Beiträge seit 2008
vor 10 Jahren

Zwei Möglichkeiten:
Unterschiedliche CallConv zwischen C++/C#.
Du speicherst den Delegaten nicht, wie es weismat dir geraten hat.

Bei mir funktioniert der Code, siehe angehängtes Projekt.
Nun sollte es doch möglich sein, den Fehler zu finden ...

Grüße
spooky

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Es funktioniert zwar immer noch nicht, aber dafür gibts was neues!
Ich hatte den GC nicht verwendet und die Methode, die ich als Callback übergeben hatte sah anders aus. Wenn ich aber deine Test-Methode nehme, beendet sich das Programm nach ihrem ersten Durchlauf, ohne einen Mucks zu sagen. Wenn ich die Zeile

		Marshal.Copy(data, 0, position, data.Length);

aber auskommentiere, lande ich jedoch wieder bei der alten Fehlermeldung.
Sagt das einem von euch etwas?

O
Olle Themenstarter:in
16 Beiträge seit 2014
vor 10 Jahren

Kaum zu glauben aber wahr, ich habs letztendlich hinbekommen.
Der Vollständigkeit halber hier das, worans gelegen hat:
1.) Wie Spook zuletzt auch meinte wars die CallingConvention, hier musste ich

	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate void FPS_PositionCallback(UInt16 devNo, UInt16 length, UInt32 index, IntPtr[] positions);

nutzen.
2.) Die uints mussten explizit als UInt16 deklariert werden, sonst stimmte die Speicherposition des IntPtr nicht.
3.) Der IntPtr war tatsächlich ein Pointer-Array.

War ne schwere Geburt, aber jetzt läufts. Danke euch allen für die Unterstützung! 👍