
大多数人已经习惯了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_tools
的
fetch
实用程序下载源:
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中包含头文件:
同样,我们也不要忘记在
BUILD.gn
添加对
base
项目的
BUILD.gn
。
BUILD.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 , false ); 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/ 。 还有一个“响应”按钮。