前言
先说一下上一篇文章给出了各高级语言类型和C类型的对应关系,只包含基本类型,不包含结构体等复杂结构,高级语言只有常见的JAVA(Android通用)、C#、Python、Arkts(鸿蒙系)。其它语言如delphi、PB之类的古老语言目前使用的人非常稀少,默认不写了;还有js调用需要编译位wasm,但限制非常多,极度不推荐去这样做,可以开发成插件来使用http(s)/ws(s)协议来实现,而且跨平台更加容易。
正文
接下来开始今天要说的内容吧,C语言开发里回调函数是非常常见的,但对于其它高级语言来说,相对使用就没有那么频繁了,但如果有调用C接口的需求,回调也是无法避免的。第一篇先说一下简单的回调,主要是参数的复杂程度,后面会有更复杂的接口调用示例。
C接口定义及调用
先来看一下C接口的定义,这个回调比较简单,参数只有一个const char*
#ifdef _WINDOWS
#define MYAPI _declspec(dllexport)
#else
#define MYAPI
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef void(*PushMessage)(const char* error);//消息回调函数
int MYAPI Init(
PushMessage pushMsg,
const char* licensePath,
int enAutoUpdateTmpl,
int enWriteLog);
#ifdef __cplusplus
}
#endif
这里是初始化SDK,并设置消息推送的回调函数,C语言的调用方式比较简单
void ShowMsg(SD_CONST_STRING error)
{
printf("%s\n", error);
}
int iRet = Init(ShowMsg, "test.dat", 1, 1);
if(iRet == 0)
{
//to do sth;
}
else
{
//to do sth;
}
Java接口定义及调用
Java调用C接口有2种方式,Jni和Jna,Jni需要编写C层代码,运行的适应率更高,包括一些ZF、JF的SM机都可以正常运行,Jna由于运行时需要在临时目录下解压jar包,可能会有权限问题。Jna的优势在于支持指针、结构体等复杂类型,传参时可以传递地址,避免内存使用量翻倍。可以说是各有利弊吧,总体感觉还是Jna更方便,代码里更小,与C接口对接也更容易,如果客户不是用的SM机,建议首选Jna。
Jna
定义接口及回调
public interface MYAPI extends Library
{
MYAPI instanceDll = (MYAPI) Native.load("MYAPI", MYAPI.class);
//消息回调函数
public interface CommCallBack extends Callback
{
public void methodWithCallback(final String mes);
}
int Init(CommCallBack pushMsg,final String licensePath, int enAutoUpdateTmpl,int enWriteLog);
}
调用示例
1、实现CommCallBack
static public class CommCallBackImpl implements MYAPI.CommCallBack
{
public void methodWithCallback(final String mes)
{
System.out.printf("%s\n",mes);
}
}
2、实例化并传参
static MYAPI.CommCallBack ShowMsg = new CommCallBackImpl();
int iRet = MYAPI.instanceDll.Init(ShowMsg,"test.dat", 1, 1);
Jni
1、定义接口及回调
public class MYAPI
{
//消息回调函数
public interface CommCallBack extends Callback
{
public void methodWithCallback(final String mes);
}
int Init(CommCallBack pushMsg,final String licensePath, int enAutoUpdateTmpl,int enWriteLog);
}
2、Jni层C接口实现
JavaVM * g_jvm = SD_NULL;
jobject g_pushMsgJObj = SD_NULL;
void CommPushMsg(const char* error)
{
if (g_jvm == SD_NULL || g_pushMsgJObj == SD_NULL)
{
return;
}
JNIEnv *env = SD_NULL;
SD_BOOL isCreate = SD_FALSE;
(*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_4);
if (env == SD_NULL)
{
(*g_jvm)->AttachCurrentThread(g_jvm, (void **)&env, NULL);
isCreate = SD_TRUE;
}
if (env)
{
jclass cls = (*env)->GetObjectClass(env, g_pushMsgJObj);
jmethodID jmid = (*env)->GetMethodID(env, cls, "methodWithCallback", "(Ljava/lang/String;)V");
if (jmid)
{
jstring info = (*env)->NewStringUTF(env, error);
(*env)->CallVoidMethod(env, g_pushMsgJObj, jmid, info);
(*env)->ReleaseStringUTFChars(env, info, (*env)->GetStringUTFChars(env, info, SD_FALSE));
}
if (isCreate == SD_TRUE)
{
(*g_jvm)->DetachCurrentThread(g_jvm);
}
}
}
JNIEXPORT jint JNICALL Java_org_tg_vein_SDPVD310API_SD_1API_1Init
(JNIEnv *env, jclass jc, jobject obj,jstring license, jint enAutoUpdateTmpl, jint enWriteLog)
{
const char *licensePath = (*env)->GetStringUTFChars(env, license, NULL);
jint iRet = SD_API_Init(CommPushMsg, licensePath, enAutoUpdateTmpl, enWriteLog);
if (iRet == 0)
{
jint status = (*env)->GetJavaVM(env, &g_jvm);
if (status != JNI_ERR)
{
g_pushMsgJObj = (*env)->NewGlobalRef(env, obj);
}
}
return iRet;
}
这里回调函数作为全局消息推送接口,会被所有线程来调用,在Jni层,需要准确记录env和接口地址信息,才能正确调用。
3、实例化并传参
static MYAPI.CommCallBack showmsg = new CallBackImpl();
int iRet = MYAPI.Init(showmsg,"test.dat", 1, 1);
C#接口定义及调用
1、定义接口及回调
public class MYAPI
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void PushMsg(IntPtr msg);
[DllImport(@".\MYAPI.dll", EntryPoint = "Init", CallingConvention = CallingConvention.Cdecl)]
public extern static EnApiError Init(PushMsg pushMsg,string licensePath,int enAutoUpdateTmpl,int enWriteLog);
}
这里要说一下C#当中回调参数的定义,这里很明显,和上一篇说的不太一样,const char*对应的本来应该是string啊?这里为什么是IntPtr呢?
如果不是回调,不管是入参还是出参,string对应const char*都没有问题,因为参数的内存是在C#当中来申请和管理的,但回调时,内存是由C层来申请和管理的,如果使用string会导致类型不一致,C#种string是类的实例,包含length等属性及方法,但C层的const char*只是单纯的一块内存地址,无法实现转换,所以,这里必须定义为IntPtr。
为什么Java可以定义为String?因为Java的Jna层与C层依然有默认的Jni层转换,传参并不是直接传入C接口,或者C接口直接调用回到方法的,而是在Jni层来调用的,这样,中间的类型差异可以被处理。
2、实例化并传参
MYAPI.PushMsg ShowMsgCallBack;
ShowMsgCallBack += new MYAPI.PushMsg(ShowMsg);
int enRet = TGSDK.MYAPI.Init(ShowMsgCallBack, licensePath, 1, 1);
Python接口及定义
1、回调函数定义
from ctypes import *
PushMessage = CFUNCTYPE(None, c_char_p)
2、接口定义
MYAPI = LoadLibrary('MYAPI')
MYAPI.Init.argtypes = [PushMessage, c_char_p, c_int, c_int]
MYAPI.Init.restype = c_int
def Init(pushMsg:PushMessage, licensePath:c_char_p, enAutoUpdateTmpl:SD_API_E_AUTOUPD, enWriteLog:SD_API_E_LOG) -> c_int:
return MYAPI.Init(pushMsg, licensePath, enAutoUpdateTmpl, enWriteLog)
3、调用实现
def ShowMsg(msg:c_char_p):
print(string_at(msg).decode())
showMsg = PushMessage(ShowMsg)
inRet:c_int = Init(showMsg, c_char_p("test.dat".encode()), 1, 1)
Arkts+Napi接口定义及调用
Arkts调用相对复杂一些,需要napi层作为Arkts与C接口之间的桥梁。
非线程安全
1、先看napi层,接口的定义
static void cbPushMessage(const char* msg)
{
if(pushMessage != nullptr)
{
napi_value result = nullptr;
napi_value args[1] = {nullptr};
size_t length = strlen(msg);
napi_create_string_utf8(g_env, msg, length, &args[0]);
napi_call_function(g_env, nullptr, pushMessage, 1, args, &result);
}
}
static napi_value API_Init(napi_env env, napi_callback_info info)
{
napi_value ret;
size_t argc = 6;
napi_value args[6] = {nullptr};
g_env = env;
pushMessage = args[0];
napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
NativeResourceManager *mNativeResMgr = OH_ResourceManager_InitNativeResourceManager(env, args[1]);
char filesDir[PATH_MAX] = {0};
size_t filesDirLen;
napi_get_value_string_utf8(env, args[2], filesDir, PATH_MAX, &filesDirLen);
char license[PATH_MAX] = {0};
size_t licenseLen;
napi_get_value_string_utf8(env, args[3], license, PATH_MAX, &licenseLen);
char realLicense[PATH_MAX] = {0};
sprintf(realLicense, "%s/%s", filesDir, license);
GetRawFileContent(mNativeResMgr, license, realLicense);
OH_ResourceManager_ReleaseNativeResourceManager(mNativeResMgr);
int32_t enAutoUpdateTmpl;
napi_get_value_int32(env, args[4], (int32_t*)&enAutoUpdateTmpl);
int32_t enWriteLog;
napi_get_value_int32(env, args[5], (int32_t*)&enWriteLog);
int32_t apiRet = Init(cbPushMessage, realLicense, enAutoUpdateTmpl, enWriteLog);
napi_create_int32(env, apiRet, &ret);
return ret;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
{ "Init", nullptr, API_Init, nullptr, nullptr, nullptr, napi_default, nullptr },
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
2、arkts层、Index.d.ts
export const Init: (pushMsg: (msg: string) => void | null, resmgr: resourceManager.ResourceManager,
filesPath: string,
license: string, autoUpd: number, switchLog: number) => number;
3、arkts层、MYAPI.ets
import sdk from 'libmyapi.so';
import common from '@ohos.app.ability.common';
let context = getContext(this) as common.Context;
export function SD_API_Init(pushMsg: (msg: string) => void, license: string, autoUpd: number,
switchLog: number): number {
return sdk.Init(pushMsg, context.resourceManager, context.filesDir, license, autoUpd, switchLog);
}
4、界面调用、Index.ets
@Entry
@Component
struct Index {
@State logStr: string = '';
controller: TextAreaController = new TextAreaController();
// 定义一个eventId为1的事件
event: emitter.InnerEvent = {
eventId: 1
};
// 收到eventId为1的事件后执行该回调
callback = (eventData: emitter.EventData) => {
this.logStr += eventData.data?.msg + '\n';
};
executeCallback(msg: string) {
// 定义一个eventId为1的事件,事件优先级为Low
let event: emitter.InnerEvent = {
eventId: 1,
priority: emitter.EventPriority.LOW
};
let eventData: emitter.EventData = {
data: {
"msg": msg
}
};
// 发送eventId为1的事件,事件内容为eventData
emitter.emit(event, eventData);
}
build() {
Row() {
}
.onAppear(() => {
// 订阅eventId为1的事件
emitter.on(this.event, this.callback);
let ret: number = Init(this.executeCallback, "test.dat", 1, 1);
})
}
}
由于调用线程不确定,这里用了消息订阅事件来处理,将消息交给UI线程处理。
线程安全实例
napi_ref cbObj = nullptr;
napi_threadsafe_function tsfn;
static void CallJs(napi_env env, napi_value js_cb, void *context, void *data) {
napi_get_reference_value(env, cbObj, &js_cb);
napi_value argv;
int i = (*(int *)data);
napi_create_int32(env, i, &argv);
napi_value result = nullptr;
napi_call_function(env, nullptr, js_cb, 1, &argv, &result);
}
static napi_value SetCallback(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value js_cb, work_name;
napi_status status;
napi_get_cb_info(env, info, &argc, &js_cb, nullptr, nullptr);
status = napi_create_reference(env, js_cb, 1, &cbObj);
// Set initial_refcount to 0 for a weak reference, >0 for a strong reference.
status = napi_create_reference(env, js_cb, 1, &cbObj);
status =
napi_create_string_utf8(env, "Node-API Thread-safe call from Async work Item", NAPI_AUTO_LENGTH, &work_name);
status = napi_create_threadsafe_function(env, js_cb, NULL, work_name, 0, 1, NULL, NULL, NULL, CallJs, &tsfn);
return nullptr;
}
线程安全部分差别在napi层,其它层代码就不展示了,按照常规方式调用即可。
第一次写这么多东西,有点乱,代码是从工程里摘出来的,难免会有些问题,还请见谅。
标签:调用,函数,int,nullptr,接口,MYAPI,Init,env,napi From: https://blog.csdn.net/geesehoward20000/article/details/143768628