Laden...

WPF controls (Image) mittels C-DLL callback updaten

Erstellt von greenpepper vor 12 Jahren Letzter Beitrag vor 12 Jahren 3.921 Views
G
greenpepper Themenstarter:in
10 Beiträge seit 2011
vor 12 Jahren
WPF controls (Image) mittels C-DLL callback updaten

Hallo zusammen,

ich möchte eine WPF GUI erstellen deren Controls zum Großteil mit Daten aus einer C DLL gefüllt werden sollen. Ein spezieller Fall bereitet mir hier Schwierigkeiten. Die DLL dient als Interface zu einer Kamera von der ich unter anderem Live Bilder (mittels eines Callbacks) abholen kann. Ich bin soweit dass ich das Kamerabild erfolgreich als .jpg file abspeichern kann (nur um zu sehen ob die BitmapSource richtig gefüllt wird). Jedesmal wenn das Callback aufgerufen wird, möchte ich jetzt
das Image control cameraImage mit der BitmapSource neu befüllen. Ich könnte hier eine ganze Reihe von Ansätzen auflisten die ich versucht habe (mit BeginInvoke udgl.) es hat aber bisher nichts zum Ziel geführt. Schön langsam bin ich am Verzweifeln und hoffe hiermit hier zielführenden Input zu bekommen. Ich komme aus der hardwarenahen Programmierung und bin in Sachen WPF und C# noch ziemlich neu, sonst würde die Geschichte wohl auch leichter fallen...
Vielen Dank auf alle Fälle im Voraus für allfällige Hinweise und Ideen!


public partial class MainWindow : Window
{
    [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
    public unsafe delegate void NewImageCallback(IntPtr pUserData);

    [DllImport("Camera.dll", CallingConvention = CallingConvention.Cdecl)]
    public unsafe static extern int attachNewImageCallback(IntPtr Hnd, MulticastDelegate imageCallback, IntPtr pUserData);

    NewImageCallback del = new NewImageCallback(imageCallback);

    public MainWindow()
    {
        InitializeComponent();
        attachNewImageCallback(Handle, del, pnt);
    }

    public unsafe static void imageCallback(IntPtr pUserData)
    {
        BitmapSource bs = BitmapSource.Create(...);
        // zu Testzwecken in file gespeichert
        FileStream stream = new FileStream("new.jpg", FileMode.OpenOrCreate);
        JpegBitmapEncoder encoder = new JpegBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(bs));
        encoder.Save(stream);
        stream.Close();
        // hier soll das Image control cameraImage mit der BitmapSource gefüllt werden
        
    }
}


<Window x:Class="testGUI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="GUI" Height="350" Width="525" Loaded="Window_Loaded" Icon="/WpfApplication1;component/logo.png" WindowStyle="SingleBorderWindow">
    <Image Height="220" Name="cameraImage" Stretch="Fill" Width="300" />
</Window>

5.658 Beiträge seit 2006
vor 12 Jahren

Hi greenpepper,

vermutlich suchst du die WriteableBitmap-Klasse.

Christian

Weeks of programming can save you hours of planning

G
greenpepper Themenstarter:in
10 Beiträge seit 2011
vor 12 Jahren

Hallo und danke für den Hinweis, das hilft schon mal ein wenig. Jedoch habe ich immer noch das Problem dass das Callback von einem DLL Thread aufgerufen wird und ich dadurch

System.InvalidOperationException wurde nicht behandelt.
Message=Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet.

bekomme. Hier die Initialisierung im Konstruktor.


public MainWindow()
{
    InitializeComponent();
    writeableBitmap = new WriteableBitmap(
                width,
                height,
                300,
                300,
                PixelFormats.Rgb24,
                null);
    this.cameraImage.Source = writeableBitmap;
    attachNewImageCallback(Handle, del, pnt);
}

Wenn ich dann im Callback darauf zugreifen will mit z.B.:


public unsafe static void imageCallback(IntPtr pUserData)
{
    writeableBitmap.Lock();
}

kommt (natürlich) die oben genannte Exception.

6.862 Beiträge seit 2003
vor 12 Jahren

Baka wa shinanakya naoranai.

Mein XING Profil.

G
greenpepper Themenstarter:in
10 Beiträge seit 2011
vor 12 Jahren

Den Thread kenne ich und er hat leider auch nicht geholfen. Wann der imageCallback aufgerufen wird, wird nicht der GUI sondern von einem der DLL-Threads bestimmt. Dadurch habe ich wohl auch das Problem im Callback auf keine Member und Methoden meiner MainWindow Klasse Zugriff zu haben um sie per Invoke oder BeginInvoke aufzurufen. Im Callback hole ich mir das Kamerabild mit einer weiteren DLL Funktion ab und diese Daten muss ich auf das Image control bekommen - im Idealfall mit der Bildfrequenz von etwa 20 Hz.

<-- leider immer noch recht ratlos...

F
10.010 Beiträge seit 2004
vor 12 Jahren

Du hast aus dem Callback keinen zugriff, weil du ihn dir nicht ermöglicht hast.

S
248 Beiträge seit 2008
vor 12 Jahren

Hallo greenpepper,

ich hoffe, dass dir dies weiterhilft:

        private void YourThreadProc(IntPtr dummyPtr)
        {
            if (!Dispatcher.CheckAccess())
            {
                Dispatcher.Invoke(new Action<IntPtr>(YourThreadProc), dummyPtr);
                return;
            }

            bitmap.Lock();
            byte[] data = new byte[bitmap.Format.BitsPerPixel / 8 * bitmap.PixelWidth];
            Random rand = new Random();
            for (int i = 0; i < bitmap.PixelHeight; i++)
            {
                rand.NextBytes(data);
                IntPtr ptr = bitmap.BackBuffer + (i * bitmap.BackBufferStride);
                Marshal.Copy(data, 0, ptr, data.Length);
            }
            bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
            bitmap.Unlock();
        }

grüße

G
greenpepper Themenstarter:in
10 Beiträge seit 2011
vor 12 Jahren

Hallo Spook,
und danke. Das hat geholfen. Falls es wen interessiert oder womöglich noch Verbesserungsvorschläge auftauchen hier der laufende Code:


public partial class MainWindow : Window
{
    static private void UpdateImageData(byte[] pixels)
    {
        if (!Dispatcher.CurrentDispatcher.CheckAccess())
        {
            Dispatcher.CurrentDispatcher.Invoke(new Action<byte[]>(UpdateImageData), pixels);
            return;
        }

        writeableBitmap.Lock();
        writeableBitmap.WritePixels(new Int32Rect(0, 0, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight), pixels, stride, 0);
        writeableBitmap.Unlock();
    }
                      
    public unsafe static void imageCallback(IntPtr pUserData)
    {
        // fetch image data
        // ...
        byte[] pixels = new byte[height * stride];

        int x = 0;
        for (int i = 0; i < 220; i++)
        {
            for (int j = 0; j < 300; j++, x++)
            {
                pixels[j * 3 + i * stride] = param.redChannel[x];
                pixels[j * 3 + i * stride + 1] = param.greenChannel[x];
                pixels[j * 3 + i * stride + 2] = param.blueChannel[x];
            }
        }
        writeableBitmap.Dispatcher.Invoke(new Action<byte[]>(UpdateImageData), pixels);
            
    }
}

5.658 Beiträge seit 2006
vor 12 Jahren

Hi greenpepper,

danke für das Feedback! Mich würde mal interessieren, wie es mit der Performance der WriteableBitmap-Klasse aussieht. Ist das ganze tatsächlich schnell genug, um ein Video flüssig abzuspielen?( Ich gehe einfach mal davon aus, daß es sich bei der Kamera um eine Webcam handelt.

Christian

Weeks of programming can save you hours of planning

S
248 Beiträge seit 2008
vor 12 Jahren

Hallo greenpepper,

1.

writeableBitmap.Dispatcher.Invoke(new Action<byte[]>(UpdateImageData), pixels);

Den Invoke würde ich weglassen und die Methode direkt aufrufen, denn diese schaut selbst nach ob ein Invoke nötig ist oder nicht (doppelt gemoppelt).

2.
Du schreibst die Farbkanäle getrennt. Es ist auf jeden Fall schneller wenn du ein passendes PixelFormat wählst und den gesammten Puffer mit einem Aufruf von WriteableBitmap.WritePixels kopierst. Falls du nur den AlphaKanal entfernen willst kannst du z.B. Bgr32 nehmen und dieser wird ignoriert.

grüße

G
greenpepper Themenstarter:in
10 Beiträge seit 2011
vor 12 Jahren

Hallo Christian,

die Performance ist sehr gut wie du auch am Video im Anhang sehen kannst. Wegen der sehr geringen erlauben Dateianhanggröße kann ich leider nur mit einem sehr kurzen Schwenker über mein Blackberry dienen. Wie gesagt - läuft tadellos!

5.658 Beiträge seit 2006
vor 12 Jahren

Hi greenpepper,

sehr schön, danke! Das ist ja eine Framerate, die sich sehen lassen kann...

Aber sag mal, ist das ein Einschußloch da vorne im Objektiv? 😃

Christian

Weeks of programming can save you hours of planning

G
greenpepper Themenstarter:in
10 Beiträge seit 2011
vor 12 Jahren

Hallo Christian,

das ist kein Einschussloch 😉
Das Ding ist ja keine Webcam sondern ein Testaufbau für eine zukünftige medizinische Kamera. Das "Einschussloch" sind Lichblitzer des Abdeckglases des handfeführten Gerätes.

@Spook
Wenn ich nicht doppelt mopple und die Methode direkt aufrufe habe ich wieder dasselbe Problem:

Message=Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet.

bei


writeableBitmap.Lock();

Und da ich die Farbkanäle genau so getrennt bekomme bleibt mir denke ich nichts anderes übrig als es so zu machen wie ich es mache...?
writableBitmap wird es ja dann eh auf einmal zugewiesen:


writeableBitmap.WritePixels(new Int32Rect(0, 0, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight), pixels, stride, 0);

F
10.010 Beiträge seit 2004
vor 12 Jahren

Das mit dem Static solltest Du noch mal ändern, das hat da nichts zu suchen.

S
248 Beiträge seit 2008
vor 12 Jahren

Wenn die Kanäle so kommen, dann ist es halt so.

Zu deinem Problem:
Das Problem ist relativ simpel:
Die Dispatcher-Klasse regelt die Umleitung in den GUI Thread. Die Klasse Window erbt von DispatcherObject die Property "Dispatcher" vom Typ Dispatcher.

Ich rufe in meinem Code diese Property ab. Da du aber Dispatcher.CurrentDispatcher aufrufst interpretiert der Compiler dies als Aufruf der statischen Property Klasse Dispatcher.

if (!Dispatcher.CheckAccess()) vs
if (!Dispatcher.CurrentDispatcher.CheckAccess())

Und da du den Dispatcher für den momentanen Thread holst ist es logisch, dass auch kein Umleiten nötig ist, es ist ja der selbe Thread 😉

if (!this.Dispatcher.CheckAccess()) ist wohl die bessere, da eindeutigere, Schreibweise in diesem Fall.

Siehe Fzelle: Die Methoden nicht als statisch deklarieren.

grüße

G
greenpepper Themenstarter:in
10 Beiträge seit 2011
vor 12 Jahren

Danke Spook!
Wenn Fzelle wenn er schon Kommentare abgibt diese auch ein wenig hilfreicher gestalten könnte wäre das static wohl nie nötig gewesen 😉
"Du hast aus dem Callback keinen zugriff, weil du ihn dir nicht ermöglicht hast."
und
"Das mit dem Static solltest Du noch mal ändern, das hat da nichts zu suchen."
ist zwar "nett" hilft aber genau gar nichts.
Manche Leute sind eben neu in der wpf c# welt und können mit solchen Hinweisen nahe Null anfangen.

Ich werd schon draufkommen...