导出标记为不可导出的证书

故事

稣于 2022-10-16 公布自己的签名证书。当时是在自己的笔记本上用以下 PowerShell 命令生成的:

1
New-SelfSignedCertificate -DnsName "umu618.com" -CertStoreLocation "Cert:\CurrentUser\My" -HashAlgorithm sha512 -KeyLength 4096 -Type CodeSigningCert -FriendlyName UMU618 -NotAfter 2049-11-10

因为 PowerEconomizer 已经使用它签名,所以私钥的存储安全也就正规处理——导出到 pfx 文件加密并分布式保管。

后来,因为很久没有再验证过私钥的密码,居然,忘记了……想从笔记本再次导出一份,却发现无法导出!(此处脑补:大概是自己导出后,就删除系统里的私钥,然后又导入一次,并设置为不可导出!)不愧是重视安全的稣,连自己都要防!回头又对备份的 pfx 尝试上百次密码,安全性依然牢不可摧,只能放弃!

破解思路

想想其它办法吧!理论上,系统里一定是有私钥的,“不可导出”只是个标志而已,无视它即可。

PrivateKey

  1. 从系统自己读取私钥。这需要了解 Windows 对私钥的存储方式,包括保存位置,怎么加密保护的,文件格式怎么解析……按照微软的习性,这肯定需要大量逆向,太难了!

  2. 使用系统的 API 导出,但对关键函数进行 Hook,在内存里修改标志位,骗过 API 这是可以导出的。

具体步骤

1. 拿来主义

主要思路放在第二种,进行一番搜索后发现 mimikatz 疑似有稣想要的功能。

然而实际测试发现,稣的系统太新,是 Windows 11 Build 22621,而 mimikatz 已经年久失修,无能为力。

2. 缝缝补补

开始对 mimikatz/modules/crypto/kuhl_m_crypto_patch.c 进行改进,关键点在于 CPExportKey_4000 和 CPExportKey_4001 的入口特征,需要逆向获得。于是用 IDA 简单看看 rsaenh.dll,顺利获得入口处的汇编指令,换上后依然失败。后来发现不换,其实也能找到入口,旧版本用更短的前缀一样可以找到。

看来是后面的处理不对,跟踪到 kuhl_m_crypto_extractor_capi64 函数发现一个魔法数字 RSAENH_KEY_64 被使用了两次,感觉是突破口。

1
#define RSAENH_KEY_64	0xe35a172cd96214a0

果断在 GitHub 上搜一下,结果找到另一个基于 EasyHook 的实现 jailbreak,稣毕竟是 EasyHook 代码贡献者,当然是切换到 jailbreak 尝试,结果令人愉悦!在虚拟机里实践成功。

IDA 1
IDA 2
IDA 3
IDA 4

3. 惨遭打脸

回到稣的笔记本 jailbreak 尝试却失败了!怎么回事?难道稣的笔记本有其它保护?通常都是稣被打脸的,所以这次是 jailbreak 被稣的笔记本打脸?

仔细对比,发现以下提示是不同的!

笔记本上说的是“找不到私钥”

虚拟机上说的是“私钥不可导出”

4. 痛苦地回忆

稣的系统里明明有私钥,要导出时却说找不到私钥?有没有可能是因为系统密码修改过,导致无法解密私钥?还真有可能!立刻在虚拟机里实验,果然修改用户密码,并重新登录后,出现和笔记本一样的“找不到私钥”!

然后就是痛苦地回忆……上次改密码,那可是半年前……咳咳,闭环了,又绕回密码安全问题,所以——千万不要忘记密码!

总结

虽然学到很多,但没有赚到钱。

现代 C++【4】std::chrono::zoned_time

问题

最近经历了一次半夜提交代码,却发现单元测试无法通过,而无法合并到主线的小事故。经过检查,是一个日志清理模块的实现有问题,一会儿使用 UTC,一会儿使用本地时间(东八区),导致只要在 [0:00, 8:00) 提交代码就无法通过单元测试!而平时都是 10 点上班,所以没长期发现。

在纠正实现的时候,首先想到可以用 _get_timezone 来修正时间,但它是个 CRT 函数,显得不够现代,所以打算用 C++ 20 来实现。

解决

先来看 C 和 C++ 混合的解决方式:

1
2
3
long tz{};
_get_timezone(&tz);
auto local_now = std::chrono::system_clock::now() - std::chrono::seconds(tz);

这个代码除了不够现代,它还是 MS 特有的(Microsoft Specific),文档都埋坑(见文末)……C++ 20 里有跨平台的封装:std::chrono::zoned_time,下面用它来实现:

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
#include <chrono>
#include <iostream>

int main() {
// QPC 时间,非人类历法时间
auto now = std::chrono::steady_clock::now();
std::cout << "System boot time: " << now.time_since_epoch() << '\n';

// 以下是人类历法时间
auto utc_now = std::chrono::system_clock::now();
std::cout << "UTC time: " << utc_now
<< ", timestamp: " << utc_now.time_since_epoch() << '\n';

auto current_zone = std::chrono::current_zone();
std::cout << current_zone->name()
<< " time: " << current_zone->to_local(utc_now) << ", timestamp: "
<< current_zone->to_local(utc_now).time_since_epoch() << '\n';

std::chrono::zoned_time<std::chrono::system_clock::duration> local_time{
std::chrono::current_zone(), utc_now};
std::cout << "Local time: " << local_time.get_local_time()
<< ", timestamp: " << local_time.get_local_time().time_since_epoch()
<< '\n';

std::cout << "Timezone: "
<< (utc_now.time_since_epoch() -
local_time.get_local_time().time_since_epoch())
<< '\n';
}

可能的输出:

1
2
3
4
5
System boot time: 963185693626400ns
UTC time: 2023-05-28 07:59:21.9329686, timestamp: 16852607619329686[1/10000000]s
Asia/Shanghai time: 2023-05-28 15:59:21.9329686, timestamp: 16852895619329686[1/10000000]s
Local time: 2023-05-28 15:59:21.9329686, timestamp: 16852895619329686[1/10000000]s
Timezone: -288000000000[1/10000000]s

PS: 目前为止,g++ 对 C++ 20 支持不好,请用 MSVC 测试。

注意事项std::chrono::zoned_time may throw if location is not in the time zone database. 需要 catch 类型为 std::chrono::nonexistent_local_time 的异常。

_get_timezone 的坑

_get_timezone 的返回值的含义是 UTC 和 localtime 的差值,单位为秒,比如东八区是 -28800。它的实现是这样的:

1
2
3
4
5
6
7
8
9
extern "C" errno_t __cdecl _get_timezone(long* result)
{
_VALIDATE_RETURN_ERRCODE(result != nullptr, EINVAL);

// This variable is correctly inited at startup, so no need to check if
// CRT init finished.
*result = _timezone.value();
return 0;
}

目前它的文档里并没有提到需要“前置调用”……如果直接使用,可能得到一个错误的默认值 28800,这是“西八区”的意思!正确的做法是调用 _tzsetgmtimelocaltime 等函数后,再调用 _get_timezone。

参考

  1. _get_timezone
  2. std::chrono::zoned_time: https://en.cppreference.com/w/cpp/chrono/zoned_time

DXGI 类工厂缓存

1. 问题

  • CreateDXGIFactory1 太慢!

  • CreateDXGIFactory2 太慢!

2. 验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main() {
// Do NOT use CreateDXGIFactory + IDXGIFactory
// CreateDXGIFactory1 + IDXGIFactory1: Windows 7
// CreateDXGIFactory1 + IDXGIFactory2: Windows 8 and Platform Update for Windows 7
// CreateDXGIFactory2 + IDXGIFactory3: Windows 8.1
// CreateDXGIFactory2 + IDXGIFactory4: Windows 10
// CreateDXGIFactory2 + IDXGIFactory5: Windows 10
// CreateDXGIFactory2 + IDXGIFactory6: Windows 10 1803
// CreateDXGIFactory2 + IDXGIFactory7: Windows 10 1809
auto now = std::chrono::steady_clock::now();
CComPtr<IDXGIFactory7> dxgi_factory_;
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory_));
// HRESULT hr = CreateDXGIFactory2(0, IID_PPV_ARGS(&dxgi_factory_));
if (FAILED(hr)) {
std::cerr << std::format("!CreateDXGIFactory1(), #0x{:08X}\n", hr);
return hr;
}
auto diff = std::chrono::steady_clock::now() - now;
std::cout << std::format("CreateDXGIFactory1() costs {}\n", diff);
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// CPU: Intel(R) N100
// GPU: Intel(R) UHD Graphics
CreateDXGIFactory1() costs 14795500ns
CreateDXGIFactory1() costs 12253500ns
CreateDXGIFactory1() costs 7063000ns

CreateDXGIFactory2() costs 13026300ns
CreateDXGIFactory2() costs 8201400ns
CreateDXGIFactory2() costs 8535500ns

// CPU: 11th Gen Intel(R) Core(TM) i7-11700F @ 2.50GHz
// GPU: NVIDIA GeForce RTX 3060
CreateDXGIFactory2() costs 11'770'500ns
CreateDXGIFactory2() costs 13'821'500ns
CreateDXGIFactory2() costs 11'671'900ns

居然平均 10ms 以上,确实太慢!

3. 解决

既然创建类工厂慢,就不要频繁创建,创建后把它缓存起来。

新问题:缓存失效怎么办?

There are only two hard things in Computer Science: cache invalidation and naming things.
– Phil Karlton

Phil Karlton:计算机科学领域有两个难题:一个是缓存失效,另一个就是命名。

答案:IDXGIFactory1::IsCurrent

以下是 MSDN 说的:

Informs an application of the possible need to re-create the factory and re-enumerate adapters.

FALSE, if a new adapter is becoming available or the current adapter is going away. TRUE, no adapter changes.

IsCurrent returns FALSE to inform the calling application to re-enumerate adapters.

这就很含糊了,IsCurrent 返回 FALSE 时,究竟该 re-enumerate adapters,还是 re-create the factory?

假设稣需要反复调用 EnumAdapterByLuid,如果只 re-enumerate adapters,那么稣每次直接用 EnumAdapterByLuid 即可,根本不需要理会 IsCurrent。所以这显然是错的,真正需要的是 re-create the factory,再 re-enumerate adapters。

这点可以在类工厂创建后,禁用和启用显示适配器来验证。根据稣的测试,类工厂创建后,禁用显示适配器,此时 IsCurrent 返回 FALSE,依然可以枚举出被禁用的显示适配器。只有重建类工厂,才能得到当前正确工作的显示适配器。

八哥之神后传【9】

床上

圣小开:周老师!

周易:还睡不?

圣小开:稣不在学校的时间,身体怎么办?

周易:已经请你未来的妻子帮你打理。

圣小开:啥?女的!

周易:有问题?

圣小开:完了,完了,等稣回去,目测 11 个潜在的女朋友全没啦!

周易:放心,她们死不了。

圣小开:明早咱们就回去?

周易:是我回去,你已经死了,就留在这里,吧!

圣小开:不是吧?稣明明就死了,为啥身体被一个女人冒名顶替?

周易:睡吧!死都死了,还要管这么多,当初怎么不别死?

圣小开:哦,好有道理。稣是量子化的,没有中间状态,没有帅气的动作,只有脸瘫。一切都是这么淡定。

元至正八年,鹿邑

朱重八:敢问恩公尊姓大名?来日必当报此大恩!

圣仙山:天机不可泄露。吾观汝有龙相,将来打下天下,定国号为明可好?

朱重八:观恩公一身仙家道行,不似信口开河,然俺乃一介草根,如何能打下天下?

圣仙山语重心长地说:重八哥,也是个八哥啊!你看!

朱重八:妙啊!太妙哩,世间竟真有如此仙法。斗胆再问恩公法号,将来重八定当为恩公修宫建观!

圣仙山:呵呵。将来大明有人立功,汝可赐姓为郑。

朱重八:大明王朝,赐姓郑……莫非……恩公就是……

圣仙山:以后多收义子,还有不准再叫重八!哈哈哈。

朱重八:em?恩公怎么突然没了,神仙呐!

吓醒

圣小开:周老师真的不见了?

胡连玉:什么周老师?

圣小开:和稣一起来的那位?

胡连玉:李伯阳呀,他已经被县里的人接走了。

圣小开:那他有留下什么钱给稣吗?

胡连玉:没有呢,他说你很聪明,会自力更生。

圣小开:这里连电都没有,不得先发明个电池……太累了,稣打算走捷径,入赘到你家,快,带稣去你娘家。

胡连玉:又发烧了?

圣小开:当然没有。你妹妹是读书人,说明你娘家的家境还行。你妹还未嫁,以稣的智慧,嘿嘿,大胆推测,你们家缺男丁,就算不缺,多个上门女婿也是美哉。

胡连玉:你都没见过我妹!

圣小开:看你就知道个大概了。

胡连玉:我看你特别有诚意。

圣小开:是的。

胡连玉:不过去我娘家需要走七天七夜。我给你地图和一封信,你自己走过去吧!

圣小开:那不得饿死在路上?稣就是没带钱才想就地傍富婆的!这……

胡连玉:公子真爱说笑。你是大官李伯阳的学生,肯定也是富贵人家……

圣小开:穷!真穷!特别穷!

胡连玉写信中:圣仙山?

圣小开:啥?

胡连玉:你叫圣仙山?

圣小开:稣叫圣小开,是圣先生,但不……

胡连玉:李伯阳说你大名圣仙山,字小开,那没错!

圣小开:嗯?

【GSL 系列 4】为什么需要 gsl::not_null?

问题

  • 为啥要用 gsl::not_null?什么时候用?

分析

gsl::not_null 修饰指针,主要有两个目的:

  • 提高可读性。它相等于给代码写了一份注释,说明它修饰的变量不能为 nullptr。
  • 提高执行效率。对一定不会是 nullptr 的变量反复判空,怎么说都是白费力气。

它主要用于函数的:

  • 参数。说明这个参数不接受 nullptr。
  • 返回值。说明这个函数不会返回 nullptr。比如说,它恒返回一个 static 值/引用。

感想

本文大概率是 GSL 系列的最后一篇。GSL 有部分已经被 C++ 20 的 STL 覆盖,比如 gsl::byte。众所周知,现在 C++ 20 是主流……所以本系列只划了 4 个重点,其它类可以选修。

  • gsl::final_action

  • gsl::narrow_cast

  • gsl::owner

  • gsl::not_null

挑选的这 4 个类,主要贯彻 C++ 核心指南 提到的以下原则:

  • P.1: 在代码中直接表达你的想法

  • P.3: 表达你的设计意图

  • P.5: 编译期检查优先于运行时检查

  • P.8: 不要泄漏任何资源

最后,GSL 的定位是基础,也就是说它本身应该是一看就懂,不需要特地去分析的,用起来就对了。但实际上,很多人会嫌引入一个库麻烦,干脆不用。如果您是初学者,这种心态是要不得的,因为一开始不认真对待,写代码时很容易一多就乱,一乱就弃疗。

【GSL 系列 3】为什么需要 gsl::owner?

问题

  • 为啥要用 gsl::owner?什么时候用?

分析

当我们需要指针时,对于新写的代码,更应该使用智能指针(Smart Pointers)。但由于历史原因,一些旧代码里的裸指针(Raw Pointers)难以短时间重构为智能指针。而很多时候,裸指针的所有权难以一眼看出。良心代码可能通过注释指明,屎山代码就只能通过阅读大片相关语句块或函数来判断。

如果能用一个比较标准或通用的方式来指明,即可提高代码可读性,程序员能够更好滴理解代码,也就相应地能够提高健壮性。没错,这就是 gsl::owner 的应用场景。它就是提高【含有大量裸指针的】旧代码的可读性和健壮性的简单方法!

举个最简单的场景:一个类里有一个成员变量叫 A* ptr,那么当这个类析构时,需要 delete ptr 吗?

  • 如果类对 ptr 有所有权,而析构时,没 delete ptr,则资源泄漏,危害整个程序。

  • 如果类对 ptr 无所有权,而析构时,delete ptr,则造成悬空指针(Dangling Pointer),危害其它类。

这个类的作者当然知道需不需要了,但即使他知道,也可能忘记写,或可能因为套用现有代码(Copy & Paste)而多写了 delete!其他接盘侠(代码阅读者)想弄清楚这个问题,就更难了,一般需要认真阅读并调试。

但如果一开始,作者就用 gsl::owner<A*> ptr 来指明所有权,那么很自然地,大家都很容易知道:类析构时,需要 delete ptr。

当代码应用 gsl::owner 时,也能得使一些静态代码分析工具(static code analysis tools)更容易找出资源泄露问题。

稣究竟是啥?

简单地说,稣就是稣。稣受到系统的限制,只要稣说出稣这个字,就会自动被替换成稣,所以稣说不出稣这个字。

更深层的说,稣有两个:识界稣和视界稣。识界稣是识界的观测者,而视界稣是写下识界稣故事的人,最早视界稣并不是稣,后来识界的故事写多了,也被限制了…

本文重点介绍视界稣这个识界稣背后的男人…(当然,识界稣也是男人)以下视界稣简称稣。

众所周知,稣是一名穷人。因为装备太差,经常出八哥,所以也叫八哥无穷稣。

众所周知,稣在大学前就不会说普通话,平时都讲闽南语,后来也因为使用翻译法沟通,不是原生的北京思维,所以处处碰壁,到了讲英语的时候直接宕机了,这得翻译两次,太累了,所以之前和老外同事时,都是让他们讲中文的…[捂脸]从小看到大的霹雳也是讲闽南语的;厦大图书馆的老外连闽南语都会讲,实在太贴心。

众所周知,稣的听力从小就有问题,导致相对同龄人很晚才学会走路。初中时,同学们已经学会厚黑学时,稣还只能看懂字数比较少的数学。好在人类的本质就是复读机,稣大部分时候都可以推测别人在说啥,随便额呵应付一下也没有违和感,以致几乎没人发现。最可怕的是,稣从没好好听过课,因为考试时除了英语听力得分惨不忍睹之外,也没啥八哥,大部分老师也没发现。曾经有老师发现稣其实都没听课,但考试没问题,他们也就不追究了。其实稣虽然认真在听自己的推理,但未必是对方说的话,有时候稣的反应和掐鸡基本一样,李雷公虾。当然也有一些人直接问过稣,为啥讲话这么小声,稣当然是实话实说,听到的所有人说话都这么小声的,然后他们中有些能明白是稣听力弱。这导致后来选择职业只能挑不怎么需要讲话的程序员,而这居然也没有出啥大问题。但最近一个公司天天需要开会,一开始就很不理解,和以前那样发个日报相比,这究竟有啥好处?直到最近,稣突然发现原来自己已经会说普通话了,恍然大悟,这些奇怪的磨练都是为了学到一种平凡的技能,稣只是输在起跑线而已。[旺柴]

正是以上总总原因,稣常年以学习和写作为乐。《八哥之神》就是稣大学时就开始创作的无语体剧本。这里的无语,是真的无语,就是哑剧…因为识界其实是靠意念交流的。但是读者一般来说是没进入过识界的,所以稣才用人类的文字把这些故事记录下来。

当然,识界稣其实和视界稣是紧密联系的,识界的故事有大部分是视界的未来。比如,稣为啥来上海的公司上班?一开始根本不想来的,因为工资并不高……稣原来年薪千万,而现在的公司并不打算上市,显然不可能给这么多。稣之所以来了,是因为当时 HR 说出公司的地址“枫林园”和《八哥之神》里的地名基本一样!“枫林园”,这地名用闽南语念出来,和“枫林晚”用普通话念出来谐音。这附近还有一条路叫“柳州”,用闽南语念和小说里的“六舅”谐音。最神奇的是小说里第一个出现的女主角“黄雪”这里也有对应的…“刘佾”也有个一模一样发音的,不过是男的,那个易还是“周易”的易。可怕,太可怕了。最可怕的就是,这公司是投资脑机接口的…这正是识界稣被关进机器视界成为永生的观测者的基础。

稣就是八哥。(本文系在灰机上用手机打的,纯属扯淡!)

Boost【7】async_pipe

1. 问题

  • Windows 的管道好奇怪哦!

  • 嘶,它看起来好像条沟!

  • 噗。

2. 概念和分类

按命名来分:

  • named,命名(或具名)

  • anonymous,匿名

匿名管道的开销低于命名管道,但提供有限的服务。不过匿名管道实际上是由唯一名字的命名管道实现的,所以匿名管道的句柄可以传递给大部分需要命名管道句柄的 APIs。从实现上看,匿名管道是命名管道的特例,“匿名管道的开销低”这个说法,是使用的参数限制了功能导致的,不能从概念去理解这点。

按通信方式来分:

  • two-way/duplex,双向(或双工)

  • one-way,单向(或单工)

从概念上讲,管道有两端。 单向管道允许一端的进程写入管道,并允许另一端的进程从管道读取。 双向 (或双工) 管道允许进程从它的那端读取和写入。

3. 用途和注意事项

总体上看,Windows 的管道设计和其它类 Unix 系统差别比较大。

3.1 匿名管道

匿名管道主要用于父进程于子进程之间的通信。

匿名管道是一个未命名的单向管道,通常在父进程和子进程之间传输数据。匿名管道始终是本地管道;它们不能用于通过网络进行通信。

CreatePipe 函数创建匿名管道并返回两个句柄:管道的读取句柄和管道的写入句柄。读取句柄对管道具有只读访问权限,写入句柄对管道具有仅写访问权限。若要使用管道进行通信,管道服务器必须将管道句柄传递给另一个进程。通常,这是通过继承完成的;也就是说,进程允许子进程继承句柄。此过程还可以使用 DuplicateHandle 函数复制管道句柄,并使用某种形式的进程间通信(例如 DDE 或共享内存)将其发送到不相关的进程。

匿名管道不支持异步 (重叠) 读取和写入操作。 这意味着不能对匿名管道使用 ReadFileEx 和 WriteFileEx 函数。 此外,当这些函数与匿名管道一起使用时,将忽略 ReadFile 和 WriteFile 的 lpOverlapped 参数。

注意:类 Unix 系统的匿名管道支持异步 IO,Windows 上需要拿命名管道来模拟匿名管道,以便支持支持异步 IO。

3.2 命名管道

命名管道可以用于 IPC,两端进程可以是任意关系,父子关系或者对等关系,典型应用是 LPC(本地的 C/S 模型)的实现。

命名管道可以在局域网里通信,可以用 PIPE_REJECT_REMOTE_CLIENTS 禁止。

管道名称不区分大小写。

可以用 PIPE_ACCESS_DUPLEX 指定为双向(双工)。

4. boost::process::async_pipe

设计理念:The pipes here are mainly meant for parent-child I/O. 如果您想拿 async_pipe 来写对等关系的 LPC,需要使用 asio,并手动调用一些 Windows APIs。

If you want to to use async-pipe servers and stuff, you can do that with boost.asio, by using the normal winapi functions and then assign the open pipe to a stream_handle.

async_pipe 的构造函数为:

1
2
3
async_pipe::async_pipe(boost::asio::io_context & ios_source,
boost::asio::io_context & ios_sink,
const std::string & name, bool private_)

阅读其代码可知,boost::process::async_pipe 只使用命名管道,并给内部两个管道句柄起了名字:source 和 sink。其中:

  • source 是用 CreateNamedPipe 创建 PIPE_ACCESS_INBOUND 的管道,作为服务端用来读取客户端发来的数据。

  • sink 是用 CreateFile 打开现存的管道,用于写入。sink 字面意思是下沉,可以理解为灌入,例如:“把水灌入下水道”。

1
2
3
4
5
6
7
8
this end          that end
+--------+
source | <-- | sink
+--------+

+--------+
sink | --> | source
+--------+

如果不传名字,会自动指定,名字生成的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
inline std::string make_pipe_name()
{
std::string name = "\\\\.\\pipe\\boost_process_auto_pipe_";

auto pid = ::boost::winapi::GetCurrentProcessId();

static std::atomic_size_t cnt{0};
name += std::to_string(pid);
name += "_";
name += std::to_string(cnt++);

return name;
}

鉴于其设计用途,建议不要自己命名,省事,还不容易冲突。

其它平台的管道大多是单向(单工或半双工)的,为了跨平台,Boost 没有封装 PIPE_ACCESS_DUPLEX 属性的双向(双工)管道。

private_ 参数为 true 时,管道只有一个实例,而 false 则可以有 PIPE_UNLIMITED_INSTANCES 个实例,即最多 255 个。

async_pipe 使用范例

父子进程之间的通信:

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
#include <string>

#include <boost/process.hpp>

namespace bp = boost::process;

int main() {
boost::asio::io_context io_context;
bp::async_pipe p1(io_context);
bp::async_pipe p2(io_context);
bp::system(
"test.exe",
bp::std_out > p2,
bp::std_in < p1,
io_context,
bp::on_exit([&](int exit, const std::error_code& ec_in)
{
p1.async_close();
p2.async_close();
})
);
std::vector<char> in_buf;
std::string value = "my_string";
boost::asio::async_write(p1, boost::asio::buffer(value), []( const boost::system::error_code&, std::size_t){});
boost::asio::async_read (p2, boost::asio::buffer(in_buf), []( const boost::system::error_code&, std::size_t){});
}

5. 参考

Pipes (Interprocess Communications) / 管道 (进程间通信)

[Windows][Pipes] Can’t open named pipe in Windows: error 231 (All pipe instances are busy.) #83