HarmonyOS Next鸿蒙NDK使用示例

news2024/9/20 1:10:22

创建一个Native C++项目

        跟普通项目相比,主要区别是多了一个cpp文件夹、oh-package.json5中的dependencies引入还有build-profile.json5中的externalNativeOptions配置,abiFilters是支持的CPU架构,目前移动端项目只支持arm64-v8a、x86_64两种。

        普通项目也可以复制以下文件/配置到对应位置变成一个Native C++项目。但要注意把一些entry的字符替换成你的module名称。如果同样是entry就不用修改。

生成一个测试用的so库

编写测试代码

        新建两个文件test_c.cpp和test_c.h,位置如下

test_c.h

#ifndef NATIVETEST_TEST_C_H
#define NATIVETEST_TEST_C_H

class test_c {
public:
    explicit test_c();
    ~test_c();

    int add(int a, int b);
    int sub(int a, int b);
};

#endif //NATIVETEST_TEST_C_H

test_c.cpp

#include "test_c.h"
test_c::test_c() {}
test_c::~test_c() {}
int test_c::add(int a, int b) {
    return a + b;
}

int test_c::sub(int a, int b) {
    return a - b;
}

修改CMakeLists.txt文件

        前面都是建项目时默认写好的语句,主要加了一行add_library(test_c SHARED test_c.cpp)

cmake_minimum_required(VERSION 3.5.0)
#…………中间省略
#第一个test_c是指将要生成的so文件名,最终的名称会变成libtest_c.so
add_library(test_c SHARED test_c.cpp)

构建so库

        点击Build -> Build Hap(s)/APP(s) -> Build Hap(s), 生成的so文件在以下目录。生成后就可以把test_c.cpp删掉了。

引用so库

复制so文件到对应目录

        在cpp目录下建一个libs文件夹,按照CPU架构把so文件放到对应的文件夹中(同时把所有.h头文件放到include文件夹中,这里因为在上个步骤中已经写好了test_c.h,所以只需放入so文件)

修改CMakeLists.txt文件

主要是加了最后一行target_link_libraries(entry PUBLIC ${NATIVERENDER_ROOT_PATH}/libs/${OHOS_ARCH}/xxxxx.so),具体so文件的名称自行修改

# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
#项目名称,创建项目时填的值
project(NativeTest)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

if(DEFINED PACKAGE_FIND_FILE)
    include(${PACKAGE_FIND_FILE})
endif()

# 添加头文件.h目录,包括cpp,cpp/include,告诉cmake去这里找到代码引入的头文件
# 一般头文件都放在cpp/include下
include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so)

#add_library(test_c SHARED test_c.cpp)
#会根据不同的架构去不同的目录下找到对应的so文件
target_link_libraries(entry PUBLIC ${NATIVERENDER_ROOT_PATH}/libs/${OHOS_ARCH}/libtest_c.so)

调用so库中的方法

        以下代码可以复制进创建项目时生成的napi_init.cpp文件中

#include "test_c.h"
// 如果是用C编写的库,需要在extern "C"{}中包裹,否则会出现链接错误
// extern "C"{
//     #include "test_c.h"
// }
#include <hilog/log.h>//日志输出所需要的库
#pragma comment(lib, "libhilog_ndk.z.so")//日志输出所需要的库
void test_demo()
{
    // 这里只是演示调用引用的so库的方法
    test_c test;
    int r1 = test.add(10, 5);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.add(10, 5) = %{public}d", r1);
    int r2 = test.sub(10, 5);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.sub(10, 5) = %{public}d", r2);
}

napi_init.cpp完整代码

#include "napi/native_api.h"
#include "test_c.h"
// 如果是用C编写的库,需要在extern "C"{}中包裹,否则会出现链接错误
// extern "C"{
//     #include "test_c.h"
// }
#include <hilog/log.h>//日志输出所需要的库
#pragma comment(lib, "libhilog_ndk.z.so")//日志输出所需要的库
void test_demo()
{
    // 这里只是演示调用引用的so库的方法
    test_c test;
    int r1 = test.add(10, 5);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.add(10, 5) = %{public}d", r1);
    int r2 = test.sub(10, 5);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.sub(10, 5) = %{public}d", r2);
}
static napi_value Add(napi_env env, napi_callback_info info)
{
    test_demo();
    size_t argc = 2;
    napi_value args[2] = {nullptr};
    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
    napi_valuetype valuetype0;
    napi_typeof(env, args[0], &valuetype0);

    napi_valuetype valuetype1;
    napi_typeof(env, args[1], &valuetype1);

    double value0;
    napi_get_value_double(env, args[0], &value0);

    double value1;
    napi_get_value_double(env, args[1], &value1);

    napi_value sum;
    napi_create_double(env, value0 + value1, &sum);

    return sum;

}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}

使用Node-API实现ArkTS与C/C++模块之间的交互

定义接口

        在index.d.ts文件中,提供ArkTS/JS侧的接口方法。

//一个同步返回的加法计算
export const add: (a: number, b: number) => number;
//一个异步返回的加法计算
export const addWithCallBack: (a: number, b: number, callBack: (result:number) => void) => number;
//各种数据类型的参数传参demo,这里只列了几个常见的
export const paramsTest: (a: number, b: string, c:boolean, d:string[], e:ArrayBuffer) => void;

接口实现

        在index.d.ts定义的接口需在napi_init.cpp有对应实现。

#include "napi/native_api.h"
#include "test_c.h"
#include <thread>
#include <hilog/log.h>//日志输出所需要的库
#pragma comment(lib, "libhilog_ndk.z.so")//日志输出所需要的库
void test_demo()
{
    // 这里只是演示调用引用的so库的方法
    test_c test;
    int r1 = test.add(10, 5);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.add(10, 5) = %{public}d", r1);
    int r2 = test.sub(10, 5);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", " test.sub(10, 5) = %{public}d", r2);
}

//一个同步返回的加法计算
static napi_value Add(napi_env env, napi_callback_info info)
{
    test_demo();//测试调用so库的方法
    
    size_t argc = 2; // 参数个数
    napi_value args[2] = {nullptr};
    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
  
    // 获取第一个参数
    double value0;
    napi_get_value_double(env, args[0], &value0);
    // 获取第二个参数
    double value1;
    napi_get_value_double(env, args[1], &value1);
    
    // 返回值
    napi_value sum;
    napi_create_double(env, value0 + value1, &sum);
    return sum;
}

//定义需要传递给异步工作的数据结构
struct CallbackContext {
    napi_env env = nullptr;
    napi_ref recvCallbackRef = nullptr;
    napi_async_work work;
    //需要传入的参数
    double a;
    double b;
    //返回的参数
    double result;
};

// 这里可以进行耗时操作, 方法名可修改,但参数固定
void AddAsync(napi_env env, void *data) {
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddAsync is called");
    // 获取传入的参数
    CallbackContext *context = (CallbackContext *)data;
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "context.a: %{public}f", context->a);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "context.b: %{public}f", context->b);
    // 模拟耗时操作
    std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 睡眠1秒
    // 计算结果
    context->result = context->a + context->b;
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddAsync end");
}

//AddAsync执行完毕后会自动调用这个方法, 方法名可修改,但参数固定
void AddCallBack(napi_env env, napi_status status, void *data) {
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddCallBack is called");
    CallbackContext *context = (CallbackContext *)data;
    napi_value recvCallback = nullptr;
    napi_get_reference_value(context->env, context->recvCallbackRef, &recvCallback);
    // 因为回调方法只有一个参数,  若有多个参数要给每个参数都赋值
    int size = 1;
    napi_value argv[size];
    napi_create_double(env, context->result, &argv[0]);

    napi_value ret;
    napi_call_function(env, nullptr, recvCallback, size, argv, &ret);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "AddCallBack delete");
    napi_delete_reference(context->env, context->recvCallbackRef);
    napi_delete_async_work(context->env, context->work);
    delete context;
}

//一个异步返回的加法计算
static napi_value AddWithCallBack(napi_env env, napi_callback_info info)
{
    size_t argc = 3;
    napi_value args[3] = {nullptr};
    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
   
    double value0;
    napi_get_value_double(env, args[0], &value0);

    double value1;
    napi_get_value_double(env, args[1], &value1);

    // 获取回调函数
    napi_ref recvCallbackRef;
    napi_create_reference(env, args[2], 1, &recvCallbackRef); 
    
    CallbackContext *context = new CallbackContext;
    context->env = env;
    context->recvCallbackRef = recvCallbackRef;
    context->a = value0;
    context->b = value1;
    //异步调用
    napi_value resource;
    //第二个参数为当前方法名
    napi_create_string_latin1(context->env, "AddWithCallBack", NAPI_AUTO_LENGTH, &resource);
    //创建异步工作,AddAsync为耗时操作的方法,AddCallBack为耗时操作完成后的回调方法,可替换成自己实际所需的方法
    napi_create_async_work(context->env, nullptr, resource, AddAsync, AddCallBack, context,
                           &context->work);
    napi_queue_async_work(context->env, context->work); // 实现在UI主线程调用

    // 直接返回空值,实际返回值通过回调方法返回
    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    return result;
}

static napi_value ParamsTest(napi_env env, napi_callback_info info)
{
    size_t argc = 5;
    napi_value args[5] = {nullptr};
    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
    
    //获取第一个参数--数字类型
    double a;
    napi_get_value_double(env, args[0], &a);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "double a: %{public}f", a);
    
    //获取第二个参数--字符串类型
    size_t typeLen = 0;
    napi_get_value_string_utf8(env, args[1], nullptr, 0, &typeLen);
    char *b = new char[typeLen + 1];
    napi_get_value_string_utf8(env, args[0], b, typeLen + 1, &typeLen);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "string b: %{public}s", b);
    
    //获取第三个参数--bool类型
    bool c;
    napi_get_value_bool(env, args[2], &c);
    OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "boolean c: %{public}d", c);
    
    //第四个参数--数组类型
    // 检查参数是否为数组
    bool is_array;
    napi_is_array(env, args[3], &is_array);
    if (!is_array) {
        napi_throw_type_error(env, nullptr, "Argument must be an array");
        return nullptr;
    }
    // 获取数组长度
    uint32_t length;
    napi_get_array_length(env, args[3], &length);
    // 遍历数组
    for (int i = 0; i < length; i++) {
        napi_value result;
        napi_get_element(env, args[3], i, &result);
        
        size_t len = 0;
        napi_get_value_string_utf8(env, result, nullptr, 0, &len);
        char *text = new char[len + 1];
        napi_get_value_string_utf8(env, result, text, len + 1, &len);
        OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "array d[%{public}d]: %{public}s", i, text);
    }
    
    //获取第五个参数--ArrayBuffer类型
    bool is_array_buffer;
    // 检查第五个参数是否为ArrayBuffer
    napi_is_arraybuffer(env, args[4], &is_array_buffer);
    if (is_array_buffer) {
        napi_value array_buffer_value;
        uint8_t *data;
        size_t byte_length;
        
        array_buffer_value = args[4];
        // 获取ArrayBuffer的外部数据指针和长度
        napi_get_arraybuffer_info(env, array_buffer_value, (void **)&data, &byte_length);

        OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "sizeof(uint8_t) = %{public}d", (int)sizeof(uint8_t));

        // 使用data指针处理ArrayBuffer数据
        OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "arraybuffer size = %{public}d,  (ie: bytes=%{public}d) ",
                     (int)(byte_length / sizeof(uint8_t)), (int)byte_length);

//         for (int i = 0; i < ((int)(byte_length / sizeof(uint8_t))); ++i) {
//             OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "data[%{public}d] = %{public}d ", i, *(data + i));
//         }
    } else {
        // 参数不是ArrayBuffer的处理逻辑
        OH_LOG_Print(LOG_APP, LOG_INFO, 0, "test", "e is not ArrayBuffer");
    }
    
    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    return result;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        //第一个参数是index.d.ts定义的方法名,第三个参数是当前cpp文件中的方法名
        { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "addWithCallBack", nullptr, AddWithCallBack, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "paramsTest", nullptr, ParamsTest, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&demoModule);
}

ArtTS侧调用接口

直接修改默认创建的Index.ets

import { hilog } from '@kit.PerformanceAnalysisKit';
import testNapi from 'libentry.so';//导入C++模块

@Entry
@Component
struct Index {
  callBack = (result:number)=>{
    hilog.info(0x0000, 'testTag', 'addWithCallBack(4,5) = %{public}d', result);
  }

  build() {
    Row() {
      Column({space:10}) {
        Button('add(2,3)')
          .onClick(()=>{
            hilog.info(0x0000, 'testTag', 'add(2,3) = %{public}d', testNapi.add(2, 3));
          })

        Button('addWithCallBack(4,5)')
          .onClick(()=>{
            hilog.info(0x0000, 'testTag', 'addWithCallBack(4,5) Begin');
            testNapi.addWithCallBack(4,5,this.callBack)
          })

        Button('paramsTest')
          .onClick(()=>{
            //获取一个arraybuffer数据,本地有什么图片资源就用哪个就行
            getContext().resourceManager.getMediaContent($r('app.media.app_icon')).then((mediaContent)=>{
              hilog.info(0x0000, 'testTag', 'paramsTest getMediaContent success');
              hilog.info(0x0000, 'testTag', 'paramsTest Begin');
              testNapi.paramsTest(4, "text", false, ['apple','boy','cat'],mediaContent.buffer)
            })
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

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

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

相关文章

微信支付开发--订阅号与服务号的区别

微信支付的开发&#xff0c;首先要搞明白订阅号与服务号的区别&#xff0c;其次就是要明白微信支付有很多通道&#xff0c;例如&#xff1a;JSAPI、APP、H5、Natice、小程序支付等。 微信支付系统的时序图 1、运营主体的不同 a、订阅号&#xff1a; 个人、媒体、企业、政府或其…

sheng的学习笔记-AI-序贯覆盖(sequential covering)

AI目录&#xff1a;sheng的学习笔记-AI目录-CSDN博客 规则学习&#xff1a;sheng的学习笔记-AI-规则学习&#xff08;rule learning&#xff09;-CSDN博客 剪枝&#xff1a;https://blog.csdn.net/coldstarry/article/details/137441167 基础知识 什么是序贯覆盖 规则学习…

论文翻译:arxiv-2022 Ignore Previous Prompt: Attack Techniques For Language Models

Ignore Previous Prompt: Attack Techniques For Language Models https://arxiv.org/pdf/2211.09527 忽略之前的提示&#xff1a;针对语言模型的攻击技术 文章目录 忽略之前的提示&#xff1a;针对语言模型的攻击技术摘要1 引言 摘要 基于Transformer的大型语言模型&#xf…

G1: Yunli‘s Subarray Queries (easy version)(1900)(定长区间众数)

思路&#xff1a;因为是定长区间&#xff0c;因此我们可以利用滑动窗口维护定长区间的众数的数量 AC代码&#xff1a; #include<bits/stdc.h>using namespace std;typedef long long ll; const int MOD 998244353; const int N 2e5 10;ll a[N]; ll b[N];//前i个数的…

com.alibaba.druid.pool.DruidDataSource error

Druid报错 Sep 11, 2024 11:16:03 AM com.alibaba.druid.pool.DruidDataSource error SEVERE: init datasource error, url: "jdbc:mysql://x.x.x.x:xxxx/test01?useSSLfalse&serverTimezoneUTC" java.sql.SQLException: connect error, url "jdbc:mysql:…

JAVA:对称加密技术的详细指南

请关注微信公众号&#xff1a;拾荒的小海螺 博客地址&#xff1a;http://lsk-ww.cn/ 1、简述 对称加密是一种加密算法&#xff0c;其中加密和解密使用相同的密钥。其主要特点是速度快、效率高&#xff0c;适用于大数据量的加密需求。对称加密算法通常用于保护数据的机密性和完…

Day17_0.1基础学习MATLAB学习小技巧总结(17)——字符向量元胞数组

利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍&#xff0c;为了在这个过程中加深印象&#xff0c;也为了能够有所足迹&#xff0c;我会把自己的学习总结发在专栏中&#xff0c;以便学习交流。 素材来源“数学建模清风” 特此说明&#xff1a;本博客的内容只在于总结在…

谷歌浏览器Chrome安装历史版本(亲测可用)

前言&#xff1a; 谷歌浏览器Chrome安装历史版本&#xff08;亲测可用&#xff09; 官网链接&#xff1a; https://downzen.com/en/windows/google-chrome/versions/?page4https://downzen.com/en/windows/google-chrome/versions/?page4 使用教程&#xff1a; 1、打开官网…

C++ 二叉树进阶

1.二叉搜索树简介 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树 &#xff0c;或者是具有以下性质的二叉树 : 若它的左子树不为空&#xff0c;则左子树上 所有节点的值都小于根节点的值 若它的右子树不为空&#xff0c;则右子树上 所有节点的值都大于根节点的值 它…

人工智能对教育4.0的影响

随着技术变革的加速&#xff0c;迫切需要支持教育系统管理新的机遇和风险。如果管理得当&#xff0c;技术为帮助教育系统实现教育4.0提供了一个独特的机会。教育4.0是一种专注于为学习者提供适合未来的能力、技能、态度和价值观的教学方法。“教育4.0”是由全球教育专家、从业者…

艾体宝干货丨OIDA之二:掌握数据包分析-学会识别

在 OIDA 方法&#xff08;观察、识别、剖析、分析&#xff09;中&#xff0c;识别阶段对于在捕获的网络流量中精确定位相关数据至关重要。本文重点介绍如何在这一关键步骤中有效使用 Wireshark 和 Profitap 的 IOTA。 OIDA方法系列文章主要包含四个部分&#xff0c;分别是观察…

鸿蒙OS 应用基础知识

APP HarmonyOS 的应用软件包以 APP Pack&#xff08;Application Package&#xff09;形式发布&#xff0c;它是由一个或多个 HAP&#xff08;HarmonyOS Ability Package&#xff09;以及描述每个 HAP 属性的 pack.info 组成。HAP 是 [Ability]的部署包&#xff0c;HarmonyOS …

虚拟机安装VMware-tools详细教程

这里以VM16.12版本为例子&#xff0c;所有windows系统在所有虚拟机版本上都是一样的操作&#xff0c;参考即可 第一步打开虚拟机&#xff0c;这里需要注意的是虚拟机设备要有CD/DVD驱动器&#xff0c;这也是很多人说vmtool安装按钮是灰色的原因 第二步:打开虚拟机&#xff0c;…

linux入门到实操-1 Linux概述、诞生过程、发行版本,如何安装?

教程来源&#xff1a;B站视频BV1WY4y1H7d3 3天搞定Linux&#xff0c;1天搞定Shell&#xff0c;清华学神带你通关_哔哩哔哩_bilibili 整理汇总的课程内容笔记和课程资料&#xff0c;供大家学习交流下载&#xff1a;夸克网盘分享 本文内容为完整笔记的入门篇 概述部分历史内容…

git push失败原因上传的文件超过了Gitee的上限100M

! [remote rejected] master -> master (pre-receive hook declined) error: failed to push some refs to 这个错误信息表明你在尝试将更改推送到Gitee的socket_service仓库时遇到了问题。具体来说&#xff0c;问题出在你尝试推送的文件大小超过了Gitee平台设定的限制。Git…

Science Robotics 在小动物模型中实现渐进和可逆主动脉收缩的软机器人平台

前言速览&#xff1a;目前对左心室压力过载引起心脏重构过程的理解主要来源于主动脉束带的动物模型。然而&#xff0c;这些研究未能同时控制疾病的进展和逆转&#xff0c;阻碍了其临床意义。为此&#xff0c;来自哈佛大学、麻省理工学院等的研究人员介绍了一种基于植入式可扩张…

回馈式负载箱的操作和维护

回馈式负载箱是用于测试电源设备&#xff08;如发电机、逆变器等&#xff09;性能的设备&#xff0c;它可以模拟真实的负载情况&#xff0c;通过调节负载的大小和类型&#xff0c;来检测电源设备的输出能力和稳定性。回馈式负载箱的操作和维护对于保证其正常工作和延长使用寿命…

19:I2C一:程序模拟I2C通信时序

I2C 1、什么是I2C2、I2C的通信时序2.1&#xff1a;起始信号2.2&#xff1a;停止信号2.3&#xff1a;主机向从机发送一个字节数据2.4&#xff1a;主机向从机读取一个字节数据2.5&#xff1a;主机接收应答2.6&#xff1a;主机发送应答 3、程序模拟I2C的通信时序3.1&#xff1a;指…

为什么企业需要数据目录?

想象一下&#xff0c;如果在没有目录系统的庞大图书馆里寻找一本特定的书&#xff0c;你可能会耗费无数个小时搜索&#xff0c;但最终却一无所获。 同理&#xff0c;企业的数据如果没有一个组织良好、易于搜索的系统&#xff0c;也无法充分发挥其潜力。企业数据目录能够简化这一…

“爱满中华”与“民生之语”——全国人民的幸福之音!

近年来,随着科技的不断进步,数字化手段在各个领域的应用越来越广泛。在此背景下,我国宣传部推出了“爱满中华”全民自助补贴APP,旨在通过数字化手段,更有效地推行全民扶贫补助政策,而“爱满中华”自助补贴平台和“民生之语”利民通讯软件也正式被我国中信办称为国家网络未来工程…