IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 利用C++和napi以及gtk编写addons向js提供接口 -> 正文阅读

[JavaScript知识库]利用C++和napi以及gtk编写addons向js提供接口

利用C++和napi以及gtk编写addons向js提供接口

在做electron的时候,发现有一些linux上的内容只凭借node的api无法实现,例如获取桌面文件的图标等等,而且目前情况下node-ffi已经很久不更新了,使用起来还需要降级node,但是降级的话很多新的模块也就不能用了(比如fs-extra还有什么promise的错误)。

因此,选择了napi来开发,这个用起来很方便。而且利用node-gyp编译后,结果就是.node形式的二进制文件,直接import就可以用了,但是为了方便使用最好是将你的addons使用bindings封装成模块,因为我实测不封装成模块,后面项目编译和打包的时候根本搜索不到你的.node文件,并且回报一些奇怪的错误。

在写小项目的时候,甚至不用管js,专注于写C++然后,最后的时候,利用napi头文件,根据示例提供的方式获取js层传来的参数,调用自己写的方法,在把c++类型封装成js类型就可以了。

也就是说,在编写的时候,和普通c++唯一的区别就是,你需要关注怎么把js传进来的参数转换C++类型,怎么把你写的c++类型转换成js类型,ok,bb完了,进入正题.

第一步:首先新建文件夹,新建package.json

package.json中填写一下内容:

{
  "name": "native",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "gypfile": true,
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bindings": "^1.5.0",
    "node-addon-api": "^4.1.0"
  }
}

当然这里是我给出的版本,这些模块你可以使用自己喜欢的版本,然后npm install 安装相关模块

新建binding.gyp文件

这个文件用于配置和你的C++文件相关的东西,例如入口文件、你需要用到的库文件、头文件,后续都需要在这里配置模板是这样:

{
  "targets": [
    {
      "target_name": "nativelib",//你编译后生成文件的名字
      "sources": [
        "main.cc"//你的入口文件
      ],
      "include_dirs": [  //这里需要填写头文件的目录,具体怎么填需要用到pkg
        "<!@(node -p \"require('node-addon-api').include\")",
        "/usr/include/at-spi2-atk/2.0", 
        "/usr/include/at-spi-2.0",
        "/usr/include/dbus-1.0",
        "/usr/lib/x86_64-linux-gnu/dbus-1.0/include",
        "/usr/include/gtk-3.0",
        "/usr/include/gio-unix-2.0",
        "/usr/include/cairo",
        "/usr/include/pango-1.0",
        "/usr/include/fribidi",
        "/usr/include/harfbuzz",
        "/usr/include/atk-1.0",
        "/usr/include/cairo",
        "/usr/include/pixman-1",
        "/usr/include/uuid",
        "/usr/include/freetype2",
        "/usr/include/libpng16",
        "/usr/include/gdk-pixbuf-2.0",
        "/usr/include/libmount",
        "/usr/include/blkid",
        "/usr/include/glib-2.0",
        "/usr/lib/x86_64-linux-gnu/glib-2.0/include",
      ],
      "libraries": [ //这里是lib,怎么填后面说
        "-lgtk-3",
        "-lgdk-3",
        "-lpangocairo-1.0",
        "-lpango-1.0",
        "-lharfbuzz -latk-1.0",
        "-lcairo-gobject",
        "-lcairo",
        "-lgdk_pixbuf-2.0",
        "-lgio-2.0",
        "-lgobject-2.0",
        "-lglib-2.0",
      ],
      "dependencies": [
        "<!(node -p \"require('node-addon-api').gyp\")"
      ],
      "cflags!": ["-fno-exceptions"],
      "cflags_cc!": ["-fno-exceptions"],
      "defines": ["NAPI_CPP_EXCEPTIONS"],
      "xcode_settings": {
        "GCC_ENABLE_CPP_EXCEPTIONS": "YES"
      }
    }
  ]
}

在普通c++项目中,我们可以使用pkg-config去搜索相关的库和头文件,这个addons中我没有找到什么替代方案,这个里面我不能使用pkg-config,所以我是手动用pkg-config在命令行中查找后手动填写的,比如在这个项目中我需要用到gtk,像这样

现在命令行中,搜索依赖:

 pkg-config --cflags --libs gtk+-3.0

搜索的结果是

-pthread -I/usr/include/gtk-3.0 -I/usr/include/at-spi2-atk/2.0 -I/usr/include/at-spi-2.0 -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -I/usr/include/gtk-3.0 -I/usr/include/gio-unix-2.0 -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/fribidi -I/usr/include/harfbuzz -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/uuid -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -lgtk-3 -lgdk-3 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -latk-1.0 -

然后能根据这个结果,我将每一个-I后面跟的参数填写进bingding.gyp文件的include_dirs,然后pkg运行结果后面部分l开头的那些lib写到

bingding.gyp文件的libraries,就大功告成了,然后去你在bingding.gyp设置的c++文件(设置我的main.cc)里面写相关的C++代码

编译c++部分代码

下面是c++部分代码的写法:

#include<gtk/gtk.h>
#include<gio/gio.h>
#include<string>
#include <napi.h>
#include<gio-unix-2.0/gio/gdesktopappinfo.h>
using namespace Napi;
using std::string;
static std::vector<string> search_path;
//你自己的函数随便用c++写
void push(const string& path){
    search_path.push_back(path);
}
/*这个函数用来封装你写的c++函数,也就是把传来的CallbackInfo获取出参数并转换,然后调用你写的c++函数,再把你的函数的返回值封装?成js的内容
*/
void call_add_search_path(const CallbackInfo& info){
    string path = info[0].As<String>().Utf8Value();
    push(path);
}
//export method
Napi::Object Init(Env env, Object exports) {
  //在这里设置,第一个参数是你导出的函数的名字
  exports.Set("add_seach_path", Function::New(env, call_add_search_path));
  return exports;
}
NODE_API_MODULE(addon, Init)

这是我原项目main.cc的代码

#include<gtk/gtk.h>
#include<gio/gio.h>
#include<string>
#include <napi.h>
#include<gio-unix-2.0/gio/gdesktopappinfo.h>
using namespace Napi;
using std::string;
static std::vector<string> search_path;

void push(const string& path){
    search_path.push_back(path);
}

/*
native method  get_icon
desc:get icon thrrough gtk,gio
param : filename ,icon size
dependency:gtk 3.0
if you only want to get .so library,extract this function and delete eveything relative to napi,
then use "gcc main.c -fPIC -shared -o libiconget.so `pkg-config --cflags --libs gtk+-3.0`" command to compile it.

*/
string get_icon(const string& filename,const int size){
    string result= "unknown";
    //get file from path
    GFile* file = g_file_new_for_path(filename.c_str());
    GError* gerror = 0;
    //query "standard::icon" info from file
    GFileInfo* gfileinfo = g_file_query_info(file,"standard::icon", GFileQueryInfoFlags::G_FILE_QUERY_INFO_NONE,g_cancellable_new(), &gerror);
    if (gerror) {
        g_error_free(gerror);
        gerror = 0;
    }
    if (gfileinfo) {
        //get icon name from gfileinfo
        GIcon* icon = g_file_info_get_icon (gfileinfo);
        if(icon){
            GtkIconTheme* theme = gtk_icon_theme_new();
            //add some ukui paths
            gtk_icon_theme_append_search_path(theme,"/usr/share/icons/ukui/48x48/places");
            gtk_icon_theme_append_search_path(theme,"/usr/share/icons/ukui/48x48/mimetypes");
            gtk_icon_theme_append_search_path(theme,"/usr/share/icons/ukui/48x48/apps");
            gtk_icon_theme_append_search_path(theme,"/usr/share/icons/ukui/48x48/devices");
            GtkIconInfo* info = 0;
            //lookup icon
            info = gtk_icon_theme_lookup_by_gicon (theme,icon,size,GtkIconLookupFlags::GTK_ICON_LOOKUP_NO_SVG);
           if(info){
               //copy result
               result = gtk_icon_info_get_filename(info);
               //free info
               g_object_unref(info);
           }
            g_object_unref(theme);
        }
        //free gfileinfo,file,theme

        g_object_unref(gfileinfo);
        g_object_unref(file);

    }
    return result;
}

string get_desktop_name(const string& dpath,const string& unknow){
    // std::ifstream is(dpath,std::ios_base::in);
    // string iconname;
    // string line;
    // while(getline(is,line)){
    //     if(!strncmp(line.c_str(),"Name=",4)) {
    //         iconname = line.substr(5);
    //         break;
    //     }
    // }
    // is.close();
    // if (iconname!="")return iconname;
    // return unkown;
    GDesktopAppInfo* appinfo = g_desktop_app_info_new_from_filename (dpath.c_str());
    if(!appinfo) return unknow;
    const char* result = g_desktop_app_info_get_string(appinfo,"Name");
    if(result==NULL) return unknow;
    return result;
    
}
string get_type(const string& s){
    GFile* file = g_file_new_for_path(s.c_str());
    GError* gerror = 0;
    //query "standard::icon" info from file
    GFileInfo* gfileinfo = g_file_query_info(file,"standard::icon", GFileQueryInfoFlags::G_FILE_QUERY_INFO_NONE,g_cancellable_new(), &gerror);
    if (gerror) {
        g_error_free(gerror);
        gerror = 0;
        return "";
    }
    GIcon* icon = g_file_info_get_icon (gfileinfo);
    string type = g_themed_icon_get_names ((GThemedIcon*)icon)[0];
    g_object_unref(gfileinfo);
    g_object_unref(file);
    return type;
}
string get_desktop_exec(const string& dpath){
    // std::ifstream is(dpath,std::ios_base::in);
    // string iconname;
    // string line;
    // while(getline(is,line)){
    //     if(!strncmp(line.c_str(),"Exec=",4)) {
    //         iconname = line.substr(5);
    //         break;
    //     }
    // }
    // is.close();
    GDesktopAppInfo* appinfo = g_desktop_app_info_new_from_filename (dpath.c_str());
    const char* re =  g_desktop_app_info_get_string(appinfo,"Exec");
    if(re == NULL) return "";
    return re;
        
}
string get_desktop_icon(const string& dpath,const string& unkpath){
    GDesktopAppInfo* appinfo = g_desktop_app_info_new_from_filename (dpath.c_str());
    const char* c = g_desktop_app_info_get_string(appinfo,"Icon");
    if(c== NULL) return unkpath;
    string iconname = c;
    if(g_path_is_absolute(iconname.c_str())) return iconname;
    GFile* file = 0;
    string cpath;
    for(size_t i=0; i<search_path.size(); i++){
        cpath = search_path[i] + iconname + ".png";
        file = g_file_new_for_path(cpath.c_str());
        if(g_file_query_exists(file,NULL))
        {
            g_object_unref(file);
            return  cpath;
        }
    }
    return unkpath;
}
Array get_recommended_open_way(const CallbackInfo& info){
    Env env = info.Env();
    string path = info[0].As<String>().Utf8Value();
    Array obj_array  = Array::New(env);
    GFile* file = g_file_new_for_path (path.c_str());
    GError* gerror = 0;
    GFileInfo* gfileinfo = g_file_query_info(file,"standard::*", GFileQueryInfoFlags::G_FILE_QUERY_INFO_NONE,g_cancellable_new(), &gerror);
    if(gerror){
        g_error_free(gerror);
        gerror = 0;
        g_object_unref(file);
        return obj_array;
    }
    const char* types = g_file_info_get_content_type (gfileinfo);
    GList* list = g_app_info_get_recommended_for_type (types);
    string exe,name;
    GAppInfo* appinfo = nullptr;
    int i = 0;
    while(list){
        appinfo = static_cast<GAppInfo*>(list->data);
        name = g_app_info_get_name(appinfo);
        exe = g_app_info_get_commandline  (appinfo);
        Object obj = Object::New(env);
        obj.Set("app",name);
        obj.Set("exec",exe);
        obj_array[i] = obj;
        g_object_unref(appinfo);
        list = list->next;
        i++;
    }
    //release object
    g_object_unref(file);
    return obj_array;
}

//transfer  params  from node and call c++ native method get_icon
String call_get_icon(const CallbackInfo& info){
    Env env = info.Env();
    string filename = info[0].As<String>().Utf8Value();
    int size = info[1].ToNumber().Int32Value();
    return String::New(env,get_icon(filename,size));
}
String call_get_desktop_icon(const CallbackInfo& info){
    Env env = info.Env();
    string dpath = info[0].As<String>().Utf8Value();
    string unknow = info[1].As<String>().Utf8Value();
    return String::New(env,get_desktop_icon(dpath,unknow));
}
String call_get_desktop_name(const CallbackInfo& info){
    Env env = info.Env();
    string dpath = info[0].As<String>().Utf8Value();
    string unknow = info[1].As<String>().Utf8Value();
    return String::New(env,get_desktop_name(dpath,unknow));
}
String call_get_desktop_exec(const CallbackInfo& info){
    Env env = info.Env();
    string dpath = info[0].As<String>().Utf8Value();
    return String::New(env,get_desktop_exec(dpath));
}
String call_get_type(const CallbackInfo& info){
    Env env = info.Env();
    string dpath = info[0].As<String>().Utf8Value();
    return String::New(env,get_type(dpath));
}
void call_add_search_path(const CallbackInfo& info){
    string path = info[0].As<String>().Utf8Value();
    push(path);
}
//export method
Napi::Object Init(Env env, Object exports) {
  exports.Set("get_ndesktop_icon", Function::New(env, call_get_icon));
  exports.Set("get_desktop_icon", Function::New(env, call_get_desktop_icon));
  exports.Set("add_seach_path", Function::New(env, call_add_search_path));
  exports.Set("get_desktop_name", Function::New(env, call_get_desktop_name));
  exports.Set("get_desktop_exec", Function::New(env, call_get_desktop_exec));
  exports.Set("get_type", Function::New(env, call_get_type));
  exports.Set("get_recommended_open_way", Function::New(env, get_recommended_open_way));
  return exports;
}
NODE_API_MODULE(addon, Init)

到这里就差不多,写完之后,使用npm install 或者node-gyp rebuild去编译就可以了,编译后的.node文件就是在当前目录下build文件夹中的Release文件夹下。

目前我的项目结构是这样:

要使用的话,最简单的方法就是新建项目,把编译后的.node文件复制出来,下面我新建项目调用测试


看,我在main.cc里面写的native函数都被导出来了,使用当然也可以

如果你的项目比较小,到这就可以结束了,笔者写了一个小型的桌面应用,当时这样使用编译的.node,在测试的时候是没有问题的,但是打包成可执行文件的时候就一直找不到这个文件,所以我利用bindings将其封装node-module,解决了这个问题

封装成node-module

新建index.js,然后在index.js中这样写:

//关键是这一句,利用bingdings去找nativelib.node
const native = require("bindings")("nativelib");
//这里逻辑处理可以不用管,我主要是为了方便上面业务层调用
const fs = require("fs");
native.add_seach_path("/usr/share/pixmaps/");
native.add_seach_path("/usr/share/icons/hicolor/48x48/apps/");
native.add_seach_path("/usr/share/icons/ukui/48x48/apps/");

exports.get_icon = function getIcon(filename, size, unkownicon) {
  if (filename.endsWith(".desktop")) {
    return native.get_desktop_icon(filename, unkownicon);
  } else {
    let iconpath = native.get_ndesktop_icon(filename, size);
    if (iconpath == "unknown") return unkownicon;
    if (!iconpath.includes("gnome")) return iconpath;
    let ukuipath = iconpath.replace("/gnome/", "/ukui/");
    return fs.existsSync(ukuipath) ? ukuipath : iconpath;
  }
};
exports.get_desktop_name = function(filename, unkown) {
  return native.get_desktop_name(filename, unkown);
};
exports.get_desktop_exec = function(filename) {
  return native.get_desktop_exec(filename);
};
exports.get_type = function(filename) {
  return native.get_type(filename);
};
exports.get_recommended_open_way = function(path) {
  return native.get_recommended_open_way(path);
};

项目结构:

这样就成了,如果你的主项目要用到这个模块,只需要复制这个文件夹到项目路径里面,然后执行

npm install --save ./native 

就可以将其安装到你的项目中

这样其实还有个好处,别人使用你的模块的时候,你甚至可以将node-modules文件夹和build文件夹删除,一方面发送的时候小,另一方面,然后别人安装的时候,会在他的系统环境下自动重新编译一下这个模块,提高了平台的适用性。

结束!

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-18 17:18:34  更:2021-10-18 17:18:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/13 22:51:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码