harmonyNext native简介

背景

harmonyNext目前正处在高速迭代阶段,有很多代码细节实现并未完善,因此有些基础功能需要引入现有比较完善的C/C++ library,然而harmonyNext推荐开发语言是ets,因此有必要引入相应的native开发接口。比如TLSSocket在api11上是不支持二进制流的,只能发送字符串。

现状

进入harmonyNext的native开发介绍前,先讲讲移动端的相关现状

native开发分两种情况,一种是自己直接进行C/C++相关功能开发,另外一种是调用现有library。(以下调用现有native的library库都默认编译时使用的标准C和C++库的系统版本是兼容的。如果引用的标准c库api都有变化是肯定无法直接使用的)

iOS native开发

  1. 如果需要进行C/C++相关功能开发,只需要将开发文件从.m改成.mm,开发语言从objective-c改成objective-c++,因为编译器clang能同时支持c++/oc,所以能实现完美迁移,直接在文件中写C方法即可。
  2. 调用现有library库,iOS中引入的native三方库一般都是通过静态库的方式集成的,把framework拖进工程(如果是使用cocoapods的组件化状态,还需对子组件的vendorframework进行指定),然后加入compiler source中,添加相关的系统动态库依赖,最后引入相关头文件.h,直接调用相关的c方法即可。

总体上来说,iOS静态库本身就是二进制代码,因此native开发在iOS侧几乎无感知,另外iOS很少使用so动态库,因为iOS的动态被阉割过了,无法使用dlopen这些动态接口,只能指定framework为embed引入到自己的ipa包内然后被签名上传。

java的native开发

NDK开发相对于iOS的无感知操作麻烦很多,毕竟两门语言的编译器没有兼容,所以要想调用需要完成java的符号和C/C++代码符号的转换,另外java之所以能支持与native互动,核心是因为JVM使用C/C++书写,这样他就能通过运行时的方式直接访问到java类的一些类指针,再加上C/C++有一套ffi标准能够支持c方法动态调用。

jni调用
scala 复制代码
public class MainActivity extends AppCompatActivity {    
    static {
            System.loadLibrary("native-lib");    
    }    
    public native String stringFromJNI();
}

使用还是蛮简单的,使用native申明一下方法,然后就可以加载动态库以后就可以直接调用了。

ndk开发

ndk开发分两方面一个是实现java符号和native代码符号映射和转换的代码怎么写,另外一个方面就是如何实现so库的编译,so库的交叉编译涉及到的内容太多,简单的来说就是

cmake->ninja(or makefile)->so库文件

使用cmake语法生成ninja或者makefile文件,得到文件依赖顺序和编译顺序以及三方库依赖,然后使用make命令直接编译。使用androidstudio应该是不用太关注这些细节

ndk转换代码

分成静态注册和动态注册:

静态注册,通过预先设置的函数名称规范指定当java调用某个方法的时候实际上会调用到的c方法

scala 复制代码
//java
public class MainActivity extends AppCompatActivity {    
    static { 
           System.loadLibrary("native-lib");    
    }
    public native String stringFromJNI();
}
//cpp
extern "C" JNIEXPORT jstring JNICALL Java_com_package_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz) {    
    std::string hello = "Hello from C++";    
    return env->NewStringUTF(hello.c_str());
}

按照JNI的规范书写java侧定义的native方法即可

动态注册:在JNI_OnLoad方法里面直接注册java的方法名以及对应的c++方法签名即可

ini 复制代码
void stringFromJNI(JNIEnv *env, jobject thisz){    
    std::string hello = "Hello from C++";    
    return env->NewStringUTF(hello.c_str());
}

static const JNINativeMethod methods[] = {        
    {"stringFromJNI","()V",(void*)stringFromJNI}
}        

static const char *mClassName = "com/package/MainActivity";
jint JNI_OnLoad(JavaVM* vm, void* reserved){    
    JNIEnv* env = NULL;    
    int result = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);    
    if(result != JNI_OK){               
        return JNI_VERSION_1_6;    
    }
    jclass classMainActivity = env->FindClass(mClassName);    
    result = env->RegisterNatives(classMainActivity,methods, 2);    
    if(result != JNI_OK){        
        return JNI_VERSION_1_2;    
    }    
    return JNI_VERSION_1_6;
}

从上面的状况也可以看得出,我们平常使用的so库在安卓是完全无法使用的,只有经过特定编译脚本处理(NDK)后生成的so库才能够被java所使用。

鸿蒙native开发

鸿蒙NDK和java NDK开发不能说毫不相关只能说一模一样,基本逻辑是一样的,需要完成arkts符号和native代码符号以及函数签名的映射和转换,鸿蒙目前只支持动态注册的方式完成,简称node-api。

流程开发设置比较繁琐请直接参考最新文档

developer.huawei.com/consumer/cn...

设置方面基本跟安卓也是一模一样了,毕竟都是交叉编译的一部分。

注册

ini 复制代码
static napi_value CallNative(napi_env env, napi_callback_info info)
{
    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;
}

EXTERN_C_START
// 模块初始化
static napi_value Init(napi_env env, napi_value exports) {
    // ArkTS接口与C++接口的绑定和映射
    napi_property_descriptor desc[] = {
        {"callNative", nullptr, CallNative, nullptr, nullptr, nullptr, napi_default, nullptr}
    };
    // 在exports对象上挂载CallNative/NativeCallArkTS两个Native方法
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

// 准备模块加载相关信息,将上述Init函数与本模块名等信息记录下来。
static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = nullptr,
    .reserved = {0},
};

// 加载so时,该函数会自动被调用,将上述demoModule模块注册到系统中。
extern "C" __attribute__((constructor)) void RegisterDemoModule() { 
    napi_module_register(&demoModule);
}

对外声明d.ts文件

typescript 复制代码
// entry/src/main/cpp/types/libentry/index.d.ts
export const callNative: (a: number, b: number) => number;

看一个实际的例子:TLSSocket

cpp文件注册模块:

scss 复制代码
void TLSSocketModuleExports::DefineTLSSocketClass(napi_env env, napi_value exports)
{
    std::initializer_list<napi_property_descriptor> functions = {
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_GET_CERTIFICATE, TLSSocket::GetCertificate),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_GET_REMOTE_CERTIFICATE, TLSSocket::GetRemoteCertificate),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_GET_SIGNATURE_ALGORITHMS, TLSSocket::GetSignatureAlgorithms),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_GET_PROTOCOL, TLSSocket::GetProtocol),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_CONNECT, TLSSocket::Connect),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_GET_CIPHER_SUITE, TLSSocket::GetCipherSuites),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_SEND, TLSSocket::Send),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_CLOSE, TLSSocket::Close),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_BIND, TLSSocket::Bind),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_GET_STATE, TLSSocket::GetState),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_GET_REMOTE_ADDRESS, TLSSocket::GetRemoteAddress),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_SET_EXTRA_OPTIONS, TLSSocket::SetExtraOptions),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_ON, TLSSocket::On),
        DECLARE_NAPI_FUNCTION(TLSSocket::FUNCTION_OFF, TLSSocket::Off),
    };
    ModuleTemplate::DefineClass(env, exports, functions, INTERFACE_TLS_SOCKET);
}

napi_value TLSSocketModuleExports::InitTLSSocketModule(napi_env env, napi_value exports)
{
    DefineTLSSocketClass(env, exports);
    InitTLSSocketProperties(env, exports);
    InitProtocol(env, exports);
    return exports;
}

napi_value SocketModuleExports::InitSocketModule(napi_env env, napi_value exports)
{
    TlsSocket::TLSSocketModuleExports::InitTLSSocketModule(env, exports);
    return exports;
}

static napi_module g_socketModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = SocketModuleExports::InitSocketModule,
    .nm_modname = SOCKET_MODULE_NAME,
    .nm_priv = nullptr,
    .reserved = {nullptr},
};
/*
* Module register function
*/
extern "C" __attribute__ ((constructor)) void  RegisterSocketModule(void)
{
    napi_module_register(&g_socketModule);
}

收尾

就先简单的入门式介绍一下,感觉没有太多的技术gap,后续再看看有没有时间直接深入源码看看实现再来挂一篇文章

相关推荐
小野猫子5 分钟前
Web GIS可视化地图框架Leaflet、OpenLayers、Mapbox、Cesium、ArcGis for JavaScript
前端·webgl·可视化3d地图
shenyan~16 分钟前
关于 js:9. Node.js 后端相关
前端·javascript·node.js
uwvwko31 分钟前
ctfshow——web入门254~258
android·前端·web·ctf·反序列化
所待.38341 分钟前
深入解析SpringMVC:从入门到精通
前端·spring·mvc
逃逸线LOF1 小时前
CSS之精灵图(雪碧图)Sprites、字体图标
前端·css
海天胜景2 小时前
jqGrid冻结列错行问题,将冻结表格(悬浮表格)与 正常表格进行高度同步
前端
清风细雨_林木木3 小时前
解决 Tailwind CSS 代码冗余问题
前端·css
三天不学习3 小时前
VueUse/Core:提升Vue开发效率的实用工具库
前端·javascript·vue.js·vueuse
余道各努力,千里自同风3 小时前
CSS实现文本自动平衡text-wrap: balance
前端·css
Yvonne爱编码4 小时前
CSS- 4.3 绝对定位(position: absolute)&学校官网导航栏实例
前端·css·html·html5·hbuilder