syn和quote实现派生宏Builder
前言
在这个github仓库,里面主要是一些关于Rust宏的问题。就先来实现Builder这个派生宏
简单来说,需要实现的要求如下
use derive_builder::Builder;#[derive(Builder)]
pub struct Command {executable: String,#[builder(each = "arg")]args: Vec<String>,current_dir: Option<String>,
}fn main() {let command = Command::builder().executable("cargo".to_owned()).arg("build".to_owned()).arg("--release".to_owned()).build().unwrap();assert_eq!(command.executable, "cargo");
}
最后的结果是这个command.executable值为"cargo"这个字符串。
实际上,这个args是一个vec,可以猜测其结果应该如下
["build","--release"]
正文
手动实现Builder模式
Builder 模式 是一种常见的设计模式,用于逐步构建复杂对象,尤其适用于可选参数很多的场景。它通过链式调用(method chaining)提供了一种更优雅、可读性更强的构造方式,避免了传统构造函数参数列表过长的问题。
手动实现一下。举个例子,下面这个结构体实现Builder
#[derive(Debug)]
pub struct Student {id: i32,score: f32,name: String,
}
如果需要使用Student::Builder这个方法,需要为Student实现,即
impl Student {pub fn builder() -> StudentBuilder {StudentBuilder::new()}
}
进一步需要实现StudentBuilder这个新的结构体。如下
pub struct StudentBuilder{id: Option<i32>,score: Option<f32>,name: Option<String>,
}
为什么用option,更灵活。后续将继续实现new方法和其他方法
比如id,如下
impl StudentBuilder{pub fn id(&mut self, id: i32) -> &mut Self{self.id = Some(id);self}
}
链式调用,返回self就可以了,在python里面经常有这种操作。
因此,最终代码如下
#[derive(Debug)]
pub struct Student {id: i32,score: f32,name: String,
}pub struct StudentBuilder {id: Option<i32>,score: Option<f32>,name: Option<String>,
}impl StudentBuilder {pub fn new() -> Self {Self {id: None,score: None,name: None,}}pub fn id(mut self, id: i32) -> Self {self.id = Some(id);self}pub fn score(mut self, score: f32) -> Self {self.score = Some(score);self}pub fn name(mut self, name: impl Into<String>) -> Self {self.name = Some(name.into());self}// 不再检查,直接返回pub fn build(self) -> Student {Student {id: self.id,score: self.score,name: self.name,}}
}impl Student {pub fn builder() -> StudentBuilder {StudentBuilder::new()}
}fn main() {let student = Student::builder().id(1001).name("张三").build();println!("{student:?}");
}
在StudentBuilder 中的new方法初始化,这里设为None,
在StudentBuilder中的build方法返回Student,参数是可选
运行结果如下
Student { id: Some(1001), score: None, name: Some("张三") }
更灵活,想设置就设置。而且是链式调用,好看。
首先,先创建新的crate——derive_builder。
根据上面的观察,先思考一下,要干什么???
思考一下
- 获取结构体的名字,创建对应的Builder
- 获取结构体字段,创建对应Builder中对应的方法
- 获取结构体字段的属性builder,这个属性有一个ValueName类型的元数据,其中name是each,在Builder中需要为这个each对应的值生成方法,
- builder属性在这里就只为Vec<T>实现,需要判断是否是Vec<T>。
- builder结构体所有字段都是Option类型的
- 需要考虑new和build方法
- 需要考虑结构体本身的Option,允许不使用
- 这里面感觉有许多细节的判断,有点意思!!!!
具体细节,慢慢来,不慌。
初始化项目
cargo new derive_builder --lib
其中Cargo.toml文件内容如下
[package]
name = "derive_builder"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true[dependencies]
syn={version = "2",features = ["full"]}
quote = "1.0"
proc-macro2 = "1.0"
syn用于把tokenstream变成AST。
quote用于把AST变成tokenstream。
proc-macro2 在中间解析过程中使用。
初始化这个派生宏,即
#[proc_macro_derive(Builder, attributes(builder))]
pub fn builder(input: TokenStream) -> TokenStream {let input: DeriveInput = parse_macro_input!(input as DeriveInput);
}
需要使用proc_macro_derive,宏的名字叫Builder,以及一个属性builder
首先,第一步需要把输入的TokenStream解析成DeriveInput。派生宏或者属性宏的第一步操作一般都是这个。
为了后续使用,可以看看看这个DeriveInput的定义
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]pub struct DeriveInput {pub attrs: Vec<Attribute>,pub vis: Visibility,pub ident: Ident,pub generics: Generics,pub data: Data,}
具体意思可参考以前的文章
syn与quote的使用——结构体转create语句_syn 宏编写-CSDN博客https://blog.csdn.net/qq_63401240/article/details/150639625?spm=1001.2014.3001.5502
获取结构体名字——生成对应的Builder
如何获取名字?显而易见——Ident,即
let name = input.ident;let name_builder = Ident::new(&format!("{}Builder", name), name.span());
这就生成了对应的Builder结构体。后续使用。
获取所有的字段
前面说过,要想获取结构体的字段
- 首先判断是否是结构体
- 然后判断是否是命名结构体
- 最后获取字段
因此,代码如下
// 获取字段let fields = match &input.data {Struct(s) => match &s.fields {Named(fsd) => fsd.named.clone(),_ => panic!("Builder derive macro can only be used on structs"),},_ => panic!("Builder derive macro can only be used on structs"),};
一步一步深入,画个图表示一下
浅蓝色表示enum,深蓝色表示struct,意思就是
从Data到DataStruct,到Fields,到FieldsNamed,到Punctuated,层层深入
Punctuated本质上就是一个Vec+Option构成的结构体,其定义如下
pub struct Punctuated<T, P> {inner: Vec<(T, P)>,last: Option<Box<T>>,
}
总之,层层深入,获取字段。
生成Builder结构体的字段
如何生成?
使用前面获取的字段生成,考虑前面StudentBuilder,都是用Option包裹,这里还是如此,全部都用Option包裹
前面的字段是在一个Vec中,遍历取值,生成由Option包裹的字段,为Builder结构体的字段。
因此,代码如下
// 设置builder结构体的字段,用Option包裹
fn set_builder_word(fields: &Punctuated<Field,Comma>) -> Vec<TokenStream2> {fields.iter().map(|f| {let ident = &f.ident;let ty = &f.ty;quote! {#ident: Option<#ty>}}).collect::<Vec<TokenStream2>>()
}
这里这个TokenStream2是中间流,使用了前面的依赖proc_mroc2,即如下导入
use proc_macro2::TokenStream as TokenStream2;
返回Vec,后续再拼接。
生成Builder结构体对应的new方法
new方法,很简单,因为Builder结构体全部类型都是Option,因此,初始值全是None
代码如下
fn init_builder(fields: &Punctuated<Field,Comma>)->Vec<TokenStream2>{fields.iter().map(|f| {let ident = &f.ident;quote! {#ident: None} }).collect::<Vec<TokenStream2>>()
}
和前面类似,遍历取值,生成中间流。
生成Builder结构体对应的build方法
考虑前面的需求,这个build方法,有个关键的信息current_dir这个字段类型本身就是Option<String>,什么意思,需求里面没有设置current_dir,说明其是可选的,可以是None。
需要做出细节上的考虑,在宏如何判断类型是Option???
和判断struct类似——层层深入
看代码
fn is_option(ty: &Type) -> bool {if let Type::Path(TypePath { path, .. }) = ty {if let Some(seg) = path.segments.last() {if seg.ident == "Option" {return true;}}}false
}
这什么意思?传入一个Type,判断其类型,从意思可以看出Type表示类型,Path表示路径的意思
什么是路径的意思,比如std::vec::Vec,这种带有::,就可以使用Type::Path。
还是用一张图表示其层层深入的结构
syn::Path这个结构体,本质上一个Option+Punctuated组成,而代码中的path.segments,这个
segments就是Punctuated,看看定义
#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub struct Path {pub leading_colon: Option<Token![::]>,pub segments: Punctuated<PathSegment, Token![::]>,}
可以发现分隔符是::,因此,如果对于std::vec::Vec,那么segments就是如下
["std","vec","Vec"]
这是一个Vec,后续获取最后一个元素last——Vec,
对于Option来说,类似的,获取最后一个元素,然后判断是否是Option。
解决了Option,后续就很简单了,代码如下,不必多说
fn get_build(fields:&Punctuated<Field,Comma>)->Vec<TokenStream2>{fields.iter().map(|field| {let ident = field.ident.as_ref().unwrap();let ty = &field.ty;// 检查字段类型是否为 Option<T>let ok=is_option(ty);if ok {// 对于 Option<T> 字段,允许未设置,默认为 Nonequote! {#ident: self.#ident.unwrap_or(None)}} else {// 对于非 Option<T> 字段,要求必须设置quote! {#ident: self.#ident.ok_or_else(|| format!("Field `{}` is not set", stringify!(#ident))).unwrap()}}}).collect::<Vec<TokenStream2>>()
}
还是返回Vec<TokenStream2>。
生成Builder结构体字段对应的普通方法
前面需要中有executable这个字段,其没有属性builder,不妨称其为普通方法,
模仿StudentBuilder中的id,代码如下
fn get_normal_method(field: &Field,ty:&Type)->TokenStream2{let word_name = field.ident.clone().unwrap();quote! {pub fn #word_name(mut self, value: #ty) -> Self{self.#word_name=Some(value);self}}
}
生成Builder结构体字段对应的属性方法
获取泛型
在需求中
#[builder(each = "arg")]args: Vec<String>,
args有属性builder,其本身是一个Vec,以及对元数据的处理。
这里面是最复杂的。慢慢来
首先考虑如何判断其类型是Vec<T>,不是判断Vec,而是把泛型也要判断,当然,不一定判断非要是String,可以是其他类型,但是要判断泛型。
说实话,这一步也比较麻烦,需要在前面判断Option的基础上,更进一步,难以用代码表示
用图表示,如下
额,有点小,算了。
直接看代码
fn get_generic_from_vec(ty: &Type) -> Option<Type> {if let Type::Path(TypePath { path, .. }) = ty {if let Some(seg) = path.segments.last() {if seg.ident == "Vec" {if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {args, ..}) = &seg.arguments{if args.len() != 1 {return None;} else {if let GenericArgument::Type(t) = args.first().unwrap() {return Some(t.clone());}}}}}}None
}
从segments开始看起,这个segments是一个Punctuated
#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub struct Path {pub leading_colon: Option<Token![::]>,pub segments: Punctuated<PathSegment, Token![::]>,
}
pub struct Punctuated<T, P> {inner: Vec<(T, P)>,last: Option<Box<T>>,
}
#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub struct PathSegment {pub ident: Ident,pub arguments: PathArguments,
}
而Punctuated的last这个方法,是Opton包裹了一个Box,Box指向T,而T是PathSegment,
PathSegment里面又有PathArguments,如果说Ident指向Vec,而PathArguments就是指Vect<T>
中的“<T>”这个整体。
再进一步,看看PathArguments又是什么?
#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub enum PathArguments {None,/// The `<'a, T>` in `std::slice::iter<'a, T>`.AngleBracketed(AngleBracketedGenericArguments),/// The `(A, B) -> C` in `Fn(A, B) -> C`.Parenthesized(ParenthesizedGenericArguments),}
其中AngleBracketed指的是尖括号形式的泛型参数,Parenthesized指的是括号形式的函数参数。
而AngleBracketed里面还有AngleBracketedGenericArguments
可以再看看AngleBracketedGenericArguments是什么???
#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub struct AngleBracketedGenericArguments {pub colon2_token: Option<Token![::]>,pub lt_token: Token![<],pub args: Punctuated<GenericArgument, Token![,]>,pub gt_token: Token![>],}
意思是很明显的,有“<”,有“>”,以及args这个字段,那么可以确定在这个args就是指<T>中的T
当然,因为泛型参数不止一个,所以使用了Punctuated。
还可以继续往里面看看,看看这个GenericArgument是什么???
#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]#[non_exhaustive]pub enum GenericArgument {/// A lifetime argument.Lifetime(Lifetime),/// A type argument.Type(Type),/// A const expression. Must be inside of a block.////// NOTE: Identity expressions are represented as Type arguments, as/// they are indistinguishable syntactically.Const(Expr),/// A binding (equality constraint) on an associated type: the `Item =/// u8` in `Iterator<Item = u8>`.AssocType(AssocType),/// An equality constraint on an associated constant: the `PANIC =/// false` in `Trait<PANIC = false>`.AssocConst(AssocConst),/// An associated type bound: `Iterator<Item: Display>`.Constraint(Constraint),}
有什么Lifetime、Type、Const之类的类型。
走到这就没什么可以说的,判断泛型参数的类型是否是Type。
更好玩的是,Type就是函数最开始的输入
总之,一层一层的往里面走,耐心。
但是,还没完,前面的代码仅仅从Vec<T>中提取了T。当然用了Option包裹
确定属性builder
定义一个函数,传入字段和对应的泛型,如下
fn get_generic_function(field: &Field, generic_type: &Type) -> Option<TokenStream2> {let field_ident = field.ident.as_ref().unwrap();let mut each_str = None;for attr in &field.attrs {}None
}
返回Option。
attr的类型是Attribute
#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]pub struct Attribute {pub pound_token: Token![#],pub style: AttrStyle,pub bracket_token: token::Bracket,pub meta: Meta,}
在这里,其中Meta里面就有each="arg"这个东西。
在之前,需要判断是否有属性builder,而这需要使用path方法
impl Attribute {/// Returns the path that identifies the interpretation of this attribute.////// For example this would return the `test` in `#[test]`, the `derive` in/// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`.pub fn path(&self) -> &Path {self.meta.path()}......
}
因此,判断builder的代码如下
if !attr.path().is_ident("builder") {continue;}
如果有,现在就要解析each="arg"了。
解析each="arg"并生成arg方法
如何进行解析???
可以使用parse_args_with这个在Attribute 中的方法,
代码如下
attr.parse_nested_meta(|meta|{if meta.path.is_ident("each"){let arg=meta.value().ok()?;}})
现在获取到each对应的值,现在需要判断其是否是字面量,且为字符串切片
说白了,判断类型。
即
let ard_str = arg.parse::<Lit>().unwrap();if let Lit::Str(s) = ard_str {each_str = Some(s.value());}
现在获取到了"arg",将其变成标识符Ident,然后生成arg方法,即
let method_name = each_str?;let method_ident = syn::Ident::new(&method_name, field_ident.span());Some(quote! {pub fn #method_ident(mut self, arg: #generic_type) -> Self {self.#field_ident.get_or_insert_with(Vec::new).push(arg);self}})
get_or_insert_with这个方法,如果字段是 Option<Vec<T>>,且当前是 None,就初始化为空 Vec,然后把元素放到Vec。
就这样。
因此,生成的属性方法如下。
fn get_generic_function(field: &Field, generic_type: &Type) -> Option<TokenStream2> {let field_ident = field.ident.as_ref().unwrap();let mut each_str = None;for attr in &field.attrs {if !attr.path().is_ident("builder") {continue;}let _=attr.parse_nested_meta(|meta| {if meta.path.is_ident("each") {let arg = meta.value().unwrap();let ard_str = arg.parse::<Lit>().unwrap();if let Lit::Str(s) = ard_str {each_str = Some(s.value());}}Ok(())});}let method_name = each_str?;let method_ident = syn::Ident::new(&method_name, field_ident.span());Some(quote! {pub fn #method_ident(mut self, arg: #generic_type) -> Self {self.#field_ident.get_or_insert_with(Vec::new).push(arg);self}})
}
当然,还有另外一种方法,直接给出代码
fn get_generic_function(field: &Field, generic_type: &Type) -> Option<TokenStream2> {let field_ident = field.ident.as_ref().unwrap();for attr in &field.attrs {if !attr.path().is_ident("builder") {continue;}let metas =attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated).ok()?;for meta in metas {if let Meta::NameValue(nv) = meta {if nv.path.is_ident("each") {// 解析 `each = "method_name"`let method_ident: Ident = match nv.value {Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) => Ident::new(&s.value(), s.span()),_ => panic!("`each` 的值必须是字符串字面量"),};return Some(quote! {pub fn #method_ident(mut self, arg: #generic_type) -> Self {self.#field_ident.get_or_insert_with(Vec::new).push(arg);self}});}}}}None
}
使用了parse_args_with这个解析方法,将元数据解析成Punctuated,然后判断元数据的类型,
层层深入。
两种方法都行。
拼接
前面完成了这个派生宏Builder的需要的中间代码,现在就调用上面的函数,返回中间流,
模仿StudentBuilder拼接所有代码,具体小细节不必多说
最终所有宏实现的代码如下
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::Data::Struct;
use syn::Fields::Named;
use syn::Meta;
use syn::token::Comma;
use syn::{AngleBracketedGenericArguments, DeriveInput, Expr, ExprLit, Field, GenericArgument, Ident, Lit,PathArguments, Token, Type, TypePath, parse_macro_input, punctuated::Punctuated,
};// Vec<T>
fn get_generic_from_vec(ty: &Type) -> Option<Type> {if let Type::Path(TypePath { path, .. }) = ty {if let Some(seg) = path.segments.last() {if seg.ident == "Vec" {if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {args, ..}) = &seg.arguments{if args.len() != 1 {return None;} else {if let GenericArgument::Type(t) = args.first().unwrap() {return Some(t.clone());}}}}}}None
}
fn get_generic_function(field: &Field, generic_type: &Type) -> Option<TokenStream2> {let field_ident = field.ident.as_ref().unwrap();for attr in &field.attrs {if !attr.path().is_ident("builder") {continue;}let metas =attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated).ok()?;for meta in metas {if let Meta::NameValue(nv) = meta {if nv.path.is_ident("each") {// 解析 `each = "method_name"`let method_ident: Ident = match nv.value {Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) => Ident::new(&s.value(), s.span()),_ => panic!("`each` 的值必须是字符串字面量"),};return Some(quote! {pub fn #method_ident(mut self, arg: #generic_type) -> Self {self.#field_ident.get_or_insert_with(Vec::new).push(arg);self}});}}}}None
}
fn vec_function(field: &Field) -> Option<TokenStream2> {// 获取其中的泛型参数let generic_type = get_generic_from_vec(&field.ty);if generic_type.is_some() {get_generic_function(field, &generic_type.unwrap())} else {None}
}
// 设置builder结构体的字段,用Option包裹
fn set_builder_word(fields: &Punctuated<Field, Comma>) -> Vec<TokenStream2> {fields.iter().map(|f| {let ident = &f.ident;let ty = &f.ty;quote! {#ident: Option<#ty>}}).collect::<Vec<TokenStream2>>()
}
// 初始化builder结构体
fn init_builder(fields: &Punctuated<Field, Comma>) -> Vec<TokenStream2> {fields.iter().map(|f| {let ident = &f.ident;quote! {#ident: None}}).collect::<Vec<TokenStream2>>()
}
fn is_option(ty: &Type) -> bool {if let Type::Path(TypePath { path, .. }) = ty {if let Some(seg) = path.segments.last() {if seg.ident == "Option" {return true;}}}false
}
// 完成build方法
fn get_build(fields: &Punctuated<Field, Comma>) -> Vec<TokenStream2> {fields.iter().map(|field| {let ident = field.ident.as_ref().unwrap();let ty = &field.ty;// 检查字段类型是否为 Option<T>let ok=is_option(ty);if ok {// 对于 Option<T> 字段,允许未设置,默认为 Nonequote! {#ident: self.#ident.unwrap_or(None)}} else {// 对于非 Option<T> 字段,要求必须设置quote! {#ident: self.#ident.ok_or_else(|| format!("Field `{}` is not set", stringify!(#ident))).unwrap()}}}).collect::<Vec<TokenStream2>>()
}
fn get_normal_method(field: &Field, ty: &Type) -> TokenStream2 {let word_name = field.ident.clone().unwrap();quote! {pub fn #word_name(mut self, value: #ty) -> Self{self.#word_name=Some(value);self}}
}
#[proc_macro_derive(Builder, attributes(builder))]
pub fn builder(input: TokenStream) -> TokenStream {let input: DeriveInput = parse_macro_input!(input as DeriveInput);// 获取字段let fields = match &input.data {Struct(s) => match &s.fields {Named(fsd) => fsd.named.clone(),_ => panic!("Builder derive macro can only be used on structs"),},_ => panic!("Builder derive macro can only be used on structs"),};// namebuilderlet name = input.ident;let name_builder = Ident::new(&format!("{}Builder", name), name.span());// 生成方法let fields_clone = fields.clone();// 将字段变成builder中对应的函数let word_name_vec = fields_clone.into_iter().map(|field| {let arg_method = vec_function(&field);let ty = &field.ty;if arg_method.is_some() {arg_method.unwrap()} else {get_normal_method(&field, ty)}}).collect::<Vec<proc_macro2::TokenStream>>();// builder字段let builder_word = set_builder_word(&fields);// 初始化builder字段let init_builder = init_builder(&fields);// 完成builder方法let build_res = get_build(&fields);quote! {impl #name {pub fn builder() -> #name_builder {#name_builder::new()}}pub struct #name_builder{#(#builder_word),*}impl #name_builder {#(#word_name_vec)*pub fn new()->Self{Self {#(#init_builder),*}}pub fn build(self) -> Option<#name>{Some(#name{#(#build_res),*})}}}.into()
}
总共209行,还是比较多的。
实际上,就是一些简单和细节东西的重复,不必多说,就这样。
测试一下
新建一个binary crate,导入宏所在的create。
测试,结果如下
warning: fields `args` and `current_dir` are never read
--> src\main.rs:7:5
|
4 | pub struct Command {
| ------- fields in this struct
...
7 | args: Vec<String>,
| ^^^^
8 | current_dir: Option<String>,
| ^^^^^^^^^^^
|
= note: `#[warn(dead_code)]` on by defaultwarning: `syn-insert` (bin "syn-insert") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
Running `target\debug\syn-insert.exe`
运行成功,没问题,哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈。
最后,既然都已经生成了命令,不妨试试执行一下,这里不必废话,使用windows api
代码如下
#![windows_subsystem = "console"]
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::ptr::{null, null_mut};
use winapi::um::processthreadsapi::{CreateProcessW, LPSTARTUPINFOW, PROCESS_INFORMATION, STARTUPINFOW};
use winapi::um::winbase::{CREATE_NEW_CONSOLE, CREATE_UNICODE_ENVIRONMENT};
use winapi::um::winnt::WCHAR;use derive_builder::Builder;#[derive(Builder)]
#[allow(dead_code)]
pub struct MyCommand {executable: String,#[builder(each = "arg")]args: Vec<String>,current_dir: Option<String>,
}
fn to_wide(s: &str) -> Vec<WCHAR> {OsStr::new(s).encode_wide().chain(std::iter::once(0)).collect()
}
fn main() {let command = MyCommand::builder().executable("node".to_owned()).arg("-v".to_owned()).arg("&pause".to_owned()).build().unwrap();let cmdline = format!("{} {}", command.executable, command.args.join(" "));unsafe {// 1. 构造命令行// CreateProcessW 要求“可写”字符串,且把程序名+参数放一起let mut cmdline = to_wide(cmdline.as_str());let mut si: STARTUPINFOW = std::mem::zeroed();si.cb = size_of::<STARTUPINFOW>() as u32;// 如果想让子进程输出到当前控制台,把下面标志去掉即可;// 这里演示新建一个独立窗口。si.dwFlags = 0;let mut pi: PROCESS_INFORMATION = std::mem::zeroed();// 2. 真正调用 Windows APIlet ok = CreateProcessW(null(), // lpApplicationNamecmdline.as_mut_ptr(), // lpCommandLinenull_mut(), // lpProcessAttributesnull_mut(), // lpThreadAttributes0, // bInheritHandlesCREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE,null_mut(), // lpEnvironmentnull(), // lpCurrentDirectory&mut si as LPSTARTUPINFOW,&mut pi,);if ok == 0 {eprintln!("CreateProcessW failed, code={}",winapi::um::errhandlingapi::GetLastError());return;}winapi::um::handleapi::CloseHandle(pi.hProcess);winapi::um::handleapi::CloseHandle(pi.hThread);}
}
执行后,结果如下
可以,哈哈哈哈哈
总结
笔者人已经麻了,里面太多东西,各种对类型的判断。
终于成功了。
可能不是很完美,就这样吧。