首页 > 其他分享 >高级语言调用C接口(二)回调函数(1)

高级语言调用C接口(二)回调函数(1)

时间:2024-11-14 14:45:47浏览次数:3  
标签:调用 函数 int nullptr 接口 MYAPI Init env napi

前言

先说一下上一篇文章给出了各高级语言类型和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

相关文章

  • Day 14 匿名函数 内置函数 面向对象编程
    目录0上节课复习0.1迭代器0.1.1可迭代对象0.1.2迭代器对象0.1.3for循环原理0.2三元表达式0.3列表推导式0.4字典生成器0.5生成器0.5.1生成器表达式0.6递归0.7二分法1匿名函数1.1有名函数1.2匿名函数2内置函数2.1掌握2.2了解3面向过程编程0上节课复习0.1迭代......
  • 探索 Python 函数式编程的瑞士军刀:Toolz 库
    文章目录探索Python函数式编程的瑞士军刀:Toolz库第一部分:背景介绍第二部分:Toolz库概述第三部分:安装Toolz库第四部分:Toolz库函数使用方法1.高阶函数2.计算管道3.字典合并4.分组5.累积计算第五部分:Toolz库使用场景场景1:学生分数统计场景2:数据流处理场景3:......
  • 自动化实践之:从UI到接口,Playwright给你全包了!
    作者:京东保险宋阳1背景在车险系统中,对接保司的数量众多。每当系统有新功能迭代后,基本上各个保司的报价流程都需要进行回归测试。由于保司数量多,回归测试的场景也会变得重复而繁琐,给测试团队带来了巨大的工作压力。车险投保流程主要通过H5页面进行,核心功能集中在投保、报价、......
  • ajax 调用
    一、Post$("#btn30").on('click',function(){$.ajax({type:"post",contentType:"application/json",dataType:"json",url:"https://localhost/api/app/abc",......
  • 微店商品详情数据接口探秘:从接入到数据获取全流程
    以下是微店商品详情数据接口从接入到数据获取的全流程:注册成为微店开发者:访问微店开放平台:首先,需要访问微店的开放平台官方网站。这是接入微店商品详情数据接口的入口,在浏览器中输入正确的网址即可进入。注册账号:在开放平台上进行账号注册,填写必要的信息,如用户名、密码、......
  • 淘宝上货接口(淘宝发货接口)
    淘宝上货接口详解正文开始…目前淘宝上货接口是商家必备的一种工具,它可以帮助商家实现自动化的订单发货和物流跟踪。本文将从四个方面,即接口概述、接口功能、使用方法和接口的优势与不足来详细阐述淘宝上货接口。接口概述淘宝上货接口是淘宝平台提供的一种数据交互接口,商家......
  • 淘宝商品详情接口大揭秘:轻松复制商品链接与店铺秘籍全解析
    以下是关于淘宝商品详情接口以及轻松复制商品链接与店铺的全解析:淘宝商品详情接口:接口概述:淘宝商品详情接口是淘宝开放平台提供的一种服务,允许开发者通过API调用获取淘宝商品的详细信息,这些信息对于电商业务的多种应用场景具有重要价值。使用流程:注册淘宝开放平台......
  • R语言使用caret包构建岭回归模型实战,构建回归模型、通过method参数指定算法名称、通过
    R语言使用caret包构建岭回归模型实战,构建回归模型、通过method参数指定算法名称、通过trainControl函数控制训练过程目录R语言使用caret包构建岭回归模型(RidgeRegression )构建回归模型、通过method参数指定算法名称、通过trainControl函数控制训练过程 #导入包和库#仿......
  • R语言data.table导入数据实战:data.table使用自定义函数及Reduce函数实现一次性性多表
    R语言data.table导入数据实战:data.table使用自定义函数及Reduce函数实现一次性性多表连接、data.table使用自定义函数及Reduce函数实现一次性性多表连接目录R语言data.table导入数据实战:data.table使用自定义函数及Reduce函数实现一次性性多表连接#data.table是什么?#dat......
  • 《内存函数》
    内存函数1.memcpy函数(1)介绍这里通过memcpy的定义我们可以看这个函数包含三个参数,destination就是拷贝的目的地,source就是拷贝的源头,num就是拷贝的个数。(2)使用这里要包含头文件string.h这里的个数最好写成sizeof(类型)*个数的形式,因为你拷贝什么类型未定(3)memcpy函数......