
Die meisten Menschen sind daran gewöhnt, dass Chromium sowohl ein Browser als auch die Basis für andere Browser ist. Bis vor kurzem habe ich das auch gedacht, aber als ich dieses Thema ein paar Monate lang studierte, begann ich, eine andere wundervolle Welt zu entdecken. Chrom ist ein riesiges Ökosystem, in dem es alles gibt: ein Abhängigkeitssystem, ein plattformübergreifendes Build-System und Komponenten für fast alle Gelegenheiten. Warum also nicht versuchen, mit all dieser Kraft eigene Anwendungen zu erstellen?
Unter kat eine kleine Anleitung, wie man damit anfängt.
Umweltvorbereitung
In dem Artikel, in dem ich Ubuntu 18.04 verwenden werde, finden Sie die Vorgehensweise für andere Betriebssysteme in der Dokumentation:
Die folgenden Schritte erfordern Git und Python. Wenn sie nicht installiert sind, müssen sie mit dem folgenden Befehl installiert werden:
sudo apt install git python
Depot_tools einstellen
depot_tools
ist ein Chromium-Entwicklungs-Toolkit. Um es zu installieren, müssen Sie Folgendes ausführen:
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
Fügen Sie den Pfad zur Umgebungsvariablen PATH hinzu:
export PATH="$PATH:/path/to/depot_tools"
Wichtig: Wenn
depot_tools
in Ihren Home-Ordner heruntergeladen wurde, verwenden Sie
~
in der
PATH
Variablen, da sonst Probleme auftreten können. Sie müssen die Variable
$HOME
:
export PATH="$PATH:${HOME}/depot_tools"
Code abrufen
Zuerst müssen Sie einen Ordner für die Quelle erstellen. Zum Beispiel im Home-Verzeichnis (ca. 30 GB freier Speicherplatz werden benötigt):
mkdir ~/chromium && cd ~/chromium
Danach können Sie die Quellen mit dem Dienstprogramm
fetch
von
depot_tools
:
fetch --nohooks --no-history chromium
Jetzt können Sie Tee / Kaffee trinken, da der Vorgang nicht schnell ist. Für Experimente wird kein Verlauf benötigt, daher wird das Flag
--no-history
verwendet. Die Geschichte wird noch länger sein.
Abhängigkeitsinstallation
Alle Quellen befinden sich im Ordner
src
. Gehen Sie dazu:
cd src
Jetzt müssen Sie alle Abhängigkeiten mithilfe des Skripts einfügen:
./build/install-build-deps.sh
Und lassen Sie die Haken laufen:
gclient runhooks
Damit ist die Vorbereitung der Umgebung abgeschlossen.
System erstellen
Ninja wird als
.ninja
für Chromium verwendet, und das
GN- Dienstprogramm wird zum Generieren von
.ninja
Dateien verwendet.
Um zu verstehen, wie diese Tools verwendet werden, schlage ich vor, ein Beispiel für ein Testdienstprogramm zu erstellen. Erstellen Sie dazu einen
example
im Ordner
src
:
mkdir example
Erstellen Sie dann im Ordner
src/example
die Datei
BUILD.gn
, die
BUILD.gn
enthält:
executable("example") { sources = [ "example.cc", ] }
BUILD.gn
besteht aus einem Ziel (
example
ausführbaren Datei) und einer Liste von Dateien, die zum Erstellen des Ziels benötigt werden.
Der nächste Schritt besteht darin, die Datei
example.cc
selbst zu erstellen. Zunächst schlage ich vor, eine klassische Anwendung "Hallo Welt" zu erstellen:
#include <iostream> int main(int argc, char **argv) { std::cout << "Hello world" << std::endl; return 0; }
Der Quellcode ist auf
GitHub zu finden.
Damit GN mehr über das neue Projekt
BUILD.gn
kann, fügen Sie in der Datei
BUILD.gn
in
src
die Zeile
"//example"
im Abschnitt
deps
:
... group("gn_all") { testonly = true deps = [ ":gn_visibility", "//base:base_perftests", "//base:base_unittests", "//base/util:base_util_unittests", "//chrome/installer", "//chrome/updater", "//net:net_unittests", "//services:services_unittests", "//services/service_manager/public/cpp", "//skia:skia_unittests", "//sql:sql_unittests", "//third_party/flatbuffers:flatbuffers_unittests", "//tools/binary_size:binary_size_trybot_py", "//tools/ipc_fuzzer:ipc_fuzzer_all", "//tools/metrics:metrics_metadata", "//ui/base:ui_base_unittests", "//ui/gfx:gfx_unittests", "//url:url_unittests", # ↓↓↓↓↓↓↓↓ "//example", ] ...
Jetzt müssen Sie zum Ordner
src
und das Projekt mit dem folgenden Befehl generieren:
gn gen out/Default
Mit GN können Sie auch ein Projekt für eine der unterstützten IDEs vorbereiten:
- Sonnenfinsternis
- vs.
- vs2013
- vs2015
- vs2017
- vs2019
- xcode
- qtcreator
- json
Weitere Informationen erhalten Sie mit dem Befehl:
gn help gen
Um beispielsweise mit dem
example
in
QtCreator zu arbeiten, müssen Sie den folgenden Befehl ausführen:
gn gen --ide=qtcreator --root-target=example out/Default
Danach können Sie das Projekt in QtCreator öffnen:
qtcreator out/Default/qtcreator_project/all.creator
Der letzte Schritt besteht darin, das Projekt mit Ninja zu erstellen:
autoninja -C out/Default example
Diese kurze Einführung in das Montagesystem kann abgeschlossen werden.
Die Anwendung kann mit dem folgenden Befehl gestartet werden:
./out/Default/example
Und siehe Hallo Welt. In der Tat können Sie einen separaten Artikel über das Montagesystem in Chromium schreiben. Vielleicht nicht einer.
Arbeiten Sie mit der Befehlszeile
Als erstes Beispiel für die Verwendung der Chromium-Codebasis als Framework empfehle ich, mit der Befehlszeile herumzuspielen.
Aufgabe:
Alle an die Anwendung übergebenen Argumente im Chromium-Stil anzeigen.Um mit der Befehlszeile arbeiten zu können, müssen Sie die Header-Datei in example.cc einfügen:
Außerdem dürfen wir nicht vergessen, in
BUILD.gn
eine Abhängigkeit vom
BUILD.gn
.
BUILD.gn
sollte folgendermaßen aussehen:
executable("example") { sources = [ "example.cc", ] deps = [ "//base", ] }
Jetzt wird alles, was Sie brauchen, mit dem
example
.
Um mit der Befehlszeile zu arbeiten, stellt Chromium eine Singleton-
base::CommandLine
bereit
base::CommandLine
. Um einen Link dazu zu erhalten, müssen Sie die statische Methode
base::CommandLine::ForCurrentProcess
. Zuerst müssen Sie sie jedoch mit der
base::CommandLine::Init
Methode initialisieren:
base::CommandLine::Init(argc, argv); auto *cmd_line = base::CommandLine::ForCurrentProcess();
Alle Argumente, die in der Befehlszeile an die Anwendung übergeben werden und mit einem
-
mit der
GetSwitches
Methode als
base::SwitchMap
(im Wesentlichen
map<string, string>
)
GetSwitches
. Alle anderen Argumente werden als
base::StringVector
(im Wesentlichen
vectr<strig>
). Dieses Wissen reicht aus, um den Code für die Aufgabe zu implementieren:
for (const auto &sw : cmd_line->GetSwitches()) { std::cout << "Switch " << sw.first << ": " << sw.second << std::endl; } for (const auto &arg: cmd_line->GetArgs()) { std::cout << "Arg " << arg << std::endl; }
Die Vollversion finden Sie auf
GitHub .
Um die Anwendung zu erstellen und auszuführen, müssen Sie Folgendes ausführen:
autoninja -C out/Default example ./out/Default/example arg1 --sw1=val1 --sw2 arg2
Der Bildschirm zeigt Folgendes an:
Switch sw1: val1 Switch sw2: Arg arg1 Arg arg2
Vernetzung
Als zweites und letztes Beispiel für heute schlage ich vor, mit dem Netzwerkteil von Chromium zu arbeiten.
Aufgabe:
Zeigen Sie den Inhalt der als Argument übergebenen URL an .
Chromium Network Subsystem
Das Netzwerksubsystem ist ziemlich groß und komplex. Der Einstiegspunkt für Anforderungen an HTTP, HTTPS, FTP und andere
URLRequest
ist
URLRequest
, mit dem bereits festgelegt wird, welcher Client verwendet werden soll. Ein vereinfachtes Diagramm sieht folgendermaßen aus:
Die Vollversion finden Sie in der Dokumentation .Um eine
URLRequest
zu erstellen
URLRequest
müssen Sie einen
URLRequestContext
. Das Erstellen eines Kontexts ist eine ziemlich komplizierte Operation, daher wird empfohlen,
URLRequestContextBuilder
zu verwenden. Es werden alle erforderlichen Variablen mit Standardwerten initialisiert, aber falls gewünscht, können sie in ihre eigenen geändert werden, zum Beispiel:
net::URLRequestContextBuilder context_builder; context_builder.DisableHttpCache(); context_builder.SetSpdyAndQuicEnabled(true , false ); context_builder.SetCookieStore(nullptr);
Multithreading
Der Chromium-Netzwerkstapel ist für die Verwendung in einer Umgebung mit mehreren Threads ausgelegt, sodass Sie dieses Thema nicht überspringen können. Die grundlegenden Objekte für die Arbeit mit Multithreading in Chromium sind:
- Aufgabe - Eine auszuführende Aufgabe. In Chromium handelt es sich um eine Funktion vom Typ
base::Callback
, die mit base::Bind
erstellt werden base::Bind
.
- Aufgabenwarteschlange - Die auszuführende Aufgabenwarteschlange.
- Physischer Thread - Ein plattformübergreifender Wrapper über den Betriebssystem-Thread (
pthread
unter POSIX oder CreateThread()
unter Windows). In der base::PlatformThread
Klasse implementiert, nicht direkt verwenden.
- base :: Thread - ein echter Thread, der Nachrichten aus einer dedizierten Task-Warteschlange endlos verarbeitet; Es wird nicht empfohlen, sie direkt zu erstellen.
- Thread-Pool - Thread-Pool mit einer allgemeinen Task-Warteschlange. Implementiert in der
base::ThreadPool
Klasse. Erstellen Sie in der Regel eine Instanz. Aufgaben werden mit Funktionen aus base/task/post_task.h
an sie base/task/post_task.h
.
- Sequenz oder virtueller Thread - Ein virtueller Thread, der reale Threads verwendet und zwischen diesen wechseln kann.
- Task Runner - eine Schnittstelle zum Festlegen von Aufgaben, die in der
base::TaskRunner
.
- Sequenced Task Runner - eine Schnittstelle zum Festlegen von Aufgaben, die sicherstellt, dass Aufgaben in derselben Reihenfolge ausgeführt werden, in der sie angekommen sind. Implementiert in der
base::SequencedTaskRunner
Klasse.
- Single-Thread-Task-Runner - ähnlich dem vorherigen, garantiert jedoch, dass alle Tasks in einem OS-Thread ausgeführt werden. Implementiert in der
base::SingleThreadTaskRunner
.
Implementierung
Für einige Chromium-Komponenten ist
base::AtExitManager
erforderlich.
base::AtExitManager
dieser Klasse können Sie Vorgänge registrieren, die beim Beenden der Anwendung ausgeführt werden müssen. Die Verwendung ist sehr einfach. Sie müssen ein Objekt auf dem Stapel erstellen:
base::AtExitManager exit_manager;
Wenn
exit_manager
Gültigkeitsbereich
exit_manager
, werden alle registrierten Rückrufe ausgeführt.
Jetzt müssen Sie sich um die Verfügbarkeit aller erforderlichen Multithreading-Komponenten für das Netzwerksubsystem kümmern. Erstellen Sie dazu einen
Thread pool
, eine
Message loop
Typ
TYPE_IO
zum Verarbeiten von Netzwerknachrichten und eine
Run loop
- die Hauptprogrammschleife:
base::ThreadPool::CreateAndStartWithDefaultParams("downloader"); base::MessageLoop msg_loop(base::MessageLoop::TYPE_IO); base::RunLoop run_loop;
Verwenden Sie als Nächstes den
Context builder
, um einen
Context
zu erstellen:
auto ctx = net::URLRequestContextBuilder().Build();
Um eine Anfrage zu senden, müssen Sie ein
URLRequest
Objekt mit der
CreateRequest
Methode des
ctx
Objekts erstellen. Folgende Parameter werden übergeben:
- URL, Zeichenfolge vom Typ GURL;
- Priorität;
- Delegat, der Ereignisse behandelt.
Ein Delegat ist eine Klasse, die die Schnittstelle
net::URLRequest::Delegate
implementiert. Für diese Aufgabe kann es so aussehen:
class MyDelegate : public net::URLRequest::Delegate { public: explicit MyDelegate(base::Closure quit_closure) : quit_closure_(std::move(quit_closure)), buf_(base::MakeRefCounted<net::IOBuffer>(BUF_SZ)) {} void OnReceivedRedirect(net::URLRequest *request, const net::RedirectInfo &redirect_info, bool *defer_redirect) override { std::cerr << "redirect to " << redirect_info.new_url << std::endl; } void OnAuthRequired(net::URLRequest* request, const net::AuthChallengeInfo& auth_info) override { std::cerr << "auth req" << std::endl; } void OnCertificateRequested(net::URLRequest *request, net::SSLCertRequestInfo *cert_request_info) override { std::cerr << "cert req" << std::endl; } void OnSSLCertificateError(net::URLRequest* request, int net_error, const net::SSLInfo& ssl_info, bool fatal) override { std::cerr << "cert err" << std::endl; } void OnResponseStarted(net::URLRequest *request, int net_error) override { std::cerr << "resp started" << std::endl; while (true) { auto n = request->Read(buf_.get(), BUF_SZ); std::cerr << "resp read " << n << std::endl; if (n == net::ERR_IO_PENDING) return; if (n <= 0) { OnReadCompleted(request, n); return; } std::cout << std::string(buf_->data(), n) << std::endl; } } void OnReadCompleted(net::URLRequest *request, int bytes_read) override { std::cerr << "completed" << std::endl; quit_closure_.Run(); } private: base::Closure quit_closure_; scoped_refptr<net::IOBuffer> buf_; };
Die gesamte
OnResponseStarted
befindet sich im
OnResponseStarted
Ereignishandler: Der Inhalt der Antwort wird subtrahiert, bis ein Fehler auftritt oder nichts mehr zu lesen ist. Da der Delegat nach dem Lesen der Antwort zum Abschließen der Anwendung Zugriff auf die Funktion haben muss, die die Hauptlaufschleife unterbricht, wird in diesem Fall ein Rückruf vom Typ
base::Closure
verwendet.
Jetzt ist alles bereit, um die Anfrage zu senden:
MyDelegate delegate(run_loop.QuitClosure()); auto req = ctx->CreateRequest(GURL(args[0]), net::RequestPriority::DEFAULT_PRIORITY, &delegate); req->Start();
Damit die Anforderung die Verarbeitung starten kann, müssen Sie die
Run loop
ausführen:
run_loop.Run();
Die Vollversion finden Sie auf
GitHub .
Um die Anwendung zu erstellen und auszuführen, müssen Sie Folgendes ausführen:
autoninja -C out/Default example out/Default/example "https://example.com/"
Finale
In Chromium finden Sie viele nützliche Würfel und Bausteine, aus denen Sie Anwendungen erstellen können. Es entwickelt sich ständig weiter, was einerseits ein Plus ist, und andererseits lassen regelmäßige Änderungen an der API Sie nicht entspannen. In der neuesten Version wurde
base::TaskScheduler
base::ThreadPool
glücklicherweise zu
base::ThreadPool
, ohne die API zu ändern.
PS Wir suchen einen führenden C ++ - Programmierer in unserem Team! Wenn Sie die Stärke in sich spüren, werden unsere Wünsche hier beschrieben:
team.mail.ru/vacancy/4641/ . Es gibt auch eine Schaltfläche "Antworten".