背景
harmonyNext目前正处在高速迭代阶段,有很多代码细节实现并未完善,因此有些基础功能需要引入现有比较完善的C/C++ library,然而harmonyNext推荐开发语言是ets,因此有必要引入相应的native开发接口。比如TLSSocket在api11上是不支持二进制流的,只能发送字符串。
现状
进入harmonyNext的native开发介绍前,先讲讲移动端的相关现状
native开发分两种情况,一种是自己直接进行C/C++相关功能开发,另外一种是调用现有library。(以下调用现有native的library库都默认编译时使用的标准C和C++库的系统版本是兼容的。如果引用的标准c库api都有变化是肯定无法直接使用的)
iOS native开发
- 如果需要进行C/C++相关功能开发,只需要将开发文件从.m改成.mm,开发语言从objective-c改成objective-c++,因为编译器clang能同时支持c++/oc,所以能实现完美迁移,直接在文件中写C方法即可。
- 调用现有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,后续再看看有没有时间直接深入源码看看实现再来挂一篇文章