Gobject tutorial 六

news2024/11/19 1:18:37

Instantiatable classed types Initialization and destruction

类型的实例化是通过函数g_tpye_create_instance()实现的。这个函数首先会查找与类型相关的GTypeInfo结构体,之后,查询结构体中的instance_size和 instance policy即 n_preallocs(在 2.10版本后废弃)。

如果正在创建的实例时此对象的第一个实例,类型系统必须为对象类结构体分配空间并进行初始化。在初始化时,类结构体的第一部分数据是通过复制此类的父类的结构体。类结构体的其余数据赋值为0.如果对象的类无父类,则对象的类结构体初始化为0.之后,类型系统从最顶端的基础对象开始,根据继承关系,直到现在正在实例化的对象为止,顺序调用base_init,接着,调用对象的class_init来完成类结构的初始化,最后初始化接口。

一旦类型系统初始化完成一个类结构,它就会将实例化的对象的类指针指向这个完成初始化的类结构。这就实现了多个实例化对象共用一个初始化类结构体。之后,会调用对象的instance_init函数,调用顺序是从最顶端的基础对象开始,根据继承关系,直到正在实例化的对象。

对象实例化的销毁,是通过函数g_type_free_instance()实现的。它的功能很简单:它会将实例结构体返还到实例池(这是池是根据n_preallocs分配的)。如果这个实例是一个对象的最后一个实例,那么,当实例销毁时,此对象的类初始化结构体也会被销毁。

类的销毁过程与类结构体初始化过程是对称的。即,先销毁接口,接着调用对象的class_finalize,最后,按照与调用class_init相反的顺序调用base_finalize。

过程如下图所示:

Non-instantiatable classed types: interfaces

interface与抽象类相似,只不过,interface中定义的通用API能被没有继承关系的类使用。它定义了一组方法,在Glib中,我们称这组方法为虚函数,这些方法是由实现接口的类来实现的。不同的类可以有相同的接口方法集。比如,CD播放器、MP3等设备上都有播放、暂停和停止按钮。此时,我们可以认为播放、暂停和停止属于接口中的方法集。

那么,接口是怎么定义以及使用的呢?下面我们举例说明,值得注意的是,接口的定义只需要一个数据结构,而不像对象,需要定义实例和类这两个数据结构,且我们定义的接口的数据结构第一个成员必须为GTypeInterface。GTypeInterface是所有接口的基类。其他成员还有接口函数指针。

我们实现的接口名为TComparable, 接口中方法为cmp。

/*tcomparable.h*/
#pragma once

#include <glib-object.h>

#define T_TYPE_COMPARABLE  (t_comparable_get_type ())
/*G_DECLARE_INTERFACE (TComparable, t_comparable, T, COMPARABLE, GObject)*/
GType t_comparable_get_type (void);
typedef struct _TComparable TComparable;
typedef struct _TComparableInterface TComparableInterface;
#define T_COMPARABLE(instance)           (G_TYPE_CHECK_INSTANCE_CAST(instance, T_TYPE_COMPARABLE, TComparable))
#define T_IS_COMPARABLE(instance)        (G_TYPE_CHECK_INSTANCE_TYPE(instance, T_TYPE_COMPARABLE))
#define T_COMPARABLE_GET_IFACE(instance) (G_TYPE_INSTANCE_GET_INTERFACE((instance), T_TYPE_COMPARABLE, TComparableInterface))  

struct _TComparableInterface {
  GTypeInterface parent;
  /* signal */
  void (*arg_error) (TComparable *self);
  /* virtual function 将由实现接口的类的方法来覆盖*/
  int (*cmp) (TComparable *self, TComparable *other);
};

/* t_comparable_cmp */
/* if self > other, then returns 1 */
/* if self = other, then returns 0 */
/* if self < other, then returns -1 */
/* if error happens, then returns -2 */

int
t_comparable_cmp (TComparable *self, TComparable *other);

gboolean
t_comparable_eq (TComparable *self, TComparable *other);

gboolean
t_comparable_gt (TComparable *self, TComparable *other);

gboolean
t_comparable_lt (TComparable *self, TComparable *other);

gboolean
t_comparable_ge (TComparable *self, TComparable *other);

gboolean
t_comparable_le (TComparable *self, TComparable *other);
/*tcomparable.c*/
#include "tcomparable.h"

static guint t_comparable_signal;

static void
t_comparable_default_init (TComparableInterface *iface);
GType
t_comparable_get_type (void) {
  static GType type = 0;
  GTypeInfo info;

  if (type == 0) {
    info.class_size = sizeof (TComparableInterface);
    info.base_init = NULL;
    info.base_finalize = NULL;
    info.class_init = (GClassInitFunc)  t_comparable_default_init;
    info.class_finalize = NULL;
    info.class_data = NULL;
    info.instance_size = 0;
    info.n_preallocs = 0;
    info.instance_init = NULL;
    info.value_table = NULL;
    type = g_type_register_static (G_TYPE_INTERFACE, "TComparable", &info, 0);
  }
  return type;
}

static void
arg_error_default_cb (TComparable *self) {
  g_printerr ("\nTComparable: argument error.\n");
}

static void
t_comparable_default_init (TComparableInterface *iface) {
  /* virtual function */
  iface->cmp = NULL;
  /* argument error signal */
  iface->arg_error = arg_error_default_cb;
  t_comparable_signal =
  g_signal_new ("arg-error",
                T_TYPE_COMPARABLE,
                G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                G_STRUCT_OFFSET (TComparableInterface, arg_error),
                NULL /* accumulator */,
                NULL /* accumulator data */,
                NULL /* C marshaller */,
                G_TYPE_NONE /* return_type */,
                0     /* n_params */,
                NULL  /* param_types */);
}

int
t_comparable_cmp (TComparable *self, TComparable *other) {
  g_return_val_if_fail (T_IS_COMPARABLE (self), -2);

  TComparableInterface *iface = T_COMPARABLE_GET_IFACE (self);

  return (iface->cmp == NULL ? -2 : iface->cmp (self, other));
}

gboolean
t_comparable_eq (TComparable *self, TComparable *other) {
  return (t_comparable_cmp (self, other) == 0);
}

gboolean
t_comparable_gt (TComparable *self, TComparable *other) {
  return (t_comparable_cmp (self, other) == 1);
}

gboolean
t_comparable_lt (TComparable *self, TComparable *other) {
  return (t_comparable_cmp (self, other) == -1);
}

gboolean
t_comparable_ge (TComparable *self, TComparable *other) {
  int result = t_comparable_cmp (self, other);
  return (result == 1 || result == 0);
}

gboolean
t_comparable_le (TComparable *self, TComparable *other) {
  int result = t_comparable_cmp (self, other);
  return (result == -1 || result == 0);
}

 到此,我们完成了对接口TComparable的定义。下面,我们来说明如何在各个类中使用,以及接口的虚拟函数是如何被各个类的函数覆盖的。

/*tint.c*/
#include "../tnumber/tnumber.h"
#include "../tnumber/tint.h"
#include "../tnumber/tdouble.h"
#include "tcomparable_without_macro.h"

enum {
  PROP_0,
  PROP_INT,
  N_PROPERTIES
};

static GParamSpec *int_properties[N_PROPERTIES] = {NULL, };

struct _TInt {
  TNumber parent;
  int value;
};

static void t_comparable_interface_init (TComparableInterface *iface);

static void
t_int_class_init (TIntClass *class);
static void
t_int_init (TInt *self);

GType 
t_int_get_type (void)
{
  static GType type = 0;
  if (type == 0) {
    const GTypeInfo info = {
      sizeof (TIntClass),
      NULL,   /* base_init */
      NULL,   /* base_finalize */
      (GClassInitFunc) t_int_class_init,   /* class_init */
      NULL,   /* class_finalize */
      NULL,   /* class_data */
      sizeof (TInt),
      0,      /* n_preallocs */
      (GInstanceInitFunc) t_int_init,    /* instance_init */
      NULL /* value table */
    };
    const GInterfaceInfo comparable_info = {
      (GInterfaceInitFunc) t_comparable_interface_init,  /* interface_init */
      NULL,   /* interface_finalize */
      NULL    /* interface_data */
    };
    type = g_type_register_static (T_TYPE_NUMBER, "TInt", &info, 0);
    g_type_add_interface_static (type, T_TYPE_COMPARABLE, &comparable_info);
  }
  return type;
}

static int
t_int_comparable_cmp (TComparable *self, TComparable *other) {
  if (! T_IS_NUMBER (other)) {
    g_signal_emit_by_name (self, "arg-error");
    return -2;
  }

  int i;
  double s, o;

  s = (double) T_INT (self)->value;
  if (T_IS_INT (other)) {
    g_object_get (other, "value", &i, NULL);
    o = (double) i;
  } else
    g_object_get (other, "value", &o, NULL);
  if (s > o)
    return 1;
  else if (s == o)
    return 0;
  else if (s < o)
    return -1;
  else /* This can't happen. */
    return -2;
}

static void
t_comparable_interface_init (TComparableInterface *iface) {
  iface->cmp = t_int_comparable_cmp;
}

static void
t_int_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
  TInt *self = T_INT (object);

  if (property_id == PROP_INT)
    self->value = g_value_get_int (value);
  else
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
t_int_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
  TInt *self = T_INT (object);

  if (property_id == PROP_INT)
    g_value_set_int (value, self->value);
  else
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
t_int_init (TInt *self) {
}

/* arithmetic operator */
/* These operators create a new instance and return a pointer to it. */
#define t_int_binary_op(op) \
  int i; \
  double d; \
  if (T_IS_INT (other)) { \
    g_object_get (T_INT (other), "value", &i, NULL); \
    return  T_NUMBER (t_int_new_with_value (T_INT(self)->value op i)); \
  } else { \
    g_object_get (T_DOUBLE (other), "value", &d, NULL); \
    return  T_NUMBER (t_int_new_with_value (T_INT(self)->value op (int) d)); \
  }

static TNumber *
t_int_add (TNumber *self, TNumber *other) {
  g_return_val_if_fail (T_IS_INT (self), NULL);

  t_int_binary_op (+)
}

static TNumber *
t_int_sub (TNumber *self, TNumber *other) {
  g_return_val_if_fail (T_IS_INT (self), NULL);

  t_int_binary_op (-)
}

static TNumber *
t_int_mul (TNumber *self, TNumber *other) {
  g_return_val_if_fail (T_IS_INT (self), NULL);

  t_int_binary_op (*)
}

static TNumber *
t_int_div (TNumber *self, TNumber *other) {
  g_return_val_if_fail (T_IS_INT (self), NULL);

  int i;
  double d;

  if (T_IS_INT (other)) {
    g_object_get (T_INT (other), "value", &i, NULL);
    if (i == 0) {
      g_signal_emit_by_name (self, "div-by-zero");
      return NULL;
    } else
      return  T_NUMBER (t_int_new_with_value (T_INT(self)->value / i));
  } else {
    g_object_get (T_DOUBLE (other), "value", &d, NULL);
    if (d == 0) {
      g_signal_emit_by_name (self, "div-by-zero");
      return NULL;
    } else
      return  T_NUMBER (t_int_new_with_value (T_INT(self)->value / (int)  d));
  }
}

static TNumber *
t_int_uminus (TNumber *self) {
  g_return_val_if_fail (T_IS_INT (self), NULL);

  return T_NUMBER (t_int_new_with_value (- T_INT(self)->value));
}

static char *
t_int_to_s (TNumber *self) {
  g_return_val_if_fail (T_IS_INT (self), NULL);

  int i;

  g_object_get (T_INT (self), "value", &i, NULL); 
  return g_strdup_printf ("%d", i);
}

static void
t_int_class_init (TIntClass *class) {
  TNumberClass *tnumber_class = T_NUMBER_CLASS (class);
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);

  /* override virtual functions */
  tnumber_class->add = t_int_add;
  tnumber_class->sub = t_int_sub;
  tnumber_class->mul = t_int_mul;
  tnumber_class->div = t_int_div;
  tnumber_class->uminus = t_int_uminus;
  tnumber_class->to_s = t_int_to_s;

  gobject_class->set_property = t_int_set_property;
  gobject_class->get_property = t_int_get_property;
  int_properties[PROP_INT] = g_param_spec_int ("value", "val", "Integer value", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE);
  g_object_class_install_properties (gobject_class, N_PROPERTIES, int_properties);
}

TInt *
t_int_new_with_value (int value) {
  TInt *i;

  i = g_object_new (T_TYPE_INT, "value", value, NULL);
  return i;
}

TInt *
t_int_new (void) {
  TInt *i;

  i = g_object_new (T_TYPE_INT, NULL);
  return i;
}

tstr.c、tdouble.c与tint.c使用Tcompararble的方式相同。到此,数据类型都定义完成,接下来说明,在应用程序中如何使用TComparable。

#include <glib-object.h>
#include "tcomparable_without_macro.h"
#include "../tnumber/tnumber.h"
#include "../tnumber/tint.h"
#include "../tnumber/tdouble.h"
#include "../tstr/tstr.h"

static void
t_print (const char *cmp, TComparable *c1, TComparable *c2) {
  char *s1, *s2;
  TStr *ts1, *ts2, *ts3;

  ts1 = t_str_new_with_string("\"");
  if (T_IS_NUMBER (c1))
    s1 = t_number_to_s (T_NUMBER (c1));
  else if (T_IS_STR (c1)) {
    ts2 = t_str_concat (ts1, T_STR (c1));
    ts3 = t_str_concat (ts2, ts1);
    s1 = t_str_get_string (T_STR (ts3));
    g_object_unref (ts2);
    g_object_unref (ts3);
  } else {
    g_print ("c1 isn't TInt, TDouble nor TStr.\n");
    return;
  }
  if (T_IS_NUMBER (c2))
    s2 = t_number_to_s (T_NUMBER (c2));
  else if (T_IS_STR (c2)) {
    ts2 = t_str_concat (ts1, T_STR (c2));
    ts3 = t_str_concat (ts2, ts1);
    s2 = t_str_get_string (T_STR (ts3));
    g_object_unref (ts2);
    g_object_unref (ts3);
  } else {
    g_print ("c2 isn't TInt, TDouble nor TStr.\n");
    return;
  }
  g_print ("%s %s %s.\n", s1, cmp, s2);
  g_object_unref (ts1);
  g_free (s1);
  g_free (s2);
}    

static void
compare (TComparable *c1, TComparable *c2) {
  if (t_comparable_eq (c1, c2))
    t_print ("equals", c1, c2);
  else if (t_comparable_gt (c1, c2))
    t_print ("is greater than", c1, c2);
  else if (t_comparable_lt (c1, c2))
    t_print ("is less than", c1, c2);
  else if (t_comparable_ge (c1, c2))
    t_print ("is greater than or equal to", c1, c2);
  else if (t_comparable_le (c1, c2))
    t_print ("is less than or equal to", c1, c2);
  else
    t_print ("can't compare to", c1, c2);
}

int
main (int argc, char **argv) {
  const char *one = "one";
  const char *two = "two";
  const char *three = "three";
  TInt *i;
  TDouble *d;
  TStr *str1, *str2, *str3;

  i = t_int_new_with_value (124);
  d = t_double_new_with_value (123.45);
  str1 = t_str_new_with_string (one);
  str2 = t_str_new_with_string (two);
  str3 = t_str_new_with_string (three);

  compare (T_COMPARABLE (i), T_COMPARABLE (d));
  compare (T_COMPARABLE (str1), T_COMPARABLE (str2));
  compare (T_COMPARABLE (str2), T_COMPARABLE (str3));
  compare (T_COMPARABLE (i), T_COMPARABLE (str1));

  g_object_unref (i);
  g_object_unref (d);
  g_object_unref (str1);
  g_object_unref (str2);
  g_object_unref (str3);

  return 0;
}

Interface initialization

与abstract class类似,初始化时,会为接口结构分配空间,之后将父接口结构复制进来,如果没有父接口结构,则新分配的结构会被初始化为0。之后将g_type设置为正在初始化的接口类型,g_instance_type 设置为此正在实现此接口的对象类型。接着调用接口结构中base_init、default_init(class_int)、接口实现时所在类型的interface_init(例如Tint中的 t_compare_interface_init).如果同一个接口在很多类型中都存在实现(例如,Tcomparable在Tint,Tstr中都有实现),那么,接口实现所在的每个类型在初始化过程中,当轮到接口初始化时,接口中的base_init、interface_init都要执行一次,然而,default_init却只执行一次。因此,对接口的初始化一般放在default_init中,就如上例中的t_comparable_default_init中设置信号那样。

过程及说明如下表所示:

 Interface Destruction

当接口实现所在类型的最后一个实例化(如TInt的实例化)销毁时,与此类型相关的接口的实现也会销毁。

在销毁接口的实现时,类型系统GType会首先调用接口实现的interface_finalize之后调用接口的base_finalize。同样的每个接口实现所在类型在销毁时,这两个函数都被被调用一次。当所有接口实现所在类的实例都被销毁时,接口类型才会被销毁。

过程及说明如下表所示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1838641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nuxt3页面开发实战探索

title: Nuxt3页面开发实战探索 date: 2024/6/19 updated: 2024/6/19 author: cmdragon excerpt: 摘要&#xff1a;这篇文章是关于Nuxt3页面开发实战探索的。它介绍了Nuxt3的基础入门&#xff0c;安装与配置&#xff0c;项目结构&#xff0c;内置组件与功能&#xff0c;以及页…

持续集成jenkins+gitee

首先要完成gitee部署&#xff0c;详见自动化测试git的使用-CSDN博客 接下来讲如何从git上自动拉取代码&#xff0c;实现jenkins无人值守&#xff0c;定时执行测试&#xff0c;生成测试报告。 需要这三个安装包 由于目前的jenkins需要至少java11到java17的版本&#xff0c;所以…

深度解析消费者最关心的车联网核心问题

随着科技的迅猛发展&#xff0c;车联网&#xff08;V2X&#xff09;或智能网联汽车成为了提供车辆非视距信息的独特解决方案。它们是传感器技术的关键补充&#xff0c;通过车联网&#xff08;V2X&#xff09;&#xff0c;交通工具可以与其他车辆或基础设施进行信息交流。车联网…

upload-labs第十三关教程

upload-labs第十三关教程 第十三关一、源代码分析代码审计 二、绕过分析1&#xff09;0x00绕过a.上传eval.pngb.使用burpsuite进行拦截修改之前&#xff1a;修改之后&#xff1a;进入hex模块&#xff1a; c.放包上传成功&#xff1a; d.使用中国蚁剑进行连接 2&#xff09;%00绕…

Java 打包编译、运行报错

无法访问com.sun.beans.introspect.PropertyInfo-CSDN博客 [ERROR] COMPILATION ERROR : [INFO] ------------------------------------------------------------- [ERROR] sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerChangeCon…

若依框架自定义开发使用学习笔记(1)

因为我是跳着学的&#xff0c;原理那些都没咋看。 代码自动生成&#xff0c;依赖sql表 在ruoyi数据库中&#xff0c;创建你想要的表&#xff0c;这里我创建了个购物车表&#xff0c;由于空间有限&#xff0c;只能拍到这么多。 然后就可以在前端自动生成代码 点击导入按钮 …

华为---- RIP路由协议基本配置

08、RIP 8.1 RIP路由协议基本配置 8.1.1 原理概述 RIP(Routing Information Protocol,路由协议)作为最早的距离矢量IP路由协议&#xff0c;也是最先得到广泛使用的一种路由协议&#xff0c;采用了Bellman-Ford算法&#xff0c;其最大的特点就是配置简单。 RIP协议要求网络中…

如何学习 Java 中的 Socket 编程,进行网络通信

Socket编程是网络编程的核心技术之一&#xff0c;它使得不同主机之间可以进行数据通信。Java提供了丰富的网络编程API&#xff0c;使得编写网络应用程序变得相对简单和直观。本文将详细讲解如何学习Java中的Socket编程&#xff0c;并通过示例代码展示如何实现网络通信。 一、S…

特征标注——OpenCV

特征标注 导入必要的库创建窗口显示原始图片和标注后的图片存储用户选择的图片路径字体样式和大小定义了select_image函数定义了annotate_landmarks()函数设置按钮调整图片标签的位置设置图片位置主事件循环运行显示&#xff1a;全部代码 导入必要的库 import tkinter as tk: 导…

细说MCU输出互补型PWM波形时设置死区时间的作用

目录 一、工程背景 二、死区时间的作用 一、工程背景 在作者的文章里建立工程时&#xff0c;为配置输出互补型PWM波形曾经设置了死区时间&#xff0c;DEAD100个定时器的时间周期&#xff08;简称实例1&#xff09;&#xff1a;细说MCU输出互补型PWM波形的实现方法-CSDN博客 …

【Python教程】如何搭建一个高效的Python开发环境?结尾附安装包直通车

前言&#xff1a; Python 丰富的函数库和组件库是这门语言强大的核心原因&#xff01;但我们不可能去记忆所有的方法名和参数名&#xff0c;往往只能记住一些常用的或者某个方法开头的几个字母。这个时候一个好的开发工具就需要能聪明地“猜”出你想输入的代码&#xff0c;并给…

数据结构基础(基于c++)

数据结构基础&#xff08;基于c&#xff09; 文章目录 数据结构基础&#xff08;基于c&#xff09;前言1. 递归、迭代、时间复杂度、空间复杂度2. 数据结构 数组与链表1. 数组2. 链表3. 动态数组4. 数组与链表对比 前言 参考资料&#xff1a;Hello 算法 (hello-algo.com) 1. 递…

20240619每日小程序-------朋友想开发微信小程序,那就搞一把demo

下载开发工具 hbuildX 微信开发者工具 随便搞个开源项目 会员小程序 下载后导入到hbuildX 安装依赖 npm i 安装hbuildX插件 工具—》插件安装 推荐安装&#xff1a; 微信小程序一键打包插件sass编译 启动 选择5.用微信开发者工具启动 报错不要怕 比如&#xff1a…

【车载开发系列】IIC总线协议时序图

【车载开发系列】IIC总线协议时序图 【车载开发系列】IIC总线协议时序图 【车载开发系列】IIC总线协议时序图一、前言二、IIC硬件软件实现1&#xff09;使用I2C控制器实现2&#xff09;使用GPIO通过软件模拟实现 三、I2C协议标准代码1&#xff09;起始信号2&#xff09;停止信号…

判断对称二叉树/判断相同的数-二叉树

都利用递归&#xff0c;思路相似&#xff1b; 对称二叉树就是两个相同的二叉树&#xff0c;但是子节点是right left因为对称&#xff1b; 101. 对称二叉树 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool isSymmetric(TreeNode* root) {TreeNode* rt…

【C语言】解决C语言报错:Null Pointer Dereference

文章目录 简介什么是Null Pointer DereferenceNull Pointer Dereference的常见原因如何检测和调试Null Pointer Dereference解决Null Pointer Dereference的最佳实践详细实例解析示例1&#xff1a;未初始化的指针示例2&#xff1a;释放内存后未将指针置为NULL示例3&#xff1a;…

市值飙升!超微软、苹果,英伟达成为全球市值最高上市公司

KlipC报道&#xff1a;当地时间6月18日&#xff0c;英伟达股价再度大涨&#xff0c;盘后股价上涨3.51%&#xff0c;总市值达3.335万亿美元&#xff0c;报135.58美元再刷历史新高&#xff0c;超微软、苹果成为全球市值最高的上市公司。 值得一提的是&#xff0c;在本月初&#x…

hackbar插件安装教程

目录 HackBar 插件简介 下载 Firefox浏览器&#xff08;火狐&#xff09;安装 2.1.3版本 2.5.3版本 使用 chrome浏览器&#xff08;谷歌&#xff09;安装 方法1&#xff1a;开发者模式拖安装包 激活方式&#xff1a; 方法2&#xff1a;从 Chrome 应用商店 HackBar 插件…

【推荐100个unity插件之22】基于UGUI的功能强大的简单易用的Unity数据可视化图表插件——XCharts3.0插件的使用

效果 文章目录 效果前言特性截图基础介绍插件信息5分钟上手 XCharts 3.0实例创建一个默认的折线图代码修改显示的值 推荐完结 前言 unity怎么绘制图表&#xff1f;这是最近最常听到的问题。这次就介绍一款基于UGUI的功能强大的简单易用的Unity数据可视化图表插件——XCharts3.…

LuxTrust、契约锁联合启动中欧两地跨境电子签服务

6月18日&#xff0c;欧洲领先的数字身份和电子签名厂商-LuxTrust、全球领先的数字化技术和服务的提供商-浩鲸科技一行莅临契约锁上海总部&#xff0c;并于当日下午联合举行“跨境签战略合作”现场签约仪式。 三方将以此次合作为契机&#xff0c;发挥各自领域专业优势&#xff…