Ein Wrapper bringt hier glaube ich nur einen unnötigen Mehraufwand. Wenn die API nur reine C++ Klassen exportieren würde, dann müsste man den Weg über eine CLI-DLL gehen, aber wegen drei Methoden halte ich dies für etwas übertrieben.
die Typen scheinen wohl nicht kompatibel zu sein.
Du kannst dies aber so lösen:
short getParameters(bool% isA, bool% isB, bool% isC)
{
bool a = isA;
bool b = isB;
bool c = isC;
short result = nativeDll::getParameters(a, b, c);
isA = a;
isB = b;
isC = c;
return result;
}
Edit:
Wenn du es in einer Zeile machen möchtest, kannst du die Kompatibilität auch erzwingen:
short getParameters(bool% isA, bool% isB, bool% isC)
{
return ((short(_cdecl*)(bool%, bool%, bool%))&nativeDll::getParameters)(isA, isB, isC);
}
Dabei aber die korrekte Calling Convention der nativen Funktion verwenden.
wie herbivore geschrieben hat liegt es daran, dass die Anwendung sich selbst nocheinmal startet (habe ich an meinem Rechner getestet).
Dies hättest du leicht mit Refresh und HasExited herausfinden können.
erzeuge ein Byte-Array mit der genwünschten Größe und fülle dieses mit Hilfe der Stream.Read Methode in einer while-Schleife so lange, bis dieses komplett gefüllt ist (die Methode muss nicht die angeforderte Anzahl liefern) oder Read den Wert 0 zurück gibt (dein Fehlerfall).
Dabei musst du offset und count nach jedem Durchlauf anpassen.
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);
}
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
die Referenz auf das Objekt wird in dem dabei erzeugten Delegate mit gespeichert.
Der Delegate enthält immer eine Referenz auf die aufzurufene Methode. Ist die Methode nicht statisch, so enthält er zusätzlich noch das Objekt für welches die Methode aufgerufen werden soll.
die GUI wird erst aktualisiert, wenn deine methode "StartAnimation" zurückkehrt. Anstall mit Sleep zu warten, muss du diese zyklisch aufrufen, z.B. mit Hilfe eines Timers (der alle 500ms) ausgelöst wird.
die, meiner Meinung, einfachste Art ist es, das Darstellen der Colorbar und das Zeichnen des Bildes mit der gleichen Farbpalette zu machen (siehe BitmapPalette).
Diese kann dann beliebige Farbübergänge haben.
Den Slider kannst du dann auch relativ einfach zeichnen:
Du nimmst ein Bild mit 1x256 oder 256x1 Pixel mit den Werten 0..255 und wendest auf das Bild deine vorhandene Palette an. Dieses Bild kannst du dann direkt zeichenen oder dynamisch in ein Image einsetzten.
das Bild hat die folgenden zwei Metadateneinträge:
LogicalHeight: 480
LogicalWidth: 640
(Namen kommen aus FreeImage, ob dies die korrekten Bezeichner laut Spezifikation sind weiß ich nicht).
Je nach Anwendung/Implementierung wird wohl diese Auflösung verwendet beim Lesen/Schreiben des Bildes.
Ich würde mir eine Gif-Spezifikation suchen, und nachschauen wie Metadaten die Größe beeinflussen können.
edit:
Laut FreeImage-Dokumentation sind es keine echten Metadaten, sondern Animationsdaten (die in FreeImage ähnlich wie Exif-Metadaten behandelt werden.
Hier aus der Doku:
Zitat
The Animation metadata model is a generic model used to load and save animation metadata attached to an animated file (such as a GIF or a MNG file).
[...]
LogicalWidth
Width of entire canvas area that each page is displayed in 0-65535
[...]
LogicalHeight
Height of entire canvas area that each page is displayed in 0-65535
Vielleicht hilft dir das weiter, ggf musst du diese Werte auf die "richtige" Größe setzten.
als erstes würde ich mal alles inklusive NTBA für ~10s vom Strom trennen und dann wieder anschlißen. Wurde mir vom Support empfohlen und hat, zumindest bei meinem Problem damals, geholfen.
Wenn du Operationen im GUI-Thread machst, dann wird die Oberfläche erst dann aktualisiert, wenn ein Benutzer-Code abgearbeitet wurde. Wenn am Anfang dein Bild sichtbar ist, und am Ende deiner Schleife wieder auf sichtbar geändert wird, so hat sich für die Oberfläche nichts geändert, also siehst du auch keine Änderung.
Du musst die Operation in einen 2. Thread auslagern und mit Hilfe des Dispatchers die Visible-Änderungen in den GUI-Thread leiten.
als ersten Schritt solltest du die verwendeten Datentypen überprüfen.
In Calling Win32 DLLs in C# with P/Invoke ist eine allgemeine Einführung in das Thema PInvoke.
Unten findest du auch eine Tabelle welcher C/C++ Datentyp zu welchem C# Datentyp passt.
ich habe mir die Seite angesehen. Das Beispiel passt jedoch nur bedingt zu meinem Problem.
Wie völlig richtig dargestellt wird, muss man bei allen Änderungen an den Daten aus einem anderen Thread auf die 'Umlenkung' in den GUI Thread achten. Das passiert bei mir auch, funktioniert auch ohne Probleme.
Es geht bei mir darum, dass ich die Bindung der Collection an die Listbox trenne, diese sich jedoch nicht von dem CollectionChanged-Event dieser abhängt, oder zumindest nicht sofort. Da ich mit Dispatcher.Invoke(...) arbeite, wird dies auch sicher abgearbeitet, bevor im nicht-GUI Thread Clear() aufgerufen wird.
Ich gehe einfach davon aus, dass wenn ich ItemsSource auf null setzte, dass dann die Bindung gelöst wird, und nicht, für mich nicht feststellbar, irgendwann später.