简体   繁体   中英

calling Objective C and C from Swift passing callback function

I am trying to call the HappyTime onvif library from Swift.

I have the library linked in to my project and I am able to call some simple functions, but I am having trouble getting the syntax right in my call which passes my callback function.

Here is the Swift code:

func discoverCameras()
{
    HappyInterface.sharedInstance().startProb()

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

func cameraDiscovered(cameraFound:UnsafeMutablePointer<DEVICE_BINFO>)
{
    table.reloadData()
}

my setProbeCB call gives this error:

Cannot convert value of type '(UnsafeMutablePointer) -> ()' to expected argument type 'UnsafeMutablePointer' (aka 'UnsafeMutablePointer, UnsafeMutablePointer<()>) -> ()>>')

Here is the Obj C implementation:

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

}

This is the Obj C header:

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

This is the C header:

#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__

This is the C code:

/***************************************************************************************/
#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;
        }
    }
}

Here is what the DEVICE_BINFO struct looks like:

typedef struct
{
    int     type;                               // device type
    char    EndpointReference[100];

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

One thing that should be fixed is a mismatch in the number of arguments to the callback. Swift calls the Objective-C setProbeCB() method, giving it a pointer to the cameraDiscovered() function, which takes a single argument. Then setProbeCB() gives the function pointer to the C set_probe_cb() function, which expects a pointer to a function that takes two arguments.

Another observation is that setProbeCB() could just take onvif_probe_cb instead of onvif_probe_cb* and then call C code simply as set_probe_cb(cb, 0) . However, I don't think it makes much difference.

Also, I think the question could have been distilled to a smaller size.

The following is a simplified example based on your original code. It shows how to implement a callback in Swift and have C code call it, but the real fun starts when passing data via callback parameters and return values. It gets very tricky very fast, and that's why the example doesn't show how to deal with DEVICE_BINFO in Swift code. It's a topic in its own right.

The clue to using (Objective-)C functions and types in Swift is figuring out how they are imported into Swift. For example, to find out how onvif_probe_cb is imported, type it on a line in the Swift code, place the cursor in it, and Quick Help will show you this:

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

That tells us the parameter and return types to use in our Swift implementation of the callback. The example is by no means production quality: there are all kinds of things that can go haywire in terms of memory management etc. Please see the code comments for additional info.

First, here is the C code header ( 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 */

Here is the rest of our C source ( 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;
}

Here is the Objective-C wrapper header ( 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 */

And the wrapper implementation ( 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

Finally, here is the Swift code that implements the callback ( main.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()

The bridging header just has #import oclib.h , thus exposing the contents of both C and Objective-C headers to Swift.

The expected output:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM