欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 文化 > 【Tauri2】005——tauri::command属性与invoke函数

【Tauri2】005——tauri::command属性与invoke函数

2025/9/19 4:47:20 来源:https://blog.csdn.net/qq_63401240/article/details/146581991  浏览:    关键词:【Tauri2】005——tauri::command属性与invoke函数

前言

【Tauri2】004——run函数的简单介绍(2)-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146512646?spm=1001.2014.3001.5502

前面介绍了run函数,接下来介绍这个属性tauri::command,被这个属性修饰的函数,可以被注册为通信函数。invoke也一起介绍。

代码如下

#[tauri::command]
fn greet(name: &str) -> String {format!("Hello, {}! You've been greeted from Rust!", name)
}

正文

简单地一次通信

上面greet函数的意思是

接受参数name,返回字符串。

尝试发送消息,运行pnpm run tauri dev

输入123

 显示了Hello, 123! You've been greeted from Rust!

而和greet函数返回的内容一致,完成了通信。

在前端代码中,可以发现这样一行代码

   await invoke("greet", { name })

 其中第一个参数是字符串,而这个字符串恰好也是Rust的通信函数greet

第二个参数是name,而这个name恰好是greet函数的参数。

有点意思

发现

先暂时说一些其他东西。

笔者希望打个断点,可以调试代码,说实话,Tauri中使用了许多的宏

而宏是在编译的早期,对Tauri使用的依赖进行打断点,或者修改源码,感觉没有什么用,很苦恼,后来我明白了,Tauri进行了build之后,修改项目(start),会在已经build好的基础上,进行build

如果调用cargo clean,修改源码,进行一些打印语句,需要从头开始build,很浪费时间,很慢。

笔者慢慢尝试和思考

在build后,target/tubug目录下

deps目录拥有许多东西,什么dll,什么exe,都在这里面,tauri的build后的依赖也都在里面。

 如果删除某个依赖,使用cargo build命令,不会从0开始build,而是从删除的依赖作为起点,开始build

这一点非常关键,既节约了时间,也可以查看build的产物,并且能够修改源码了,哈哈哈哈

开始

ctrl+左击command,这个属性的定义

#[proc_macro_attribute]
pub fn command(attributes: TokenStream, item: TokenStream) -> TokenStream {command::wrapper(attributes, item)
}

从proc_macro_attribute可以发现是属性宏,而且,这个属性定义在tauri-macros

而在tauri-macros,主要是对宏的操作,使用了两个关键的依赖,syn和quote。

继续点击wrapper中

pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream 

这个wrapper就是command实现功能的关键。

如何操作

笔者第一次build完成后,笔者进入target/debug/deps中

因为笔者知道是tauri-macros依赖,因此,笔者搜索tauri-macros,

经过多次的尝试后,发现

删除类型应用程序扩展,进行cargo build

就是从tauri-macros开始的,实际上可以全部删了。

pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream

首先,在源码增加一些打印语句,然后删除。

  let attr=attributes.clone();let items= item.clone();let mut attrs = parse_macro_input!(attributes as WrapperAttributes);let function = parse_macro_input!(item as ItemFn);let isStart = function.sig.ident == "greet";// 函数名叫greetif(isStart){println!("attributes: {}",attr);println!("______________________");println!("item: {}",items);println!("______________________");}

打包,看看attributes和item是什么东西,结果如下

可以看出attribute是空的,而item就是command修饰的函数本身

代码模板

接下来看看,wrapper是如何处理这个函数的,在wrapper最后,可以看到代码模板。

中间过程都是些看不懂的操作,总之,一定是为代码模板服务的

因此,直接看代码模板

下面就是代码模板

  let result=quote!(#async_command_check#maybe_allow_unused#function#maybe_allow_unused#maybe_macro_export#[doc(hidden)]macro_rules! #wrapper {// double braces because the item is expected to be a block expression($path:path, $invoke:ident) => {{#[allow(unused_imports)]use #root::ipc::private::*;// prevent warnings when the body is a `compile_error!` or if the command has no arguments#[allow(unused_variables)]let #root::ipc::Invoke { message: #message, resolver: #resolver, acl: #acl } = $invoke;#maybe_span#body}};}// allow the macro to be resolved with the same path as the command function#[allow(unused_imports)]#visibility use #wrapper;);

添加打印语句,看看经过代码模板后的函数长什么样的

结果如下

macro_rules! __cmd__greet
{($path : path, $invoke : ident) =>{{#[allow(unused_imports)] use :: tauri :: ipc :: private :: * ;#[allow(unused_variables)] let :: tauri :: ipc :: Invoke{message : __tauri_message__, resolver : __tauri_resolver__,acl : __tauri_acl__} = $invoke; let result =$path(match :: tauri :: ipc :: CommandArg ::from_command(:: tauri :: ipc :: CommandItem{plugin : :: core :: option :: Option :: None, name :stringify! (greet), key : "name", message : &__tauri_message__, acl : & __tauri_acl__,}){Ok(arg) => arg, Err(err) =>{ __tauri_resolver__.invoke_error(err); return true },}); let kind = (& result).blocking_kind();kind.block(result, __tauri_resolver__); return true;}};
} #[allow(unused_imports)] use __cmd__greet;

这段代码,笔者也是感到非常迷惑。


解释代码

尽力尝试一下,首先

macro_rules! __cmd__greet

这是定义了一个声明宏

Macros By Example - The Rust Referencehttps://rustwiki.org/en/reference/macros-by-example.html而且,声明宏的名字是__cmd__greet,而greet的函数的名字。

build后在RustRover中可以看到


 ($path : path, $invoke : ident)

$path:路径,很明显,是指函数的路径,即crate::greet

$invoke:是ident对象,这个和前端的invoke函数,笔者直觉判断,必然是有关系的。

#[allow(unused_imports)] use :: tauri :: ipc :: private :: * ;
#[allow(unused_variables)] let :: tauri :: ipc :: Invoke

这两行就很简单,前面的两个#,第一个的意思是允许未使用的导入,后面是导入的东西,第二个的意思是允许未使用的变量Invoke

看看这个Invoke到底是什么

代码如下

#[default_runtime(crate::Wry, wry)]
pub struct Invoke<R: Runtime> {/// The message passed.pub message: InvokeMessage<R>,/// The resolver of the message.pub resolver: InvokeResolver<R>,/// Resolved ACL for this IPC invoke.pub acl: Option<Vec<ResolvedCommand>>,
}

先不管, 继续往下看

     {message : __tauri_message__, resolver : __tauri_resolver__,acl : __tauri_acl__} = $invoke

 这个$invoke,不就是前面宏的参数吗,前面加个{},再加上面的定义,很明显地断言

解包。

let result =$path(match :: tauri :: ipc :: CommandArg ::from_command(:: tauri :: ipc :: CommandItem{plugin : :: core :: option :: Option :: None, name :stringify! (greet), key : "name", message : &__tauri_message__, acl : & __tauri_acl__,}){Ok(arg) => arg, Err(err) =>{ __tauri_resolver__.invoke_error(err); return true },}); 

乍一看,这里感觉很复杂,慢慢看

首先,可以发现match,这是Rust中都关键字,再结合下面的Ok和Err,可以判断代码就是match,处理Option类型或者Result,简写一下

let result =$path(match opt{Ok(arg) => arg, Err(err) => { __tauri_resolver__.invoke_error(err); return true },}); 

成功返回arg,其中的CommandItem比较有意思

plugin: ::core::option::Option::None,  //None的意思说明不是插件
name: stringify!(greet),     // greet是函数名,因此,把函数名变成字符串
key: "name",   // 参数name
message: &__tauri_message__, // 前端发送的原始消息{"name":"123"}
acl: &__tauri_acl__,  // 权限验证

stringify in std - Rusthttps://doc.rust-lang.org/std/macro.stringify.html

 查看一下CommandArg,

use tauri::ipc::CommandArg;
pub trait CommandArg<'de, R: Runtime>: Sized {/// Derives an instance of `Self` from the [`CommandItem`].////// If the derivation fails, the corresponding message will be rejected using [`InvokeMessage#reject`].fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError>;
}

这个from_command传入了一个CommandItem,返回Result

成功返回Self,失败报错

再看看CommandItem

pub struct CommandItem<'a, R: Runtime> {/// Name of the plugin if this command targets one.pub plugin: Option<&'static str>,/// The name of the command, e.g. `handler` on `#[command] fn handler(value: u64)`pub name: &'static str,/// The key of the command item, e.g. `value` on `#[command] fn handler(value: u64)`pub key: &'static str,/// The [`InvokeMessage`] that was passed to this command.pub message: &'a InvokeMessage<R>,/// The resolved ACL for this command.pub acl: &'a Option<Vec<ResolvedCommand>>,
}

和刚刚看到的参数一样。

from_command成功返回Self,那么可以推断Ok(arg),这个arg就是Self,

而这个Self是什么?暂时不知道

后面Ok(arg)=>arg

再经过$path(arg)返回给result。

而$path可以认为是greet

那么

$path(arg)  不就是调用函数  greet(arg) 

原来如此。在这里居然调用了通信函数,笔者明白了哈哈哈哈哈

所以,这个Self,在这个greet函数里面是指String,这是参数

let kind = (& result).blocking_kind();

kind的英文翻译是 种类。

取了对result的一个引用,blocking_kind是判断异步还是同步

 kind.block(result, __tauri_resolver__); return true;

处理结果,__tauri_resolver__返回给前端,返回true。


最后

#[allow(unused_imports)] use __cmd__greet;

 不必细说。


简单看看invoke在前端的定义

再来看看invoke的定义,使用的TS

declare function invoke<T>(cmd: string, args?: InvokeArgs, options?: InvokeOptions): Promise<T>;
interface InvokeOptions {headers: Headers | Record<string, string>;
}

 从前面Invoke的定义,发现message是InvokeMessage

代码如下

pub struct InvokeMessage<R: Runtime> {/// The webview that received the invoke message.pub(crate) webview: Webview<R>,/// Application managed state.pub(crate) state: Arc<StateManager>,/// The IPC command.pub(crate) command: String,/// The JSON argument passed on the invoke message.pub(crate) payload: InvokeBody,/// The request headers.pub(crate) headers: HeaderMap,
}

因此,可以断言

第一次参数cmd,。正是对应tauri::ipc::Invoke::message::command

第二个参数 args。正是对应tauri::ipc::Invoke::message::payload

第三个参数option。正是对应tauri::ipc::Invoke::message::headers

返回值Promise。对应于Invoke::resolver

还有一个,笔者也不知道。

总结

前端通过invoke发送数据,Tauri在创建好的声明宏__cmd__greet中,调用了greet函数,结果返回给了前端。

原来如此。

这个宏中间肯定要进行数据转化。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词