Chromium不仅是浏览器,而且还是一个很好的框架



大多数人已经习惯了Chromium既是浏览器又是其他浏览器的基础的事实。 直到最近,我也这样认为,但是,研究了这个主题几个月之后,我开始发现另一个奇妙的世界。 铬是一个巨大的生态系统,其中包含所有内容:依赖系统,跨平台构建系统以及几乎所有情况下的组件。 那么,为什么不尝试使用所有这些功能来创建自己的应用程序呢?

在Kat之下,有一个有关如何开始执行此操作的小指南。

环境准备


在我将使用Ubuntu 18.04的文章中,可以在文档中找到其他操作系统的过程:


以下步骤需要Git和Python。 如果尚未安装,则必须使用以下命令安装它们:

sudo apt install git python 

设置depot_tools


depot_tools是Chromium开发工具箱。 要安装它,您必须执行以下操作:

 git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 

并将路径添加到PATH环境变量中:

 export PATH="$PATH:/path/to/depot_tools" 

重要:如果将depot_tools下载到您的主文件夹中,请不要在PATH变量中使用~ ,否则可能会出现问题。 您必须使用$HOME变量:

 export PATH="$PATH:${HOME}/depot_tools" 

代码检索


首先,您需要为源创建一个文件夹。 例如,在主目录中(大约需要30 GB的可用空间):

 mkdir ~/chromium && cd ~/chromium 

之后,您可以使用来自depot_toolsfetch实用程序下载源:

 fetch --nohooks --no-history chromium 

现在,您可以去喝茶/咖啡,因为操作过程并不很快。 对于实验,不需要历史记录,因此使用--no-history标志。 故事会更长。

依赖安装


所有源都在src文件夹中,请转到它:

 cd src 

现在,您需要使用脚本放置所有依赖项:

 ./build/install-build-deps.sh 

并运行钩子:

 gclient runhooks 

这样就完成了环境的准备。

建立系统


Ninja被用作Chromium的主要装配系统, GN实用程序用于生成.ninja文件。

为了了解如何使用这些工具,我建议创建一个测试实用程序示例。 为此,请在src文件夹中创建一个example子文件夹:

 mkdir example 

然后,在src/example文件夹中,创建BUILD.gn文件,其中包含:

 executable("example") { sources = [ "example.cc", ] } 

BUILD.gn由目标(可执行文件example )和构建目标所需的文件列表组成。

下一步是创建example.cc文件本身。 首先,我建议制作一个经典的应用程序“ Hello world”:

 #include <iostream> int main(int argc, char **argv) { std::cout << "Hello world" << std::endl; return 0; } 

源代码可以在GitHub找到

为了使GN了解新项目,请在src中的BUILD.gn文件中,在deps部分中添加"//example"行:

 ... 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", ] ... 

现在,您需要返回到src文件夹并使用以下命令生成项目:

 gn gen out/Default 

GN还允许您为受支持的IDE之一准备项目:



  • vs2013
  • vs2015
  • vs2017
  • vs2019
  • Xcode的
  • qtcreator
  • json

可以使用以下命令获取更多信息:

 gn help gen 

例如,要在QtCreator中使用example项目您需要运行以下命令:

 gn gen --ide=qtcreator --root-target=example out/Default 

之后,您可以在QtCreator中打开项目:

 qtcreator out/Default/qtcreator_project/all.creator 

最后一步是使用Ninja构建项目:

 autoninja -C out/Default example 

组装系统的简要介绍可以完成。

可以使用以下命令启动该应用程序:

 ./out/Default/example 

并查看Hello world。 实际上,您可以撰写有关Chromium中装配系统的另一篇文章。 也许不是一个。

使用命令行


作为使用Chromium代码库作为框架的第一个示例,我建议使用命令行。

任务: 以Chromium样式显示传递给应用程序的所有参数。
要使用命令行,需要在example.cc中包含头文件:

 #include "base/command_line.h" 

同样,我们也不要忘记在BUILD.gn添加对base项目的BUILD.gnBUILD.gn应该看起来像这样:

 executable("example") { sources = [ "example.cc", ] deps = [ "//base", ] } 

现在,您需要的所有内容都将连接到example

为了使用命令行,Chromium提供了一个单例base::CommandLine 。 要获得链接,您需要使用静态方法base::CommandLine::ForCurrentProcess ,但是首先您需要使用base::CommandLine::Init方法对其进行base::CommandLine::Init

 base::CommandLine::Init(argc, argv); auto *cmd_line = base::CommandLine::ForCurrentProcess(); 

使用GetSwitches方法将在命令行GetSwitches应用程序并以-开头的所有参数作为base::SwitchMap (本质map<string, string>map<string, string>GetSwitches 。 所有其他参数都以base::StringVector (本质上是vectr<strig> )返回。 这些知识足以实现该任务的代码:

 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; } 

完整版本可以在GitHub找到

要构建和运行该应用程序,您需要运行:

 autoninja -C out/Default example ./out/Default/example arg1 --sw1=val1 --sw2 arg2 

屏幕将显示:

 Switch sw1: val1 Switch sw2: Arg arg1 Arg arg2 

联网


作为今天的第二个也是最后一个示例,我建议使用Chromium的网络部分。

任务: 显示作为参数传递的URL的内容

铬网络子系统


网络子系统非常大且复杂。 对HTTP,HTTPS,FTP和其他数据资源的请求的入口点是URLRequest ,它已经确定了要使用哪个客户端。 简化图如下所示:



完整版本可以在文档中找到。

要创建URLRequest必须使用URLRequestContext 。 创建上下文是一个相当复杂的操作,因此建议使用URLRequestContextBuilder 。 它将使用默认值初始化所有必需的变量,但是,如果需要,可以将它们更改为自己的变量,例如:

 net::URLRequestContextBuilder context_builder; context_builder.DisableHttpCache(); context_builder.SetSpdyAndQuicEnabled(true /* http2 */, false /* quic */); context_builder.SetCookieStore(nullptr); 

多线程


Chromium网络堆栈旨在在多线程环境中工作,因此您不能跳过本主题。 在Chromium中使用多线程的基本对象是:

  • Task-要执行的任务,在Chromium中是base::Callback类型的函数,可以使用base::Bind创建。
  • 任务队列-要执行的任务队列。
  • 物理线程-操作系统线程(POSIX上的pthread或Windows上的CreateThread()上的跨平台包装器。 在base::PlatformThread类中实现,请勿直接使用。
  • base :: Thread-一个真正的线程,它不断地处理来自专用任务队列的消息; 不建议直接创建它们。
  • 线程池-具有公共任务队列的线程池。 在base::ThreadPool类中实现。 通常,创建一个实例。 使用base/task/post_task.h函数将任务发送给它。
  • 序列或虚拟线程-使用实际线程并可以在它们之间切换的虚拟线程。
  • 任务运行器-用于设置任务的接口,在base::TaskRunner
  • 顺序任务运行程序-用于设置任务的界面,可确保任务按照到达时的顺序执行。 在base::SequencedTaskRunner类中实现。
  • 单线程任务运行程序-与上一个任务运行程序相似,但保证所有任务将在一个OS线程中执行。 在base::SingleThreadTaskRunner

实作


某些Chromium组件要求存在base::AtExitManager这是一个类,允许您注册在应用程序终止时必须执行的操作。 使用它非常简单,您需要在堆栈上创建一个对象:

 base::AtExitManager exit_manager; 

exit_manager超出范围时,将执行所有已注册的回调。

现在,您需要注意网络子系统所有必需的多线程组件的可用性。 为此,创建一个Thread pool ,类型为TYPE_IO Message loop (用于处理网络消息)和Run loop (主程序循环):

 base::ThreadPool::CreateAndStartWithDefaultParams("downloader"); base::MessageLoop msg_loop(base::MessageLoop::TYPE_IO); base::RunLoop run_loop; 

接下来,使用Context builder创建一个Context

 auto ctx = net::URLRequestContextBuilder().Build(); 

要发送请求,必须使用ctx对象的CreateRequest方法创建URLRequest对象。 传递了以下参数:

  • URL,类型为GURL的字符串;
  • 优先权
  • 处理事件的委托。

委托是实现net::URLRequest::Delegate接口的类。 对于此任务,它可能看起来像这样:

 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_; }; 

所有主要逻辑都在OnResponseStarted事件OnResponseStarted :减去响应的内容,直到发生错误或没有要读取的内容为止。 由于读取响应后,您需要完成应用程序,因此委托人必须有权访问将中断主Run loop的函数,在这种情况下,将使用base::Closure类型的回调。

现在一切就绪,可以发送请求了:

 MyDelegate delegate(run_loop.QuitClosure()); auto req = ctx->CreateRequest(GURL(args[0]), net::RequestPriority::DEFAULT_PRIORITY, &delegate); req->Start(); 

为了开始处理请求,您需要运行Run loop

 run_loop.Run(); 

完整版本可以在GitHub找到

要构建和运行该应用程序,您需要运行:

 autoninja -C out/Default example out/Default/example "https://example.com/" 

决赛


实际上,在Chromium中,您可以找到许多有用的多维数据集和积木,您可以从中构建应用程序。 它在不断发展,一方面是一个优点,另一方面,对API进行定期更改不会让您放松。 例如,在最新版本中,幸运的是将base::TaskScheduler更改为base::ThreadPool ,而无需更改API。

PS我们正在寻找我们团队中领先的C ++程序员! 如果您感到自己有力量,那么我们在这里描述了我们的愿望: team.mail.ru/vacancy/4641/ 。 还有一个“响应”按钮。

Source: https://habr.com/ru/post/zh-CN455956/


All Articles