Easy TCP Analysis是一款在线TCP数据包分析工具,其核心TCP数据包分析逻辑是用go语言所写。我们在开发离线版本的时候,由于不想同样的逻辑再使用Swift写一遍,于是将go写的核心逻辑抽离为sdk,并利用cgo暴露核心接口,编译为静态链接库。
由于接口比较耗时,我们不想调用接口卡半天再刷新页面,而是一边分析数据包一边刷新页面输出,所以接口可以传递回调函数,每解析一个数据包就回调一次回调函数。关于接口的设计,可以参考上一篇文章:go编译为c静态链接库给c语言调用,c语言传递函数给go语言回调
接着上一篇文章,这篇介绍如何在Swift中直接调用C函数。
第一步,将我们编译好的静态链接库.a
文件,拖动到我们的Mac APP项目工程目录下。可以创建个group来放静态库,例如libc
。
当我们把静态链接库拖动过去的时候,xcode会弹出如下弹窗。
记得勾选”Copy items if needed”,然后点击Finish。
第二步:给项目配置我们添加的链接库。
上一步完成后,正常我们添加的静态链接库就可以使用了。我们依次点击项目名->targets->Build Phases,我们能在Link Binary With Libraries下看到我们添加的静态链接库就说明是添加成功的了。
但如果我们添加的静态链接库还依赖其它静态链接库的话,我们还需要用同样的方法拖进来。如果我们依赖的是系统有的静态库的话,我们可以点击+
选择,例如我们的项目依赖的是libpcap,这是Mac系统有的,我们直接点+
按钮,在弹窗搜索libpcap
,然后选择搜索结果,最后点击Add按钮就添加进来了。
第三步:编写Swift桥接C的头文件。
这里我命名为TCPAnalysisCore-Bridging-Header.h
。这个头文件的作用就是import我们静态库提供的头文件。
第四步:编写Swift代码调用C函数。
我们的静态链接库暴露给Swift调用的接口函数、以及参数结构体、回调函数的c代码如下:
typedef void (*AddStream)(char *streamId);
typedef void (*AppendPackage)(char *streamId,char *pkg,char *note,char *commentary,char *connStatus);
typedef void (*RefreshDiagnostic)(char *streamId,char *diagnostic);
typedef void (*RefreshMetric)(int totalConn,int successConn,int semiConn,int retOrNotAckConn,int notSynConn,int notFinConn);
typedef void (*RefreshStreamTags)(char *streamId,char *tags);
typedef struct {
AddStream addStream;
AppendPackage appendPackage;
RefreshDiagnostic refreshDiagnostic;
RefreshMetric refreshMetric;
RefreshStreamTags refreshStreamTags;
} AnalysisCallbacks;
extern char* AnalysisPcapFile(char* filePath, AnalysisCallbacks* cbs);
extern char* ListNetworkInterfaces();
extern char* AnalysisWithCapture(char* networkInterfaceName, AnalysisCallbacks* cbs);
extern void InterruptAnalysisWithCapture();
上面的c代码对应的Swift代码如下,这是需要我们自己写的:
//
// TCPAnalysisCore.swift
// ChatTCP
//
// Created by wujiuye on 2024/6/23.
//
import Foundation
// 定义闭包类型,对应C语言中的函数指针
typealias AddStream = @convention(c) (UnsafePointer<CChar>?) -> Void
typealias AppendPackage = @convention(c) (UnsafePointer<CChar>?, UnsafePointer<CChar>?, UnsafePointer<CChar>?, UnsafePointer<CChar>?, UnsafePointer<CChar>?) -> Void
typealias RefreshDiagnostic = @convention(c) (UnsafePointer<CChar>?, UnsafePointer<CChar>?) -> Void
typealias RefreshMetric = @convention(c) (CInt, CInt, CInt, CInt, CInt, CInt) -> Void
typealias RefreshStreamTags = @convention(c) (UnsafePointer<CChar>?, UnsafePointer<CChar>?) -> Void
// 定义包含闭包的结构体,对应C语言中的结构体
internal struct AnalysisCallbacks {
var addStream: AddStream
var appendPackage: AppendPackage
var refreshDiagnostic: RefreshDiagnostic
var refreshMetric: RefreshMetric
var refreshStreamTags: RefreshStreamTags
}
// 使用 @_silgen_name 属性来声明 C 函数
@_silgen_name("AnalysisPcapFile")
func c_AnalysisPcapFile(filePath: UnsafePointer<CChar>?,
cbs: UnsafeMutablePointer<AnalysisCallbacks>?) -> UnsafeMutablePointer<CChar>?
@_silgen_name("ListNetworkInterfaces")
func c_ListNetworkInterfaces() -> UnsafeMutablePointer<CChar>?
@_silgen_name("AnalysisWithCapture")
func c_AnalysisWithCapture(networkInterfaceName: UnsafePointer<CChar>?,
cbs: UnsafeMutablePointer<AnalysisCallbacks>?) -> UnsafeMutablePointer<CChar>?
@_silgen_name("InterruptAnalysisWithCapture")
func c_InterruptAnalysisWithCapture()
我们需要使用@_silgen_name注解来为Swift函数关联到c函数。回调函数需要添加@convention(c)
注解,否则回调结果会不正确。
调用案例:
func createAnalysisCallbacks()->AnalysisCallbacks{
return AnalysisCallbacks(
addStream: { streamId in
print("Add stream: \(String(cString: streamId!,encoding: .utf8) ?? "")")
},
appendPackage: { streamId, pkg, note, commentary, connStatus in
print("Stream: \(String(cString: streamId!,encoding: .utf8) ?? "")")
print("Package: \(String(cString: pkg!,encoding: .utf8) ?? "")")
print("Note: \(String(cString: note!,encoding: .utf8) ?? "")")
print("Commentary: \(String(cString: commentary!,encoding: .utf8) ?? "")")
print("ConnStatus: \(String(cString: connStatus!,encoding: .utf8) ?? "")")
},
refreshDiagnostic: { streamId, diagnostic in
print("Stream: \(String(cString: streamId!,encoding: .utf8) ?? ""), Diagnostic: \(String(cString: diagnostic!,encoding: .utf8) ?? "")")
},
refreshMetric: { totalConn, successConn, semiConn, retOrNotAckConn, notSynConn, notFinConn in
print("Metric: \(totalConn), \(successConn), \(semiConn), \(retOrNotAckConn), \(notSynConn), \(notFinConn)")
},
refreshStreamTags: { streamId, tags in
print("Stream: \(String(cString: streamId!,encoding: .utf8) ?? ""),Tags: \(String(cString: tags!,encoding: .utf8) ?? "")")
}
)
}
func testAnalysisPcapFile(_ fileUrl:String) {
let filePath = fileUrl.cString(using: .utf8)
var callbacks = createAnalysisCallbacks()
// 使用 withUnsafeMutablePointer 来获取指向 callbacks 的指针
withUnsafeMutablePointer(to: &callbacks) { cbsPtr in
if let result = c_AnalysisPcapFile(filePath: filePath, cbs: cbsPtr) {
print("Result: \(String(cString: result))")
free(result) // 释放内存
}
}
}
踩坑记:Swift调用c,c调用go,go不要使用go func
切换到另一个协程去执行异步逻辑,因为Swift方法执行完成后,我们在Swift传递给go的参数是个函数内的局部变量,swift函数执行完这个变量就被回收了,go中再想使用这个变量就会报异常。