当前位置: 首页 > news >正文

【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";// 函数名叫greet
  if(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函数,结果返回给了前端。

原来如此。

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

相关文章:

  • 【C++篇】C++入门基础(二)
  • Web3.0合约安全:重入攻击防御方案
  • JavaScript 中Object.assign()和展开运算符在对象合并时的区别,各自的适用场景是什么?
  • Spring与Mybatis整合
  • DeepSeek(14):DeepSeek 模型微调
  • Go 语言标准库中sort模块详细功能介绍与示例
  • 为什么 PPO 概率计算适合连续动作
  • 办公网络健康监控(域名健康监控)
  • 六十天前端强化训练之第三十二天之Babel 转译配置大师级深度讲解
  • Python实现单因素方差分析
  • 构建大语言模型应用:简介(第一部分)
  • vs 2022安装指南
  • 深入解析 TypeScript 核心配置文件 tsconfig.json
  • 本地AI大模型部署革命:Ollama部署和API调试教程
  • 【JavaEE】Mybatis 动态SQL
  • ctfshow-web入门-特定函数绕过(web396-web405)
  • 剑指Offer62 -- 约瑟夫环
  • 黑盒测试的概念和特点
  • JAVA SE :认识数组
  • C#中,什么是委托,什么是事件及它们之间的关系
  • 杭州制作公司网站/山东seo网络推广
  • 全国网站制作公司/今日热点新闻视频
  • 网站开发新加坡/厦门搜索引擎优化
  • 门户网站建设注意事项/湖南企业seo优化报价
  • 嘉兴网络项目建站公司/爱站网关键词密度
  • 网站必须备案/查询域名网站