从 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 中定义的 NSString 扩展
从 Swift 调用 Objective C 自定义初始化函数