从 Swift 调用 Objective C 和 C 传递回调函数

Posted

技术标签:

【中文标题】从 Swift 调用 Objective C 和 C 传递回调函数【英文标题】:calling Objective C and C from Swift passing callback function 【发布时间】:2016-04-01 14:18:51 【问题描述】:

我正在尝试从 Swift 调用 HappyTime onvif 库。

我将库链接到我的项目中,并且我可以调用一些简单的函数,但是我无法在调用中正确获取传递回调函数的语法。

这是 Swift 代码:

func discoverCameras()

    HappyInterface.sharedInstance().startProb()

//this line gives syntax error
    HappyInterface.sharedInstance().setProbeCB(cameraDiscovered)


func cameraDiscovered(cameraFound:UnsafeMutablePointer<DEVICE_BINFO>)

    table.reloadData()

我的setProbeCB 调用给出了这个错误:

无法将类型“(UnsafeMutablePointer) -> ()”的值转换为预期的参数类型“UnsafeMutablePointer”(又名“UnsafeMutablePointer, UnsafeMutablePointer) -> ()>>”)

这是 Obj C 的实现:

- (void) setProbeCB:(onvif_probe_cb *)cb 
    set_probe_cb(*cb, 0);


这是 Obj C 标头:

- (void) setProbeCB:(onvif_probe_cb *)cb;

这是 C 头文件:

#ifndef __H_ONVIF_PROBE_H__
#define __H_ONVIF_PROBE_H__

#include "onvif.h"


typedef void (* onvif_probe_cb)(DEVICE_BINFO * p_res, void * pdata);

#ifdef __cplusplus
extern "C" 
#endif

ONVIF_API void set_probe_cb(onvif_probe_cb cb, void * pdata);
ONVIF_API void set_probe_interval(int interval);
ONVIF_API int  start_probe(int interval);
ONVIF_API void stop_probe();
ONVIF_API void send_probe_req();


#ifdef __cplusplus

#endif

#endif  //  __H_ONVIF_PROBE_H__

这是 C 代码:

/***************************************************************************************/
#define MAX_PROBE_FD    8


/***************************************************************************************/
onvif_probe_cb g_probe_cb = 0;
void * g_probe_cb_data = 0;
pthread_t g_probe_thread = 0;
int g_probe_fd[MAX_PROBE_FD];
int g_probe_interval = 30;
BOOL g_probe_running = FALSE;


/***************************************************************************************/
int onvif_probe_init(unsigned int ip)
   
    int opt = 1;
    SOCKET fd;
    struct sockaddr_in addr;
    struct ip_mreq mcast;

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0)
    
        log_print(LOG_ERR, "socket SOCK_DGRAM error!\n");
        return -1;
    

    addr.sin_family = AF_INET;
    addr.sin_port = htons(3702);
    addr.sin_addr.s_addr = ip;

    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    
        // if port 3702 already occupied, only receive unicast message
        addr.sin_port = 0;
        if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
        
            closesocket(fd);
            log_print(LOG_ERR, "bind error! %s\n", sys_os_get_socket_error());
            return -1;
        
    

    /* reuse socket addr */  
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt))) 
      
        log_print(LOG_WARN, "setsockopt SO_REUSEADDR error!\n");
    

    memset(&mcast, 0, sizeof(mcast));
    mcast.imr_multiaddr.s_addr = inet_addr("239.255.255.250");
    mcast.imr_interface.s_addr = ip;

    if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast)) < 0)
    
#if __WIN32_OS__
        if(setsockopt(fd, IPPROTO_IP, 5, (char*)&mcast, sizeof(mcast)) < 0)
#endif      
        
            closesocket(fd);
            log_print(LOG_ERR, "setsockopt IP_ADD_MEMBERSHIP error! %s\n", sys_os_get_socket_error());
            return -1;
        
    

    return fd;


char probe_req1[] = 
    "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
    "<Envelope xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns=\"http://www.w3.org/2003/05/soap-envelope\">"
    "<Header>"
    "<wsa:MessageID xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">uuid:%s</wsa:MessageID>"
    "<wsa:To xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>"
    "<wsa:Action xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>"
    "</Header>"
    "<Body>"
    "<Probe xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\">"
    "<Types>tds:Device</Types>"
    "<Scopes />"
    "</Probe>"
    "</Body>"
    "</Envelope>";  

char probe_req2[] = 
    "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
    "<Envelope xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\" xmlns=\"http://www.w3.org/2003/05/soap-envelope\">"
    "<Header>"
    "<wsa:MessageID xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">uuid:%s</wsa:MessageID>"
    "<wsa:To xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>"
    "<wsa:Action xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>"
    "</Header>"
    "<Body>"
    "<Probe xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\">"
    "<Types>dn:NetworkVideoTransmitter</Types>"
    "<Scopes />"
    "</Probe>"
    "</Body>"
    "</Envelope>";

int onvif_probe_req_tx(int fd)

    int len;
    int rlen;
    char  * p_bufs = NULL;
    struct sockaddr_in addr;

    int buflen = 10*1024;

    p_bufs = (char *)malloc(buflen);
    if (NULL == p_bufs)
    
        return -1;
    

    memset(p_bufs, 0, buflen);
    sprintf(p_bufs, probe_req1, onvif_uuid_create());

    memset(&addr, 0, sizeof(addr));

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("239.255.255.250");
    addr.sin_port = htons(3702);

    len = strlen(p_bufs);
    rlen = sendto(fd, p_bufs, len, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    if (rlen != len)
    
        log_print(LOG_ERR, "onvif_probe_req_tx::rlen = %d,slen = %d\r\n", rlen, len);
    

    usleep(1000);

    memset(p_bufs, 0, buflen);
    sprintf(p_bufs, probe_req2, onvif_uuid_create());

    len = strlen(p_bufs);
    rlen = sendto(fd, p_bufs, len, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    if (rlen != len)
    
        log_print(LOG_ERR, "onvif_probe_req_tx::rlen = %d,slen = %d\r\n", rlen, len);
       

    free(p_bufs);

    return rlen;


BOOL onvif_parse_device_binfo(XMLN * p_node, DEVICE_BINFO * p_res)

    XMLN * p_EndpointReference;
    XMLN * p_Types;
    XMLN * p_XAddrs;

    p_EndpointReference = xml_node_soap_get(p_node, "EndpointReference");
    if (p_EndpointReference)
    
        XMLN * p_Address = xml_node_soap_get(p_EndpointReference, "Address");
        if (p_Address && p_Address->data)
        
            strncpy(p_res->EndpointReference, p_Address->data, sizeof(p_res->EndpointReference)-1);
        
    

    p_Types = xml_node_soap_get(p_node, "Types");
    if (p_Types && p_Types->data)
    
        p_res->type = parse_DeviceType(p_Types->data);
    

    p_XAddrs = xml_node_soap_get(p_node, "XAddrs");
    if (p_XAddrs && p_XAddrs->data)
    
        parse_XAddr(p_XAddrs->data, &p_res->XAddr);

        if (p_res->XAddr.host[0] == '\0' || p_res->XAddr.port == 0)
        
            return FALSE;
        
    
    else
    
        return FALSE;
       

    return TRUE;


BOOL onvif_probe_res(XMLN * p_node, DEVICE_BINFO * p_res)

    XMLN * p_body = xml_node_soap_get(p_node, "Body");
    if (p_body)
    
        XMLN * p_ProbeMatches = xml_node_soap_get(p_body, "ProbeMatches");
        if (p_ProbeMatches)
        
            XMLN * p_ProbeMatch = xml_node_soap_get(p_ProbeMatches, "ProbeMatch");
            while (p_ProbeMatch && soap_strcmp(p_ProbeMatch->name, "ProbeMatch") == 0)
            
                if (onvif_parse_device_binfo(p_ProbeMatch, p_res))
                
                    if (g_probe_cb)
                    
                        g_probe_cb(p_res, g_probe_cb_data);
                    
                

                p_ProbeMatch = p_ProbeMatch->next;
            
        
        else
        
            XMLN * p_Hello = xml_node_soap_get(p_body, "Hello");
            if (p_Hello)
               
                if (onvif_parse_device_binfo(p_Hello, p_res))
                
                    if (g_probe_cb)
                    
                        g_probe_cb(p_res, g_probe_cb_data);
                    
                
            
        
    

    return TRUE;


int onvif_probe_net_rx()

    int i;
    int ret;
    int maxfd = 0;
    int fd = 0;
    char rbuf[10*1024];
    fd_set fdread;
    struct timeval tv = 1, 0;

    FD_ZERO(&fdread);

    for (i = 0; i < MAX_PROBE_FD; i++)
    
        if (g_probe_fd[i] > 0)
        
            FD_SET(g_probe_fd[i], &fdread); 

            if (g_probe_fd[i] > maxfd)
            
                maxfd = g_probe_fd[i];
            
        
    

    ret = select(maxfd+1, &fdread, NULL, NULL, &tv); 
    if (ret == 0) // Time expired 
     
        return 0; 
    

    for (i = 0; i < MAX_PROBE_FD; i++)
    
        if (g_probe_fd[i] > 0 && FD_ISSET(g_probe_fd[i], &fdread))
        
            int rlen;
            int addr_len;
            struct sockaddr_in addr;
            unsigned int src_ip;
            unsigned int src_port;
            XMLN * p_node;

            fd = g_probe_fd[i];

            addr_len = sizeof(struct sockaddr_in);
            rlen = recvfrom(fd, rbuf, sizeof(rbuf), 0, (struct sockaddr *)&addr, (socklen_t*)&addr_len);
            if (rlen <= 0)
            
                log_print(LOG_ERR, "onvif_probe_net_rx::rlen = %d, fd = %d\r\n", rlen, fd);
                continue;
            

            src_ip = addr.sin_addr.s_addr;
            src_port = addr.sin_port;

            p_node = xxx_hxml_parse(rbuf, rlen);
            if (p_node == NULL)
            
                log_print(LOG_ERR, "onvif_probe_net_rx::hxml parse err!!!\r\n");
               
            else
            
                DEVICE_BINFO res;
                memset(&res, 0, sizeof(DEVICE_BINFO));

                onvif_probe_res(p_node, &res);      
            

            xml_node_del(p_node);
        
    

    return 1;


void * onvif_probe_thread(void * argv)

    int count = 0;

    int i = 0;
    int j = 0;

    for (; i < get_if_nums() && j < MAX_PROBE_FD; i++, j++)
    
        unsigned int ip = get_if_ip(i);     
        if (ip != 0 && ip != inet_addr("127.0.0.1"))
        
            g_probe_fd[j] = onvif_probe_init(ip);
        
    

    for (i = 0; i < MAX_PROBE_FD; i++)
    
        if (g_probe_fd[i] > 0)
        
            onvif_probe_req_tx(g_probe_fd[i]);  
        
    

    while (g_probe_running)
    
        if (onvif_probe_net_rx() == 0)
        
            count++;
        

        if (count >= g_probe_interval)
        
            count = 0;

            for (i = 0; i < MAX_PROBE_FD; i++)
            
                if (g_probe_fd[i] > 0)
                
                    onvif_probe_req_tx(g_probe_fd[i]);  
                
                   
        

        usleep(1000);
    

    g_probe_thread = 0;

    return NULL;


ONVIF_API void set_probe_cb(onvif_probe_cb cb, void * pdata)

    g_probe_cb = cb;
    g_probe_cb_data = pdata;



ONVIF_API void send_probe_req()

    int i;  
    for (i = 0; i < MAX_PROBE_FD; i++)
    
        if (g_probe_fd[i] > 0)
        
            onvif_probe_req_tx(g_probe_fd[i]);  
        
    


ONVIF_API void set_probe_interval(int interval)

    g_probe_interval = interval;

    if (g_probe_interval < 10)
    
        g_probe_interval = 30;
    


ONVIF_API int start_probe(int interval)

    g_probe_running = TRUE;

    set_probe_interval(interval);

    g_probe_thread = sys_os_create_thread((void *)onvif_probe_thread, NULL);
    if (g_probe_thread)
    
        return 0;
    

    return -1;


ONVIF_API void stop_probe()

    int i;

    g_probe_running = FALSE;

    while (g_probe_thread)
    
        usleep(1000);
    

    for (i = 0; i < MAX_PROBE_FD; i++)
    
        if (g_probe_fd[i] > 0)
        
            closesocket(g_probe_fd[i]);
            g_probe_fd[i] = 0;
        
    

DEVICE_BINFO 结构如下所示:

typedef struct

    int     type;                               // device type
    char    EndpointReference[100];

    onvif_XAddr XAddr;                          // xaddr, include port host, url
 DEVICE_BINFO;

【问题讨论】:

【参考方案1】:

应该修复的一件事是回调的参数数量不匹配。 Swift 调用 Objective-C 的 setProbeCB() 方法,给它一个指向 cameraDiscovered() 函数的指针,该函数接受一个 single 参数。然后setProbeCB() 将函数指针提供给C set_probe_cb() 函数,该函数需要一个指向带有两个 参数的函数的指针。

另一个观察结果是setProbeCB() 可以只使用onvif_probe_cb 而不是onvif_probe_cb*,然后将C 代码简单地调用为set_probe_cb(cb, 0)。但是,我认为这没有太大区别。

另外,我认为这个问题可以提炼成更小的问题。

以下是基于您的原始代码的简化示例。它展示了如何在 Swift 中实现回调并让 C 代码调用它,但真正有趣的是通过回调参数和返回值传递数据。它很快就变得非常棘手,这就是为什么这个例子没有展示如何在 Swift 代码中处理DEVICE_BINFO。它本身就是一个话题。

在 Swift 中使用 (Objective-)C 函数和类型的线索是弄清楚它们是如何导入到 Swift 中的。例如,要了解onvif_probe_cb 是如何导入的,请在 Swift 代码中的一行上键入它,然后将光标放在其中,快速帮助将向您显示:

Declaration: typealias onvif_probe_cb = (UnsafeMutablePointer<DEVICE_BINFO>, UnsafeMutablePointer<Void>) -> Void
Declared in: clib.h

这告诉我们在回调的 Swift 实现中使用的参数和返回类型。 该示例绝不是生产质量:在内存管理等方面有各种各样的事情可能会出现问题。有关更多信息,请参阅代码 cmets。

首先,这里是 C 代码头(clib.h):

#ifndef clib_h
#define clib_h

#include <stdio.h>

typedef struct 
    char hostname[50];
    int32_t port;
    char url[200];
 onvif_XAddr;

typedef struct

    int     type;                               // device type
    char    EndpointReference[100];

    onvif_XAddr XAddr;                          // xaddr, include port host, url
 DEVICE_BINFO;

/** 
 * This is the typedef of the function pointer to be used for our callback.
 * The function takes a pointer to DEVICE_BINFO and a pointer to some arbitrary
 * data meaningful to the code that provides the callback implementation.  It will
 * be NULL in this example.
 */
typedef void (* onvif_probe_cb)(DEVICE_BINFO * p_res, void * pdata);

/**
 * A function to set the callback.
 */
void set_probe_cb(onvif_probe_cb cb, void * pdata);

/**
 * This is a function that calls the callback.
 */
void find_device();

#endif /* clib_h */

这是我们的 C 源代码的其余部分 (clib.c):

#include "clib.h"
#include <string.h>

onvif_probe_cb gCB = 0;   // global variable to store the callback pointer
void * gUserData = 0;     // global variable to store pointer to user data
DEVICE_BINFO gDeviceInfo; // global variable to store device info struct

void find_device() 
    // Set up gDeviceInfo
    gDeviceInfo.XAddr.port = 1234;
    strcpy( gDeviceInfo.XAddr.hostname, "myhost");
    strcpy( gDeviceInfo.XAddr.url, "http://junk.com");
    gDeviceInfo.type = 777;
    // ... and, if a callback is available, call it with the device info
    if (gCB) gCB(&gDeviceInfo, gUserData);
    else puts("No callback available");


void set_probe_cb(onvif_probe_cb cb, void * pdata) 
    gCB = cb;
    gUserData = pdata;

这里是 Objective-C 包装头 (oclib.h):

#ifndef oclib_h
#define oclib_h

#import "clib.h"
#import <Foundation/Foundation.h>

/**
 * Interface of an Objective-C wrapper around C code in clib.*. We could have
 * gone straight to C from Swift, but I'm trying to keep the example close to the
 * code in the question.  Also, this extra Objective C layer could be helpful in
 * translating data structures, such as DEVICE_BINFO, between C and Swift, since 
 * Objective-C plays much nicer with C data types.  This is no surprise: any C code
 * is valid Objective-C (Objective-C is a strict superset of C).
 */
@interface MyWrapper : NSObject

-(id)init;
// Please note: this one takes a single argument, while the C function it wraps
// takes 2; see the implementation.
-(void) setProbeCB:(onvif_probe_cb) cb;
-(void) findDevice;

@end

#endif /* oclib_h */

以及包装器实现 (oclib.m):

#import "oclib.h"

/**
 * Implementation of our Objective-C wrapper.
 */
@implementation MyWrapper

-(id)init  return self; 

-(void) setProbeCB:(onvif_probe_cb) cb 
    // We don't want anything other than device info to be passed back and
    // forth via the callback, so this wrapper function takes a single argument
    // and passes 0 as the 2nd argument to the wrapped C function.
    set_probe_cb(cb, 0);


-(void) findDevice 
    find_device();


@end

最后,这里是实现回调 (main.swift) 的 Swift 代码:

var w : MyWrapper = MyWrapper()

/**
 * This is the callback implementation in Swift.  We don't use the 2nd argument, userData, but it still
 * has to be present to satisfy the way the callback function pointer is specified in C code.
 */
func cameraDiscovered( info : UnsafeMutablePointer<DEVICE_BINFO>, userData : UnsafeMutablePointer<Void>) 
    print("Called the Swift callback!")

    let devInfo : DEVICE_BINFO = info.memory;
    print( "The device type is \(devInfo.type)")
    print( "The device port is \(devInfo.XAddr.port)")


// Provide the callback to C code via Objective-C
w.setProbeCB(cameraDiscovered)

// ... and call a function that will cause the C code to invoke the callback.
w.findDevice()

桥接头只有#import oclib.h,因此将 C 和 Objective-C 标头的内容都暴露给 Swift。

预期输出:

Called the Swift callback!
The device type is 777
The device port is 1234

【讨论】:

我认为我的问题的本质是我不知道如何将函数从 Swift 传递给 Objective C。这甚至可能吗?没有争论的更简单的例子。 Swift 代码:objectivecfunction(swiftfunction) func swiftfunction() 对于这个更简单的情况,将以下内容放入 Objective C 标头中:typedef void (*cb_t) (); void ocfunction(cb_t); 在 Objective C 实现中:void ocfunction(cb_t cb) cb(); 确保将 Obj C 标头导入到桥接头中。然后在 Swift 中:func swiftfunction() print("Hi from Swift callback!") ; ocfunction(swiftfunction)

以上是关于从 Swift 调用 Objective C 和 C 传递回调函数的主要内容,如果未能解决你的问题,请参考以下文章

从objective-c++调用swift函数

无法从Objective C中的swift类调用方法

从 Objective C 调用 Swift 中定义的 NSString 扩展

从 Swift 调用 Objective C 自定义初始化函数

是否可以使用情节提要从 swift 类中调用 Objective c 视图控制器类?

swift和c/c++的混编