这几天 Bad Apple 又逆袭了,比如这个和这个。虽然周末时候自己也做了一个,但介于身心憔悴和一个遗留问题,暂时先不和上次一样出视频了……
首先,看到那两个演示视频就可以联想到《编程之美》里面的一道面试题,但显然完全不是一回事,因为图像的 x → y
中 y
并不唯一。既然如此,那么就可以推定这是某种程度的「造假」,因为图像已经不是 CPU 占用率曲线了。
既然造假,我们就要造得有良心!那种类似直接在任务管理器上新建/覆盖一个视频窗口的做法略无节操了一些。作为一个死程,我们还是慢慢用程序解决吧……
视频预处理
这个就不用写程序了,看过我之前视频的童鞋一定知道我要用 ffmpeg 来把视频转成位图序列,没错,这次还是它!
而 ffmpeg 同时还提供了非常好用的滤镜支持,要把视频变成 CPU 占用率曲线的样子,我们需要边缘检测(edgedetect)和颜色通道混合器(colorchannelmixer)。
ffmpeg -i <文件名> -r <FPS> -vcodec bmp -vf edgedetect,colorchannelmixer=0:0:0:0:1:1:1:0:0:0:0:0 <输出文件名>
这样就能直接输出黑底绿线的位图序列,以备使用了。
屏蔽掉 CPU 占用率曲线的绘制
这里只考虑 Win 7 及之前的任务管理器,因为这货很好改。首先,这曲线一看就是 LineTo()
函数画出来的,那么我们用 OllyDbg 直接查看任务管理器中所有对于 LineTo()
的调用就可以找到对应代码。
我是 Win 7 的系统,用 OllyDbg 直接打开后,对 LineTo()
的调用只有:
- 绘制性能标签页中的背景网格
- 绘制 CPU 占用率曲线
- 绘制内存占用曲线
- 绘制联网标签页中的背景网格
- 绘制网络活动曲线
这几处,熟悉汇编的都可以很快定位整个语句的范围。至此,我们得到了一个地址及一个长度,把这个范围内的指令全部变成 Nop 指令即可。
当然 OllyDbg 中反汇编的地址是不能直接用的,还需要减去当前模块的基地址,得到偏移量以备使用……
那么来到 C++,FindWindow
→ GetWindowThreadProcessId
→ OpenProcess
即可获得任务管理器的进程句柄。
得到进程句柄后,我们首先要获得 taskmgr.exe 模块当前的基地址:EnumProcessModules
→ GetModuleInformation
。而后,就可以用上之前得到的偏移量和长度,使用 WriteProcessMemory
把绘制 CPU 曲线的代码覆写为一串 Nop 指令。
世界清静了。
绘制动画
这里就没有什么技术含量了。无非就是自己 SetTimer
开一个定时器,按照一定的帧率往窗口上 TransparentBlt
以前处理好的图片。
需要注意的是,背景网格,这货最麻烦了。我目前没有用 Hook,所以暂时的做法是:自己维护一份干净的背景网格。这样做的缺点是,网格更新时有一定几率察觉到曲线的缺失(用 Hook 后应该会好:Hook 后替换掉 Window Proc,然后正确的 WM_DRAWITEM
/WM_PAINT
之后立即把当前的图片绘制上去)。
以上。
今天在看 C++11 的 Inherited Constructors 特性时发现了一个以前不知道的传统 C++ 奇技淫巧。
class Base {
public:
void foo(float a);
};
class Derived: public Base {
public:
void foo(int a);
};
这样的代码,显然 Derived::foo()
会把 Base::foo()
覆盖。
今天得知,using
居然还可以把父类的成员「提升」到子类中:
class Derived: public Base {
public:
using Base::foo; // 看这里看这里看这里
void foo(int a);
};
如此,就相当于在 Derived
中添加了一个和 Base::foo()
一模一样的成员。(当然,从字面上也是很顾名思义的嘛。)
Derived d;
d.foo(3.14f); // 这样调用的就是 Derived::foo(float a) 了
之前我曾想,两个完全符合「is-a」关系的类,做成聚合显然是不甘心的。但如果父类接口有 N 个,子类只是想添加 1 个接口,然后将父类中的 M 个(M < N)接口暴露出来该怎么办?想想 private
继承,然后自己在子类中写 M 个 wrapper 似乎是个可行的方法,但如果 M 很大似乎依旧不甘心。现在知道了这个技巧似乎好解决了很多呢。(不过,整个类的声明也会随之变得脑残起来。)
顺带一提,传统 C++ 中的这个方法是无法提升构造函数的,而 C++11 中则加入了允许继承(提升)构造函数的特性(当然,目前貌似还没有编译器支持 = =)。
以上。
在派生类构造函数中捕获基类构造函数异常
class Base {
public:
Base()
{
throw std::runtime_error("Error from Base");
}
};
class Derived : public Base {
public:
Derived()
{
try {
} catch(std::exception const& e) {
}
}
};
类似于这样的代码,是捕获不到异常的,因为执行到构造函数的函数体中时,基类已经构造完毕了。
索性 C艹 有一个神奇的写法:
Derived::Derived()
try : Base() {
} catch(...) {
}
没错,我没有漏写大括号……你完全可以直接把一个 try {} catch {}
当作函数体……同理,对于一般函数这么写也行:
void foo()
try {
} catch(...) {
}
当然,构造函数的 catch
里是无论如何都会帮你确保有一个异常被抛出去的。(你不显式抛,它就帮你把被捕获的异常抛出去。)
后来发现这个东西学名叫做:Function-try block。
C++11 的异常处理
查了一番,似乎大的改变在于:
- 废弃原先的 Dynamic Exception Specification
- 添加了
noexcept
代替之
先看看之前的 Dynamic Exception Specification 是什么:
void foo(); // (1) 允许抛出异常
void foo() throw(X, Y); // (2) 只允许抛出 X 和 Y 异常
void foo() throw(); // (3) 不允许抛出异常
坑爹之处在于第 2 点:Dynamic Exception Specification 是在运行时进行的,于是,为了确保只能抛出指定类型的异常,就会生成额外代码降低执行效率。
最扭曲的是可自定义的 unexcepted 处理函数。所谓 unexcepted 处理函数,就是一旦你抛出了 throw(...)
中没有出现的类型,系统会去调用的那个函数。默认的 unexcepted 函数直接调用 std::terminate()
结束程序;如果你觉得这不好,你可能会想要去替换掉这个全局的处理函数,你会发现你可以:
- 和默认行为一样,调用
std::terminate()
自尽。
- 抛出异常,但是:
- 这个异常存在于
throw(...)
列表中,那么正常抛出。
- 这个异常不存在于列表中,但是
std::bad_exception
在,那么抛 std::bad_exception
。
- 这个异常不存在于列表中,而且
std::bad_exception
也不在,那么系统 std::terminate()
之。
重点在于,这是一个「全局」的处理函数。如果有人自定义这个,相当于如果要写 throw
列表,就要把 std::bad_exception
加上。
于是 C++11 因为这个在实践中基本没人用的东西而废弃了这个名字很长的功能,用 noexcept
取而代之:
void foo(); // (1) 允许抛出异常,相当于 noexcept(false)
void foo() noexcept(constant-expression); // (2) expression 成立时不允许抛出异常
void foo() noexcept; // (3) 不允许抛出异常,相当于 noexcept(true)
也就是将原先的 (2) 去掉,然后扩展一下原先的 (3)。一旦不允许抛异常的函数抛出了异常,那么直接 std::terminate()
结束运行。
对应还有一个同名的操作符 noexcept
,用来在编译时判断表达式是否允许抛出异常(遇到函数时只通过其声明时的 noexcept
与否判断)。
noexcept(1 + 2) // false
noexcept(throw 1) // true
noexcept(foo()) // 返回 foo() 声明时的 noexcept 与否
最好在哪里捕获异常
处处都 catch
的话,就反而比 if
麻烦了。据推荐,最好在这些地方 catch
:
- 需要更换错误报告手段(例如:实现 C 接口、进入了
noexcept
函数、多线程、系统回调……)
- 允许这种异常的发生(例如:如果
GetEntryDataFromZip()
会在 zip 文件数据损坏时抛出异常,那么 DisplayImageFileInZip()
就不应该捕获;而 CopyAllEntriesToAnotherZip()
则可以选择捕获这个异常,生成一个空的 zip 文件。)
- 有替代方案
参考内容:Jon Kalb: Exception-Safe Coding in C++
听说这个特性是很久以前了,总是读作「维拉迪克·坦普雷特」,一直没反应过来中文到底该叫什么,因为 C 时代的 Variadic Macro 我一直是很象形地读作「点点点」的 = =||
OK,扯远了。Variadic Template 对应中文应该是「可变参数模板」。
Parameter Pack
既然是可变参数,就需要通过某种方式来表示这些参数,而这里的解决方案就是 Parameter Pack 参数包,不知道可不可以简称「餐包」 =_,=
声明参数包的方法是在类型和名称之间加 ...
:
template<typename... Types> struct Tuple {};
Tuple<> t0; // Types 中不含参数
Tuple<int> t1; // Types 中包含一个参数:int
Tuple<int, float> t2; // Types 中包含两个参数:int 和 float
template<typename... Types> void f(Types... args);
f(); // args 中不包含参数
f(1); // args 中包含一个参数:int(1)
f(2, 1.0); // args 中包含两个参数:int(2) 和 double(1.0)
上面的两个示例中,Types
称作模板参数包,args
称作函数参数包。
参数包所包含的参数的个数可以用 sizeof...
取得。
Pack Expansion
既然提出了参数包,把所有可变参数容纳其中,那么就需要存在将其解包的操作。与 C 中 va_list
一个参数一个参数地手动解包不同,参数包的 Pack Expansion 是一口气将所有的参数以某种形式展开:
template<int... Entries>
struct IntArray {
int array[sizeof...(Entries)] = { Entries... };
};
template<typename... Types> void bar(Types... args) {}
template<typename... Types> void foo(Types... args) {
bar(&args...);
}
所谓「以某种形式」展开,就是将 pattern ...
转换为逗号分隔的 pattern_1, pattern_2, ... , pattern_N
的形式。从上面的函数 foo
就可以看出,传给 bar
的是各个参数的地址(没啥大意义);即 void foo(a, b, c)
的 &args...
会展开成 &a, &b, &c
。
std::tuple
作为一个可变参数模板的实际用例,C++11 还引入了 std::tuple
作为 std::pair
的推广形式(tuple 的意思即为元祖……哦不对,是元组……我是吃货我自重……),表示任意多个元素的组合。
使用 std::make_tuple
和 auto
可以很方便地声明一个元组:
auto x = std::make_tuple(3, 0.14, std::string("pie")); // std::tuple<int, double, std::string>
而对于各个元素的访问可以统一使用 std::get
实现(包括 std::array
和 std::pair
的大一统):
auto element = std::get<2>(x);
另一个好玩的地方是使用 std::tie
创建 lvalue reference 的 tuple:
std::set<int> some_instance_of_std_set;
std::set<int>::iterator itr;
bool success;
std::tie(itr, success) = some_instance_of_std_set.insert(2012);
虽然看着有些丑陋,但似乎可以看到些「多返回值」的影子……
当然,也可以参照 Lua 中的 _
使用 std::ignore
忽略多返回值中的特定位置的值:
int r1, r2;
std::tie(r1, std::ignore, r2) = std::make_tuple(3, 0.14, 4);
顺带的,既然是 lvalue reference,试图一句话交换两个变量的值是不可以全用 std::tie
的:
std::tie(a, b) = std::tie(b, a); // 错误方式
std::tie(a, b) = std::make_tuple(b, a); // 正确方式
字符画版本的 Bad Apple 的流行应该是好多年以前的事情了吧……虽然当时也想过自己做一个,但一直觉得图片转字符画是一个很神秘的过程(据说 mplayer 可以直接把视频按字符画输出)。前两天做完 ssoaag 发现,貌似可以用这个东西的原理输出字符动画,于是就有了下面这个视频 = =
【似乎是教程】如何制作字符动画
看到最后的效果你会发现最终产出的字符动画最右一列字符是略有问题的,在视频里也注明了,是那个 GetColor
函数里有个失误 = = 会导致某些情况下取到下一行的颜色,需要 continue
掉,并且把最后除以的 w * h
变成真正有效的像素数。
- «
- 1
- 2
- 3
- 4
- 5
- »