Chromium ist nicht nur ein Browser, sondern auch ein gutes Framework



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:

 #include "base/command_line.h" 

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 /* http2 */, false /* quic */); 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".

Source: https://habr.com/ru/post/de455956/


All Articles