How to add Elements to IVector which is bound to XAML from another thread

0

I am currently working on a Windows 10 UWP C++ project under Visual Studio Community 2017 for Windows 10 version 10.0.16299.0.

The following procedure: I have a TCP server running on my computer, which sends a message to the connected clients at certain events. The project I am working on now has my TCPClient, which receives the message and should display the values in the UI.

In the MainPage constructor I create a std:: thread which calls my "clientThread" function. In this function, I instantiate an object of my client class. As soon as I have connected to the server, I create a new std:: thread which executes an endless loop. This loop calls the "processPacketType" function, which simply calls a switch and asks if the received packet is a ChatMessage packet or not. If so, the message is retrieved.

Now to my problem: The MainPage class has a private AudioSessionViewModel object from my created class, which returns an IVector^. Now I would like to extend the Vector with the message I received. With my "processPacketType"function, I just want to instantiate a new object of my AudioSession class and add it to the vector (which I passed from thread to thread as a reference). However, I get the following error message: Exception raised on 0x778B08C2 in SoundManager - Client. exe: Microsoft C++ exception: Platform:: InvalidCastException ^ for storage location 0x0F15EC3C. HRESULT: 0x80004002 Interface not supported WinRT information: Interface not supported

The funny thing is: My processPacketType function is called from my endless loop. If I pass the newly created object to the vector outside the loop, it works. But if I do it myself in the loop, the error message comes up.

The problem is really hard to explain, but I hope you understand what I need.

//MainPage.xaml.h
namespace SoundManager___Client {
    public ref class MainPage sealed {
    public:
        MainPage();

        property AudioSessionViewModel^ ViewModel {
            AudioSessionViewModel^ get() { return this->viewModel; };
        }

    private:
        AudioSessionViewModel^ viewModel;
    };
}

//MainPage.xaml.cpp
#include "pch.h"
#include "MainPage.xaml.h"

using namespace SoundManager___Client;

using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Controls::Primitives;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Navigation;

void createClient(AudioSessionViewModel^ audioSessionViewModel);

MainPage::MainPage() {
    InitializeComponent();

    this->viewModel = ref new AudioSessionViewModel();

    std::thread client(createClient, this->viewModel);
    client.detach();
}

void createClient(AudioSessionViewModel^ audioSessionViewModel) {
    Client myClient("127.0.0.1", 1111, audioSessionViewModel);
    myClient.Connect();

    std::string buffer;

    while (true) {
        Sleep(10000);
    }
}

namespace SoundManager___Client {
    bool Client::processPacketType(const PacketType _packetType, AudioSessionViewModel^ audioSessionViewModel) {
        switch (_packetType) {
        case PacketType::ChatMessage: {
            std::string message;
            if (!getString(message))
                return false;

            OutputDebugStringA((LPCSTR)message.c_str());
            OutputDebugString(L"\n");
            audioSessionViewModel->AudioSessions->Append(ref new AudioSession(L"Test", L"20")); //<--------- Here I get the error

            break;
        }
        default:
            OutputDebugString(L"Unrecognized PacketType.\n");
            break;
        }
        return true;
    }

    void Client::clientThread(Client &_client, AudioSessionViewModel^ audioSessionViewModel) {
        PacketType packetType;
        //If I put the code to add the element over here, it works. But it doesn't inside the while loop or in the processPacketType which get called inside the loop
        while (true) {
            if (_client.mTerminateThreads == true)
                break;

            if (!_client.getPacketType(packetType))
                break;

            if (!_client.processPacketType(packetType, audioSessionViewModel))
                break;
        }

        OutputDebugString(L"Lost connection to the server.\n");
        _client.mTerminateThreads = true;
        if (!_client.closeConnection())
            OutputDebugString(L"Socket to the server was closed successfully.\n");
        else
            OutputDebugString(L"Socket was not able to be closed.\n");
    }

#pragma once

#include <sstream>

namespace SoundManager___Client {
    public ref class AudioSession sealed {
    private:
        Platform::String^ sessionName;
        Platform::String^ sessionVolume;

    public:
        AudioSession(Platform::String^ sessionName, Platform::String^ sessionVolume) : sessionName{ sessionName }, sessionVolume{ sessionVolume } { }

        property Platform::String^ SessionName {
            Platform::String^ get() { return this->sessionName; }
        }
        property Platform::String^ SessionVolume {
            Platform::String^ get() { return this->sessionVolume; }
        }
        property Platform::String^ OneLineSummary {
            Platform::String^ get() {
                std::wstringstream wstringstream;
                wstringstream << this->SessionName->Data();
                wstringstream << this->SessionVolume->Data();
                return ref new Platform::String(wstringstream.str().c_str());
            }
        }
    };

    public ref class AudioSessionViewModel sealed {
    private:
        Windows::Foundation::Collections::IVector<AudioSession^>^ audioSessions;

    public:
        AudioSessionViewModel() {
            this->audioSessions = ref new Platform::Collections::Vector<AudioSession^>();

            AudioSession^ audioSession = ref new AudioSession(L"MASTER", L"100");
            this->audioSessions->Append(audioSession);
            audioSession = ref new AudioSession(L"CHROME", L"50");
            this->audioSessions->Append(audioSession);
        }

        property Windows::Foundation::Collections::IVector<AudioSession^>^ AudioSessions {
            Windows::Foundation::Collections::IVector<AudioSession^>^ get() { return this->audioSessions; }
        }
    };
}

Solution-Update: I finally figured out how to call the dispatcher in C++/CX:

Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, ref new Windows::UI::Core::DispatchedHandler([this]() {

}));

And now I finally understood why my call worked, but only outside the loop:

I assume at least that the call from the std:: thread executes the first execution in the UI thread and only when it reaches the .detach(), the code is executed in the new thread. If I am wrong, I ask for correction in the comments!

c++
windows
uwp
windows-runtime
uwp-xaml
asked on Stack Overflow Mar 13, 2018 by CaptTaifun • edited Mar 14, 2018 by CaptTaifun

2 Answers

1

i think the problem is that your adding items to AudioSessions from the background thread while its bounded to xaml; To change the UI you need to be in the UI Threads. Use Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync to get in the UI thread and then add it to the collection

answered on Stack Overflow Mar 13, 2018 by Dave Smits
0

Updated the main post with the solution and an explanation!

answered on Stack Overflow Mar 14, 2018 by CaptTaifun

User contributions licensed under CC BY-SA 3.0