wpfmultithreadingc++-cliinterop

C++ Cli passing image pointers to WPF to display


My image processing is all in C++. Was using MFC, now exploring to upgrade to WPF. WPF side will be mainly user interface, I'm passing image pointers from unmanaged to managed for display. I found that if I'm use thread to loop the backend vector it will not display anything, but if I change it to button trigger one by one it will be functional. With the same functions, I have no idea what's going on.

WPF own my CppCli object, which it can directly call any function in c++ / cli. What could be wrong here.

wpf cs code:

private CppCli cli;

public MainWindow() { 
    cli = new CppCli(); 
    cli.ImageUpdated += Cli_ImageUpdated; // delegate and event to call method Cli_ImageUpdated 
    InitializeComponent(); 
}

private void Cli_ImageUpdated(object sender, ImageUpdateEventArgs e) 
{ 
    Dispatcher.Invoke(() =>
    {
        MyImageControl.Source = e.img;
    });
} 

private void LiveToggle_Click(object sender, RoutedEventArgs e) {

    cli.LiveToggle(); 
}

Cpp cli code:

public ref class ImageUpdateEventArgs : EventArgs { 
    public: BitmapSource^ img;

    ImageUpdateEventArgs(BitmapSource^ bitmap) { 
        img = bitmap;
    } 
};

void CppCli::LiveToggle() { 
    running = !running; 

    if (running) { 
        liveThread = gcnew Thread(gcnew ThreadStart(this, &MathFunctions::UpdateDisplay)); 
        liveThread->IsBackground = true; 
        liveThread->Start();
    }

    //UpdateDisplay() // This works if replace the whole thing above. 
}

void CppCli::UpdateDisplay() { 
    while (running) { // Remove this while loop and it works.

        int stride, rows, cols, bufferSize; 
        stride = cvObj->images[imageIndex].step[0]; 
        rows = cvObj->images[imageIndex].rows; 
        cols = cvObj->images[imageIndex].cols; 
        bufferSize = cvObj->images[imageIndex].total() * cvObj->images[imageIndex].elemSize(); 
        IntPtr ptr(cvObj->images[imageIndex].ptr()); 

        ImageUpdated(this, gcnew ImageUpdateEventArgs(BitmapSource::Create(cols, rows, 96, 96, PixelFormats::Bgra32, nullptr, ptr, bufferSize, stride)));

        Thread::Sleep(100); 
        imageIndex++; 
        if (imageIndex >= cvObj->images.size())
        { 
            imageIndex = 0;
        }
    } 
}

Solution

  • Since the BitmapSource is created in a thread other than the UI thread, it must be frozen before being assigned to the Source property of the Image element in the UI thread:

    private void Cli_ImageUpdated(object sender, ImageUpdateEventArgs e) 
    { 
        e.img.Freeze(); // call here or in the ImageUpdateEventArgs constructor
    
        Dispatcher.Invoke(() => MyImageControl.Source = e.img);
    }
    

    For details, please see the Remarks section in Freezable.Freeze and Freezable.IsFrozen.