Erstellen Ihrer eigenen Windows-Runtime-Komponenten für tolle Apps im Metro-Stil

Entwicklerblog für Windows 8-Apps

Ein Einblick in die Entwicklung von Apps im Metro-Stil – präsentiert vom Windows 8-Entwicklerteam

Erstellen Ihrer eigenen Windows-Runtime-Komponenten für tolle Apps im Metro-Stil

Rate This
  • Comments 0

Für Windows 8 haben wir die Plattform vollständig neu gestaltet, damit Sie vertraute Programmiersprachen und Technologien verwenden können, um geräte- und formfaktorspezifische Apps zu erstellen. Mit der Windows-Runtime lassen sich sogar ganz einfach mehrere Sprachen innerhalb einer einzigen App verwenden. Sie können eine App im Metro-Stil mit HTML und JavaScript erstellen, die mit dem Xbox 360-Controller interagieren kann, indem Sie Ihre eigene Komponente für Windows-Runtime in C++ programmieren. Wiederverwendbare XAML-Steuerelemente können über Windows-Runtime-Komponenten verfügbar gemacht werden, die sofort von in C++ und C# geschriebenen Apps im Metro-Stil genutzt werden können. Grundsätzlich können Sie Apps auf der Windows 8-Plattform mit der von Ihnen gewünschten Programmiersprache erstellen.

In diesem Blogbeitrag wird erläutert, was Sie wissen müssen, um eigene Komponenten für Windows-Runtime zu erstellen.

Die Grundlagen

Die Windows-Runtime ist das Herzstück, das die Sprachenvielfalt ermöglicht. Sie können die Runtime in gewohnter Weise von JavaScript, C++, C# und Visual Basic aus aufrufen. Die gleiche Grundlage ist auch zum Erstellen eigener APIs verfügbar.

Eine Windows-Runtime-Komponente, die Sie erstellen und Ihrer App hinzufügen, wird typischerweise als Drittanbieterkomponente für Windows-Runtime bezeichnet. Diese unterscheidet sich von Erstanbieterkomponenten, die bereits Teil der Windows 8-Plattform sind. Sie können diese Drittanbieterkomponenten für Windows-Runtime in C++, C# oder Visual Basic schreiben. Die verfügbaren APIs können von überall aus aufgerufen werden, auch von anderen Windows-Runtime-Komponenten in Ihrer App. Die mit Windows-Runtime-Komponenten verfügbar gemachten APIs lassen sich mit jeder Sprache aufrufen.

Die Windows-Runtime-Komponenten für Ihre App können Windows-Runtime-APIs, Win32-, COM-, .NET-APIs oder Drittanbieterbibliotheken verwenden, sofern diese die Entwicklung von Apps im Metro-Stil unterstützen. Beachten Sie, dass die von Ihnen erstellten Windows-Runtime-Komponenten sich von den klassischen C++-DLLs oder .NET-Assemblys unterscheiden, die APIs verfügbar machen. Das Erstellen einer Klassenbibliothek in .NET oder einer eigenständigen DLL in C++ unterscheidet sich von der Erstellung einer Windows-Runtime-Komponente. Windows-Runtime-Komponenten werden in WinMD-Dateien deklariert, die Windows-Runtime-Metadaten bereitstellen und es Sprachen wie JavaScript ermöglichen, Windows-Runtime-APIs zu verwenden (zum Beispiel pascalCasedNames-Unterstützung für APIs, die für JavaScript verfügbar gemacht werden). Durch die Windows-Runtime-Metadaten kann Visual Studio hervorragende Bearbeitungsfunktionen bereitstellen, wie etwa IntelliSense-Unterstützung.

Warum eigene Windows-Runtime-Komponenten erstellen?

Durch eigene Windows-Runtime-Komponenten können Sie eine Architektur der Wiederverwendbarkeit und der Sprachinteroperabilität entwickeln. Betrachten wir nun einige App-Szenarien, in denen veranschaulicht wird, wie Sie Drittanbieterkomponenten für Windows-Runtime nutzen können, um die Benutzerfreundlichkeit zu erhöhen.

Sie können Ihre Windows-App im Metro-Stil in den Xbox 360-Controller für Windows integrieren
Abbildung 1: Xbox 360-Controller

Verwenden von Win32- und COM-APIs in Ihrer App im Metro-Stil

Die Plattform von Internet Explorer 10 ermöglicht Ihnen, mit HTML, CSS und JavaScript besonders benutzerfreundliche Apps im Metro-Stil zu erstellen. Wie können Sie jedoch ein Spiel, das Sie mit HTML5-Canvas erstellt haben, in den Xbox 360-Controller für Windows integrieren? Die XInput-API, mit der Apps Controllerbefehle empfangen können, macht Win32-APIs verfügbar, die nicht direkt von JavaScript angesteuert werden können.

Dies ist ein hervorragendes Beispiel dafür, wie die Erstellung einer Windows-Runtime-Komponente dieses Problem lösen kann und es Ihnen ermöglicht, die XInput-APIs in Ihren HTML-basierten Apps im Metro-Stil zu verwenden. Das Beispiel zum Zeichnen mit dem Controller mithilfe von XInput und JavaScript veranschaulicht genau das. Die Beispiel-App enthält eine in C++ geschriebene Gamecontroller-Komponente für Windows-Runtime, die die von den XInput-APIs zur Verfügung gestellten Funktionen umfasst. Die HTML-basierte Controller-App zum Zeichnen verwendet die Gamecontroller-Komponente in C++ für Windows-Runtime, um die Interaktion mit dem Xbox 360-Controller zu ermöglichen.

Dieses Szenario, das mit HTML und JavaScript allein nicht zu verwirklichen wäre, ist ein perfektes Beispiel dafür, wie Sie durch Erstellung einer Drittanbieterkomponente für Windows-Runtime ein komplexes Szenario verwirklichen können.

Rechenintensive Vorgänge

Apps für Bereiche wie Naturwissenschaft, Ingenieurswesen oder Landkarten/Geografie sind oft sehr rechenintensiv. Diese rechenintensiven Vorgänge erfordern typischer Weise eine leistungsfähige Parallelverarbeitung, und C++ ist besonders geeignet, hier eine optimale Leistung zu erzielen. Bei der Entwicklung des „Bing Maps Trip Optimizer“, einer App im Metro-Stil in JavaScript und C++, sehen Sie ein weiteres Szenario, wie Sie durch die Erstellung einer Windows-Runtime-Komponente in C++ eine größere Benutzerfreundlichkeit für eine App erzielen können.

Warum sollte jemand eine Route auf der Basis lokaler Daten berechnen, wenn er diese rechenintensive Aufgabe ebenso in der Cloud auf Bing-Servern ausführen könnte? Bing Maps stellt für diese Aufgabe JavaScript-APIs zur Verfügung, in manchen Situationen muss eine App jedoch offline ausgeführt werden. Der Benutzer soll zudem in der Lage sein, die Route in Echtzeit und am Touchscreen zu ändern. Wenn wir diese rechenintensiven Aufgaben lokal ausführen können, ist die App wesentlich benutzerfreundlicher.

Wenn rechenintensive Aufgaben in C++ mittels der Parallel-Task-Bibliothek bewältigt werden, kann der Client seine Aufgaben besser für die Benutzer erfüllen. Die Windows-Runtime ist für dieses Szenario gut geeignet, da eine Clientbenutzeroberfläche (UI) mit vielen Funktionen in HTML und JavaScript mit dem AJAX-Steuerelement von Bing Maps erstellt werden kann, während für die rechenintensiven Routenberechnungen C++-Code mit schneller Parallelberechnung zum Einsatz kommt.

Bibliotheken

In der Community werden nützliche Bibliotheken zur Verfügung gestellt, die von Entwicklern zusammengestellt wurden und zur allgemeinen Verwendung bereit stehen. Früher konnte die Wiederverwendung dieser Bibliotheken für Sie zu einer echten Herausforderung werden, wenn sie nicht der Programmiersprache entsprachen, in der Ihre App implementiert wurde. Wenn Sie zum Beispiel eine tolle .NET-App entwickelt hatten, konnten Sie keine in C++ geschriebene Bibliothek verwenden, ohne aufwendige Interop Hoops wie PInvoke zu durchlaufen.

Die Windows-Runtime überbrückt diese Sprachkluft in Windows 8 und ermöglicht es, mit einer einzigen Bibliothek aus Windows-Runtime-Komponenten auf einheitlicher Codebasis ein viel größeres Spektrum von Entwicklern zu erreichen, unabhängig von der Sprache der Komponente und der primären Programmiersprache Ihrer App.

Sie können jetzt eine einzige von der Windows-Runtime verfügbar gemachte XAML-Bibliothek mit benutzerdefinierten Steuerelementen erstellen, die sowohl C++- als auch C#-App-Entwickler verwenden können. Sie können auch diverse Windows-Runtime-Bibliotheken für Datenspeicher in Ihren XAML- oder HTML-basierten Apps im Metro-Stil verwenden, die von anderen Entwicklern zur Verfügung gestellt wurden. All diese Szenarien sind möglich, ohne einen Interop-Code schreiben zu müssen.

Wir sind überzeugt, dass die Windows-Runtime ein Segen für alle Bibliotheken ist, die Entwickler erstellen und mit der Community für Apps im Metro-Stil teilen. Im Folgenden werden zwei konkrete Beispiele mit den Grundlagen für die Erstellung einer Drittanbieterkomponente für Windows-Runtime in C++/CX und C# beschrieben.

Szenario 1: Erweitern Ihrer App mit systemeigenem Audio

Angenommen, Sie möchten eine Softwaresynthesizer-App mithilfe von XAML erstellen, das von einer in C# geschriebenen App-Logik unterstützt wird. Um dieser App eine Filterunterstützung hinzuzufügen, wird XAudio verwendet, das eine direkte Steuerung des Audiopuffers ermöglicht.

Hinzufügen der Windows-Runtime-Komponente zur Beispiellösung

In Visual Studio wird ein neues C++-Projekt mit Windows-Runtime-Komponenten zur vorhandene Lösung hinzugefügt. Die Windows-Runtime-Komponente umfasst die Funktionalität zur Musikverarbeitung:

Verwendung von Visual Studio zum Hinzufügen einer neuen Windows-Runtime-Komponente in C++ zur Musik-App
Abbildung 2: Hinzufügen einer neuen Windows-Runtime-Komponente in C++

Visual Studio hat ein C++-Projekt erstellt, das APIs zur Verfügung stellt, deren Implementierung in einer DLL und den Windows-Runtime-Metadaten in einer WinMD-Datei zu einem Paket zusammengefasst wird. Beide werden für das C#-Projekt verfügbar gemacht.

Definieren der Klasse, die für das XAML-C#-Projekt zur Verfügung gestellt wird

C++/CX wird verwendet, um die APIs zu erstellen, die für das C#-Projekt verfügbar gemacht werden, Sie können jedoch auch die C++-Vorlagenbibliothek für Windows-Runtime (WRL) verwenden. Begonnen wird mit der Definition einer grundlegenden Klasse, die die XAudio-Funktionalität einkapselt:

XAudioWrapper.h

#pragma once

#include "mmreg.h"
#include <vector>
#include <memory>

namespace XAudioWrapper
{
public ref class XAudio2SoundPlayer sealed
{
public:
XAudio2SoundPlayer(uint32 sampleRate);
virtual ~XAudio2SoundPlayer();

void Initialize();

bool PlaySound(size_t index);
bool StopSound(size_t index);
bool IsSoundPlaying(size_t index);
size_t GetSoundCount();

void Suspend();
void Resume();

private:
interface IXAudio2* m_audioEngine;
interface IXAudio2MasteringVoice* m_masteringVoice;
std::vector<std::shared_ptr<ImplData>> m_soundList;
};
}

Sie werden zunächst die Verwendung der Schlüsselwörter public, ref und sealed in den Klassendeklarationen bemerken. Damit in einer App im Metro-Stil für eine Klasse aus einer anderen Sprache wie etwa JavaScript oder C# heraus eine gesonderte Instanz erstellt werden kann, muss diese Klasse als „public ref class sealed“ deklariert werden.

Die öffentlichen Funktionen (Methoden, Eigenschaften …) der Klasse sind beschränkt auf die integrierten C++-Typen oder Windows-Runtime-Typen. Das sind die einzigen Typen, für die das Überschreiten der Sprachgrenze innerhalb von Windows-Runtime-Komponenten zulässig ist. Daher können Sie die reguläre C++-Bibliothek (z. B.: Bibliotheken mit Sammlungen von Standardvorlagen) für die privaten Datenmember Ihrer Klasse verwenden, so wie es in diesem Codeausschnitt gezeigt wird. Diese privaten Datenmember müssen nicht den Regeln im Zusammenhang mit der Überschreitung der Sprachgrenze entsprechen. Der Visual Studio-Compiler gibt Fehlermeldungen aus und bietet Hilfestellung an, falls Sie nicht unterstützte Konstrukte verwenden sollten.

Implementieren der Klasse, die in der Windows-Runtime-Komponente verfügbar gemacht wird

Nachdem die grundlegende Schnittstelle der Klasse definiert ist, werden jetzt einige der implementierten Methoden betrachtet:

XAudioWrapper.cpp

XAudio2SoundPlayer::XAudio2SoundPlayer(uint32 sampleRate) :
m_soundList()
{
// Create the XAudio2 engine
UINT32 flags = 0;

XAudio2Create(&m_audioEngine, flags);

// Create the mastering voice
m_audioEngine->CreateMasteringVoice(
&m_masteringVoice,
XAUDIO2_DEFAULT_CHANNELS,
sampleRate
);
}

void XAudio2SoundPlayer::Resume()
{
m_audioEngine->StartEngine();
}

bool XAudio2SoundPlayer::PlaySound(size_t index)
{
//
// Setup buffer
//
XAUDIO2_BUFFER playBuffer = { 0 };
std::shared_ptr<ImplData> soundData = m_soundList[index];
playBuffer.AudioBytes = soundData->playData->Length;
playBuffer.pAudioData = soundData->playData->Data;
playBuffer.Flags = XAUDIO2_END_OF_STREAM;

HRESULT hr = soundData->sourceVoice->Stop();
if (SUCCEEDED(hr))
{
hr = soundData->sourceVoice->FlushSourceBuffers();
}

//
// Submit the sound buffer and (re)start (ignore any 'stop' failures)
//
hr = soundData->sourceVoice->SubmitSourceBuffer(&playBuffer);
if (SUCCEEDED(hr))
{
hr = soundData->sourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
}

return SUCCEEDED(hr);
}

In diesem Codeausschnitt werden die XAudio2-COM-APIs verwendet, die für die Entwicklung von Apps im Metro-Stil bereit stehen, um das Audio-Modul einzusetzen, Töne wiederzugeben und das Modul fortzusetzen. Zusätzlich ist es möglich C++-Konstrukte und -Typen über die Windows-Runtime-Typen hinaus zu verwenden, um die erforderliche Funktionalität zu implementieren.

Hinzufügen und Verwenden der Windows-Runtime-Komponente

Nach der Definition und Implementierung der grundlegenden Klasse wird Visual Studio verwendet, um aus dem C#-Projekt die XAudioWrapper-Komponente für Windows-Runtime zum C++-Projekt hinzuzufügen:

Verwendung von Visual Studio zum Hinzufügen eines Verweises auf die XAudioWrapper-Komponente für Windows-Runtime im Musik-App-Beispiel

Abbildung 3: Hinzufügen der XAudioWrapper-Komponente für Windows-Runtime zum Musik-App-Beispiel

Als Ergebnis wird die Klasse, die das C++-Projekt zur Verfügung stellt, für das C#-Projekt verfügbar gemacht:

MainPage.cs

using XAudioWrapper;

namespace BasicSoundApp
{
public sealed partial class MainPage : Page
{
XAudio2SoundPlayer _audioPlayer = new XAudio2SoundPlayer(48000);
public MainPage()
{
this.InitializeComponent();
}

protected override void OnNavigatedTo(NavigationEventArgs e)
{
_audioPlayer.Initialize();
}

private void Button_Click_1(object sender, RoutedEventArgs e)
{
_audioPlayer.PlaySound(0);
}
}
}

Wie der Codeausschnitt zeigt, kann aus C# heraus mit dem XAudio-Wrapper interagiert werden, gerade so, als ob er eine reguläre .NET-Komponente wäre. Ein Verweis auf seinen Namespace wird erstellt, die Komponente wird instanziiert und die diversen Methoden, die sie zur Verfügung stellt, können aufgerufen werden. Und das alles, ohne dass DllImports zum Aufrufen des systemeigenen Codes erforderlich sind.

Szenario 2: Verwenden integrierter APIs zum Öffnen einer ZIP-Datei aus Ihrer App heraus

Angenommen, Sie möchten auch eine Dateianzeige-App mit HTML erstellen und den Benutzern dieser App die Möglichkeit geben, auch ZIP-Dateien anzuzeigen. Die in Windows integrierten APIs, die in der .NET-Plattform verfügbar gemacht werden, sollen für die Verarbeitung von ZIP-Dateien verwendet werden.

Hinzufügen der Windows-Runtime-Komponente zur Beispiellösung

Hierbei handelt es sich um denselben Vorgang wie er für die Musik-App beschrieben wurde, in diesem Fall wird jedoch die Funktionalität zur ZIP-Bearbeitung mit der C#-Komponente für Windows-Runtime umschlossen:

Verwendung von Visual Studio zum Hinzufügen einer neuen C#-Komponente für Windows-Runtime zur Dateiansicht-App
Abbildung 4: Hinzufügen einer neuen C#-Komponente für Windows-Runtime 

Visual Studio hat ein C#-Projekt erzeugt, um die APIs zur Verfügung zu stellen, für die sowohl die Implementierung als auch die Windows-Runtime-Metadaten in einer WinMD-Datei zu einem Paket zusammengefasst werden und für das Webprojekt zur Verfügung gestellt werden.

Implementieren der Klasse, die in der Windows-Runtime-Komponente verfügbar gemacht wird

Wir verwenden C# zum Erstellen der APIs, die für das Webprojekt verfügbar gemacht werden, es kann jedoch auch Visual Basic dazu verwendet werden. Zuerst wird eine grundlegende C#-Klasse definiert, die die ZIP-Funktionalität einkapselt:

ZipWrapper.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage;

public sealed class ZipWrapper
{
public static IAsyncOperationWithProgress<IList<string>, double> EnumerateZipFileAsync(StorageFile file)
{
return AsyncInfo.Run(async delegate(
System.Threading.CancellationToken cancellationToken, IProgress<double> progress)
{
IList<string> fileList = new List<string>();
progress.Report(0);

using (var stream = await file.OpenStreamForReadAsync())
{
using (var archive = new ZipArchive(stream))
{
for (int i = 0; i < archive.Entries.Count; i++)
{
// add code for processing/analysis on the file
// content here

// add to our list and report progress
fileList.Add(archive.Entries[i].FullName);
double progressUpdate = ((i + 1) / ((double)archive.Entries.Count)) * 100; // percentage
progress.Report(progressUpdate);
}
}
}

progress.Report(100.0);
return fileList;
});
}
}

Diese Klasse ist „public“ und „sealed“. Wie beim Erstellen von C++-Komponenten für Windows-Runtime ist dies erforderlich, damit andere Sprachen diese Klasse instanziieren können. Die in der Klasse verfügbar gemachten statischen Methoden verwenden eine Mischung aus Windows-Runtime-Typen (wie etwa StorageFile) und .NET-Typen (wie etwa IList) in der Methodensignatur. Es sollten Windows-Runtime-Typen zur Definition der öffentlichen Felder, der Parameter und Rückgabetypen verwendet werden, die den anderen Sprachen verfügbar gemacht werden. Dann können Sie bestimmte grundlegende .NET-Typen (z. B. DateTimeOffset und Uri) und Primitive (z. B. IList) verwenden.

Sie werden auch bemerken, dass die obige Methode die Windows-Runtime-Infrastruktur für async- und progress-Unterstützung nutzt, die Sie zur Definition Ihrer Windows-Runtime-Komponenten verwenden können (und sollten). Was die Implementierung der Windows-Runtime-Komponente oder jeder privaten Funktionalität in der Klasse betrifft, sind Sie nicht auf die alleinige Verwendung von Windows-Runtime-Typen und -APIs beschränkt. Es steht Ihnen frei, jede der .NET API-Oberflächen, die für die Entwicklung von Apps im Metro-Stil verfügbar gemacht werden, zu verwenden, wie der Codeausschnitt der ZipArchive-APIs zeigt.

Hinzufügen und Verwenden der Windows-Runtime-Komponente

Nach der Implementierung des ZIP-Tool-Wrappers wird Visual Studio verwendet, um dem C#-Projekt einen Verweis aus dem JavaScript-Projekt hinzuzufügen:

Verwendung von Visual Studio zum Hinzufügen eines Verweises auf die ZipUtil-Komponente für die Windows-Runtime in der Dateiansicht-App
Abbildung 5: Hinzufügen der ZipUtil-Komponente für die Windows-Runtime zur Dateiansicht-App

Als Ergebnis wird die Klasse, die das C#-Projekt zur Verfügung gestellt hat, für das Webprojekt zugänglich gemacht:

program.js

function pickSinglePhoto() {

// Create the picker object for picking zip files
var openPicker = new Windows.Storage.Pickers.FileOpenPicker();
openPicker.viewMode = Windows.Storage.Pickers.PickerViewMode.thumbnail;
openPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
openPicker.fileTypeFilter.replaceAll([".zip"]);

// Open the picker for the user to pick a file
openPicker.pickSingleFileAsync().then(function (file) {
if (file) {
ZipUtil.ZipWrapper.enumerateZipFileAsync(file).then(
function (fileList) {
for (var i = 0; i < fileList.length; i++)
document.getElementById('output').innerHTML += " " + fileList[i];
},
function (prog) {
document.getElementById('zipProgress').value = prog;
}
);
} else {
document.getElementById('output').innerHTML = "an error occurred";
}
});
};

Wie Sie sehen, ist es möglich, mit dem ZIP-Tool-Wrapper aus JavaScript heraus zu interagieren, gerade so als ob es sich um ein reguläres JavaScript-Objekt handelte. Es ist möglich, die statische Methode aufzurufen, die in unserer Windows-Runtime-Komponente verfügbar gemacht wird, und es ist dabei möglich, async-Sprachkonstrukte von JavaScript zu verwenden, beispielsweise .then().

Allgemeine Richtlinien

Nicht jede API, die Sie für Ihre App im Metro-Stil schreiben, sollte als Drittanbieterkomponente für Windows-Runtime verfügbar gemacht werden. Generell wird empfohlen, Windows-Runtime-Typen zu verwenden, wenn Sie zwischen Programmiersprachen wechseln und dabei in die Sprache integrierte Typen und Konstrukte für eine Funktionalität nutzen, die nicht durch eine Windows-Runtime-Komponente öffentlich verfügbar gemacht wird. Zusätzlich sollten Sie diverse sprachspezifische Features und Regeln im Zusammenhang mit der Überschreitung der Sprachgrenze in Ihre Überlegungen einbeziehen, wenn Sie eine Windows-Runtime-Komponente erstellen. Dazu gehören Delegate und Ereignisse, asynchrone Operationen, Methodenüberladung, die Verwendung spezifischer Datentypen (wie etwa Sammlungen), die Behandlung von Ausnahmen und Tipps für das Debuggen. Sie können diese Themen noch weiter vertiefen, indem Sie den Abschnitt Erstellen von Windows-Runtime-Komponenten für Ihre jeweilige Entwicklersprache aufrufen.

Zusammenfassung

Mit Windows-Runtime-Komponenten können Sie jetzt Programmiersprachen und API-Technologien kombinieren, um damit Apps nach Ihren Vorstellungen zu erstellen. Windows 8 macht keine Kompromisse. Selbst während der Entwicklung gibt es keine Kompromisse, sodass Sie die Programmiersprachen kombinieren und anpassen können, die am besten zu Ihrem Szenario passen. Dadurch haben Sie wahrscheinlich mehr Zeit für die Entwicklung von Innovationen und verschwenden weniger Zeit auf das Erlernen neuer Programmiersprachen.

Viel Spaß bei der App-Entwicklung!

– Ines Khelifi, Programmmanager, Windows

Referenzen

In diesem Beitrag wurde gerade einmal an der Oberfläche dessen gekratzt, was mit der Erstellung von Windows-Runtime-Komponenten möglich ist. Hier einige zusätzliche Quellen zu Vertiefung Ihrer Kenntnisse bei der App-Entwicklung:

  • Loading...
Leave a Comment
  • Please add 8 and 7 and type the answer here:
  • Post