Monthly Read Posts in Feb 2017

RESTful架构风格下的4大常见安全问题

  • Don’t forget to check correlations between resources in request URL
  • Some rare used HTTP headers might be helpful
  • Restrict API request frequency

这篇略水,某些常见的问题反而都没有提到,例如大木老师遇到的这个问题


Safe Bitfields in C++

Main observation: Use union to implement/simulate bitfields.

Each bitfield is implemented by using a single union member with mask and bit-length being set up initially.

非常 stunning 的一个实现,但是很可惜,根据 comments 这个实现仍然是 undefined behavior,because it call member functions on inactive union members.

Monthly Read Posts in Jan 2017

Library order in static linking

An object file both provides (exports) external symbols to other objects and libraries, and expects (imports) symbols from other objects and libraries.

During linking, the linker maintains a symbol table, which keeps two important lists:

  • a list of symbols exported by all the objects and libraries encountered so far
  • a list of undefined symbols that objects and libraries demanded to import and were not found yet.

Case 1: the linker encouters a new object file

linker first checks symbols the object file exports, and:

  1. add them to the list of exported symbols
  2. remove them from undefined symbols, if necessary.

Note that, if any symbol has already been in the exported list, then we get a multiple definition error.

Linker then checks symbols the file imports, and added them to the list of undefined symbols, unless they can be found in the exported symbol list.

we can see that for linking pure object files, link order doesn’t matter.

Case 2: the linker encounters a new library

Linker goes over all the object files in the library, and for each one, it first looks at the exported symbols:

  1. If any of the exported symbols are on the undefined list, then this object file is chosen, being added to the link, and is treated as normal object file as above(its exported symbols and imported symbols are processed as normal object file).
  2. If any of the object files in the library has been included in the link, the library is rescanned again(because a lately added object file may require symbols exported from one early examined-but-skipped object file).

when linking is done, if any symbols remain in the undefined list, the linker will throw an undefined reference error.

Note that after the linker has looked at a library, it won’t look at it again. Even if it exports symbols that may be needed by some later library.

An very important corollary: If object or library AA needs a symbol from library BB, then AA should come before library BB in the command-line invocation of the linker.

Use Flags to Solve Circular Library Dependency

Use flags such as --start-group and --end-group to repeatedly scan libraries, until no new import symbol was found.

But may take significant time to finish linking.


Beginners’ guide to linkers

和前一篇着重分析链接顺序不同,这篇(其实更像是一个短 paper)介绍了整个链接的基础知识,适合扫盲。

但是如果有一定基础的话,这篇反而没有什么让人觉得惊奇的点。


Exploring std::unique_ptr

同样是一篇基础扫盲 posts,适用于检查自己是否对 std::unique_ptr 的必备知识存在缺漏的情况。

如果认真阅读过 Scott Meyers 的 Effective Modern C++,那么 post 里提到的,诸如“为什么使用 std::make_unique() 可以带来异常安全” 这种高级议题就变得显而易见了。

另外 post 对 custom deleter 的叙述的反而不够详细。


HTTP Made Easy

非常赞的 HTTP 协议扫盲文章

这篇 post 从 HTTP 1.0 开始讲起,然后过渡到 HTTP 1.1,所以对于 HTTP 1.1 为什么要求 request header 一定要带 Host header 提供了一个非常直观的解释。

此外 post 还增加了 chunked transfer encoding 部分,印象中 Top-down approach 那本 computer networking 是没有提及这部分内容的。


C++ 11 之美

这篇 post 主要有三个点

  • 编译期的类成员存在性检查 & 利用 SFINAE 做静态的分支选择(我的这篇 post 里的实现就是从这篇 post 改进而来)
  • 利用 std::function<> 实现 lazy-computation
  • 演示了如何将字符串 "hello/test/20" 转换为函数调用 hello("test", 20)

Android 三大图片缓存原理、特性对比

讲道理这篇文章很水….

一开始 mark 这篇文章是因为当时还在做安卓直播姬,转眼9个月过去了….

现在为了借鉴文中几个库的架构设计从 pocket 的回收站里挖出来看了一遍,不过想想也就那么回事了….

编译期判断是否存在某个成员函数

之前在某篇文章描述了一个实际上不是那么好用的检查某个模板类型参数是否存在某个函数的方法。

这次会介绍一个相对有用的,在编译期检查某个类是否存在给定成员函数的做法,并且可以根据检查的结果执行不同的代码。

(简而言之就是如何拙劣的模拟一下 duck typing)

假设我们要检查某个类 class Foo 是否存在成员数 bar(int)

实现辅助设施

1
2
3
4
5
6
7
8
9
10
11
template<typename T, typename... Args>
struct has_member_bar {
template<typename U>
constexpr static auto check(const void*)->
decltype(std::declval<U>().bar(std::declval<Args>()...), std::true_type());

template<typename U>
constexpr static std::false_type check(...);

static constexpr bool value = decltype(check<T>(nullptr))::value;
}

检查的方法是尝试生成函数 T::bar 的返回类型,如果不存在相应函数,则 check() 的重载决议会匹配到第二个函数上。

这里引入 variadic template 的原因是我们希望能够检查时同时确定函数的 signature(注意 signature 是不包含返回类型的)。

使用方法:

1
constexpr bool has_bar = has_member_bar<Foo, int>::value;

如果想把这部分做成通用的设施,那只能引入宏,根据需要生成辅助结构的代码;因为一个辅助结构只能检查一个函数成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define DECLARE_HAS_CLASS_MEMBER(NAME)                                                  \
template<typename T, typename... Args> \
struct has_member_##NAME { \
template<typename U> \
constexpr static auto check(const void*)-> \
decltype(std::declval<U>().NAME(std::declval<Args>()...), std::true_type()); \
\
template<typename U> \
constexpr static std::false_type check(...); \
\
static constexpr bool value = decltype(check<T>(nullptr))::value; \
}

#define HAS_CLASS_MEMBER(CLASS, MEMBER, ...) \
has_member_##MEMBER<CLASS, __VA_ARGS__>::value

使用前需要先用 DECLARE_HAS_CLASS_MEMBER(class_name) 来创建一个辅助结构,再利用 HAS_CLASS_MEMBER(Foo, bar, int) 来判断是否存在 Foo::bar(int)

如果希望能利用上面的设施在编译期做到根据函数是否存在执行不同的操作,那么则可以借助 SFINAE 来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
DECLARE_HAS_CLASS_MEMBER(foo);
DECLARE_HAS_CLASS_MEMBER(bar);

struct Foo {
void foo() {}
};

struct Bar {
void bar() { std::cout << "Bar::bar()\n"; }
void foo(const std::string&, int) { std::cout << "Bar::foo(const std::string&, int)\n"; }
};

template<typename T>
typename std::enable_if<HAS_CLASS_MEMBER(T, foo, const std::string&, int)>::type RunFoo(T& obj)
{
obj.foo("hello world", 1);
}

template<typename T>
typename std::enable_if<!HAS_CLASS_MEMBER(T, foo, const std::string&, int)>::type RunFoo(T& obj)
{
obj.bar();
}

int main()
{
Bar bar;
RunFoo(bar);
return 0;
}

但是如果想将 SFINAE 这部分做成通用的 static-if,还是比较 tricky 的;我一开始利用 lambda + SFINAE 倒是模拟了一个,但是因为 lambda 直接被无条件编译,导致即使在编译期已经确定不用某个分支的 lambda,仍然会编译这个 lambda 的代码,使得 static-if 功能大减。

我猜想使用 generic lambda 应该可以绕过去,但是实现上可能会更加 tricky,并且使用上不会太直观,或者满地坑。

其实 HAS_CLASS_MEMBER 最好的作用是等到 C++ 17 普及了 if-constexpr,这样整个流程都变得清晰了。

另外有一点不得不说,其实很多你认为需要通过编译期判断是否存在某个成员函数才能解决的问题,压根就走不到需要自己实现 SFINAE 那一步,通过一些(可能不是那么优雅)简单的构造,就可以利用 type traits 解决,例如,我在实现 KBase Singleton 的方案。

自动开始安装 Inno Setup 打包的安装程序

有时候我们希望用户执行安装程序后,跳过路径选择等一系列确认,自动开始安装,已尽可能减少等待时间。

最明显的例子就是,用户在已经安装程序的情况下,下载了新版的安装包,需要执行更新操作。因为安装路径、设置选项等信息早在用户首次安装时就已经确定,升级安装过程中完全可以跳过。

某科学的直播姬在加入自动更新功能的同时,就需要安装包具备上述能力。

OK,那么如何在 inno setup 中实现自动安装?

Step 1

Inno Setup 提供了 ShouldSkipPage(PageID: Integer) 函数,这个函数会在安装的不同阶段被调用,并且参数是当前安装程序页的 id,函数返回 True,则表示跳过此页。

于是只要重写这个函数,在更新模式下,自动跳过除了 wpInstalling 的所有页。

但是如果只这么做,会发现安装程序还是无法自动开始安装,它会停在 wpReady ,即确认页。

这是 a feature by design,因为 inno setup 的作者觉得不让用户确认就自动开始安装是一个很 evil 的行为;所以即使在 ShouldSkipPage() 里选择跳过确认页,inno setup 也会自动忽略这个要求。

真他喵的体贴啊。

既然只能手动点击安装,那我们就通过发送按钮点击事件来模拟用户点击就好了。

Step 2

在函数 CurPageChanged(CurPageID: Integer) 中检查当前页,如果已经到了确认页,就发送按钮事件。

1
2
3
4
if CurPageID = wpReady then begin
param := 0 or BN_CLICKED shl 16;
PostMessage(WizardForm.NextButton.Handle, CN_COMMAND, param, 0);
end;

这里注意几个坑,按我当时被踩的先后顺序:

  1. 如果你的安装程序定制了 UI,那么极有可能会隐藏原生的 next button 并且提供一个自己美化过的安装按钮;如果真的是这样,那么请通过设置 WizardForm.NextButton.Height := 0 来隐藏他,而不是直接 set invisible。因为后者会导致这个按钮直接不响应他的事件。
  2. 发送消息采用 PostMessage。我一开始用的 SendMessage 并没有成功,具体原因未知…

Rants

Inno Setup 这种试图通过提供一系列设置和受限的脚本的方式来生成安装包的行为,固然可以降低使用门槛,但是也增加了自定义的难度和复杂度。Joel Spolsky 那篇洞察一切的 the law of leaky abstractions 其实已经说明了一切。

其实如果不是接手项目的时候安装包早就已经固定了,我很可能自己就用 C++ 给撸了,如果真这样,也没有这么多七七八八的事情…

至于我为什么会亲自上阵做安装包…还不是因为没有人愿意做,并且之前的 iss 脚本质量实在一般,都快大坑小坑落玉盘了,总得有个人出来 clean the mess/ass,你说对伐(真怀念当年不想搞了可以把脏活直接丢给老大的日子…)。

自动将 non-capturing lambda 转换为函数指针

在开发某科学的直播姬的过程中,经常需要在 obs-studio 处理源之后紧接着做一些事情,例如针对大图片源做自动放缩等。

obs-studio 采用一个专有的 graphics rendering thread 来渲染各种 visualizable sources,并且允许你根据需求,注册各种底层源操作事件的回调函数(obs-studio 自己称之为 signal handler)。

我在 obs signal handler 之上,利用 std::function 构建了一层自己的 signal-handler,将原始的 obs 事件经过合理转换后得到语义更加明确的直播姬专属事件,并且通知上层。

但是前面提到,各种源操作都是在 obs 专属的渲染线程上完成的,那么我们向 obs 注册的 handler 也必然运行在这个专属线程上;这显然不是一个好现象,因为在直播姬程序语义下,这部分操作应属于我们自己定义的 UI 线程,并且用户可见的 UI 元素的操作也必须只能在 UI 线程中完成。

所以我们需要在 signal 转换的同时切换线程,让 std::function 的调用发生在指定的线程。

于是这里遇到这个 post 要解决的问题:我们使用 chromium base lib 来完成各种线程操作,但是 base::Bind() 不接受 lambda 作为 currying 的载体,因为这套 lib 是在 C++ 11 标准化之前就实现的,因此 chromium team 实现这个轮子的时候并没有考虑 C++ 11 的 lambda expressions。

方案1:使用独立的函数来产生 base::Callback

优点是直观明确而且很传统不容易出错;

缺点是,每个函数平均只有5行不到的代码(毕竟只是通过调用 std::function 做一个转发),而且我们要写大量这样的函数。。。另外由于这些独立的函数都放到了源文件开头的 anonymous namespace 里,造成阅读代码时的上下文割裂。这也是当初引入 lambda 的一个很重要的原因。

方案2:使用 non-capturing lambda,但是自己手动 cast 到函数指针

优点是利用 lambda 来限制了这部分“转发”函数的可见性;

缺点是,不仅会造成上下文割裂,而且手动 cast 非常的 tedious。

思索片刻后我打算自己实现一个 lambda_decay(),来将 non-capturing lambda 自动转换为等价的函数指针。

借助 template 和一个 trick 可以做到如下的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
using lpfn = void(*)(const std::string&);

lpfn Foo()
{
return lambda_decay([](const std::string& s) { std::cout << s << std::endl; });
}

int main()
{
auto ptr = Foo();
ptr("hello world");
return 0;
}

这样就可以直接将 lambda “嵌到” base::Bind() 里,极大提升幸福感。

这里说两个我并不认为是缺点的局限性:

  1. 不能使用在 capturing lambdas 上。这其实是我恰好需要的,因为 capturing lambda 会有各种安全隐患,配合 base::Bind() 一个不小心就容易出坑;另外我的目的本来就是转换得到能被 base::Bind() 使用的等价函数指针,而正常情况下从 non-capturing lambdas cast 得到一个函数指针是良好定义的十分明确的行为。
  2. 不能使用在 generic lambdas 上。generic lambdas 很好很有用,但是它不是银弹,尤其在配合 base::Bind() 使用的情况下。

接下来是实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template<typename T>
struct dememberize;

template<typename C, typename R, typename... Args>
struct dememberize<R(C::*)(Args...) const> {
using type = R(*)(Args...);
};

template<typename T>
struct lambda_pointerize_impl {
using type = typename dememberize<decltype(&T::operator())>::type;
};

template<typename T>
using lambda_pointerize = typename lambda_pointerize_impl<T>::type;

template<typename F>
lambda_pointerize<F> lambda_decay(F lambda)
{
return lambda_pointerize<F>(lambda);
}

其实如果 VS 2013 支持 constexpr 的话,其实用起来会更优雅。

另:我发现英文在自创单词上简直太有感觉了。。。。

Monthly Read Posts in Dec 2016

C++ Performance: Common Wisdoms and Common “Wisdoms”

The post discusses some oftenly-argued performance related aspects of C++.

Topics/objects include:

  • C++ Exceptions – (Almost-)Zero-Cost until Thrown
  • Smart Pointers and STL: Allocations are Still Evil
  • Using boost:: – Case by Case and Only If You Do Need It
  • Polymorphism – Two-Fold Impact
  • RTTI – Can be Easily Avoided Most of the Time
  • On Branch (mis)-Predictions and Pipeline Stalls
  • On inline asm and SSE
  • On inlines and force-inlines
  • On templates
  • On compiler flags
  • On Manual Loop Unrolling
  • “For-free” Optimizations a.k.a. Avoiding Premature Pessimization

All suggestion based on a fundamental pre-requisite: Three things Really Matter for performance.

The first one is Algorithm, the second one is your code being Non-Blocking, and the third one is Data Locality.

The rest of the tricks, while potentially useful, should be used only on case-by-case basis, when a bottleneck is identified. Otherwise it will be a Premature Optimisation.

Note: 作者是一个游戏开发者,上述观点也是结合游戏开发阐述的,因此对相当一部分高性能应用也有参考意义。


Iterables vs. Iterators vs. Generators

Key points:

Anything that can return an iterator (usually via overriding __iter__()) is iterable.

Anything that can be iterated (usually via overriding __next__(), or overriding next() in python 2.x) is an iterator.

A generator is a special kind of iterator; it make you avoid writing __netxt__() and __iter__().

Two types of generator:

  • Generator function: A function with yield to return value, the return value of the functio call is a generator
  • Generator expression: A expression using comprehension-syntax

Note:有 C++ STL 背景基本看这种问题都秒懂…


架构为什么会腐化

文中观点

  • 不理解项目业务价值,对于实际需求把握不明确
  • 过度设计,导致扩展性差
  • 懒于重构

个人观点

  • 明确/理解需求是第一位
  • 过度设计导致扩展性差存疑,实际上过度设计往往来源于需求把握不准
  • 初始架构设计很重要,对项目领导者要求较高
  • 重构可参考最小滑坡原则(每次离开一段代码,代码都要比刚接手时更清晰整洁)

Competing constructors

Competing constructor: two constructors that would take the same sequence of arguments (if one is somehow made invisible), but also that they do different, incompatible things.

Main observations:

  • Competing functions complicate resolution for human beings
  • Don’t write competing constructors, or even competing functions.
  • Take care of class template function, one instantiation may turn into a competing function.
  • Use tag with overload function can benefit from more clearer semantics

利用 chromium net 库的 URLRequest 实现支持断点续传的 URLDownloader

最近因为要实现某直播姬的自动更新功能,于是就要求客户端能够自动下载安装包,所以就要实现一个简单的支持续传的下载功能。

因为用了 chromium 的框架,所以自然是基于 chromium net lib 去实现;稍微翻了一下源码目录,最后决定在 net::URLRequest 的基础上自己封装一个 URLDownloader

虽然之前某豹浏览器也有恶意魔改过的下载,但是相关功能一直和我没关系,所以这次基本算是 from scratch,期间也因为资料文档基本没有,绕了一些弯路,全靠一边看源码一遍自己通过demo调试去学习…

为了方便可能的后来人,这里稍作记录。

完整的demo源码可以看这里:SimpleDownloader

URLDownloader 的头文件单独摘出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#ifndef DOWNLOADER_URL_DOWNLOADER_H_
#define DOWNLOADER_URL_DOWNLOADER_H_

#include <memory>
#include <vector>

#include "base/basictypes.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/threading/thread_checker.h"
#include "net/base/io_buffer.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "url/gurl.h"

namespace downloader {

// The user of URLDownloader must ensure that the construction and operations of an URLDownloader
// instance are performed on a same thread.
class URLDownloader : public net::URLRequest::Delegate {
public:
class CompleteCallback {
public:
virtual ~CompleteCallback() {}

virtual void OnDownloadSuccess() = 0;

virtual void OnDownloadFailure() = 0;
};

// The caller of this constructor must make sure:
// - `save_path` is writable;
// - the object `callback` refers to is alive during the entire lifecycle of URLDownloader, since
// the class itself doesn't acquire the ownership for the `callback`.
URLDownloader(const GURL& url, const base::FilePath& save_path, CompleteCallback* callback);

~URLDownloader() = default;

// Starts or resumes the download.
void Start();

// Stops the download.
void Stop();

private:
void OnResponseStarted(net::URLRequest* request) override;

void OnReadCompleted(net::URLRequest* request, int bytes_read) override;

// Saves received network data (stored in `buf_`) to disk write cache, and writes to disk
// if necessary.
// It is legal to call this function with 0 passed in as `bytes_received`, when you want to
// force flushing cache to disk.
void SaveReceivedChunk(int bytes_received, bool force_write_to_disk);

DISALLOW_COPY_AND_ASSIGN(URLDownloader);

private:
GURL url_;
base::FilePath save_path_;
base::FilePath tmp_save_path_;
CompleteCallback* complete_callback_;
size_t downloaded_bytes_;
std::vector<char> disk_write_cache_;
scoped_refptr<net::IOBuffer> buf_;
std::unique_ptr<net::URLRequestContext> request_context_;
std::unique_ptr<net::URLRequest> request_;
base::ThreadChecker thread_checker_;
};

} // namespace bililive

#endif // DOWNLOADER_URL_DOWNLOADER_H_

核心只有四个函数:URLRequest::Start(), OnResponseStarted(), URLRequest::Read(),和 OnReadCompleted()

这四个函数的 interactions 大概如下:

  • Start() 执行后内部会调用 OnResponseStarted()
  • OnResponseStarted() 中,如果 request 和 response 的通信都没有什么问题,则要用 Read() 读取接收到的网络 IO 数据,接着调用 OnReadCompleted() 切换状态;这个步骤称之为 initial reading
  • OnReadCompleted() 中,反复利用 Read() 读取网络数据

Read()OnReadCompleted() 之间有一个容易被忽略的细节: 利用 Read() 从读取网络 IO 数据时

  1. 如果数据立即可用,则 Read() 会正常读取数据
  2. 如果数据当前不可用,则会触发 asynchronous reading,但是 Read() 函数会马上返回;等到底层获取到 IO 数据后,内部自己会调用 OnReadCompleted()

除此之外,还要注意 URLRequest 对象的构造和操作必须在同一个线程。

至于续传,本质就是利用了 HTTP request header 的 range,具体请参考代码。

最后说一句,如果你的目的是实现一套和服务端 RESTFul API 通信的设施,那么直接使用 net::URLFetcher 是个更好的选择;因为后者是在 net::URLRequest 基础之上针对字符处理加的一个wrapper,屏蔽了具体的 IO data read 细节。