Mac APP开发,Swift调用C静态链接库的函数

原创 吴就业 117 0 2024-06-23

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/a06d3744cde245aea986d485f7ab872c

作者:吴就业
链接:https://wujiuye.com/article/a06d3744cde245aea986d485f7ab872c
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

Easy TCP Analysis是一款在线TCP数据包分析工具,其核心TCP数据包分析逻辑是用go语言所写。我们在开发离线版本的时候,由于不想同样的逻辑再使用Swift写一遍,于是将go写的核心逻辑抽离为sdk,并利用cgo暴露核心接口,编译为静态链接库。

由于接口比较耗时,我们不想调用接口卡半天再刷新页面,而是一边分析数据包一边刷新页面输出,所以接口可以传递回调函数,每解析一个数据包就回调一次回调函数。关于接口的设计,可以参考上一篇文章:go编译为c静态链接库给c语言调用,c语言传递函数给go语言回调

接着上一篇文章,这篇介绍如何在Swift中直接调用C函数。

第一步,将我们编译好的静态链接库.a文件,拖动到我们的Mac APP项目工程目录下。可以创建个group来放静态库,例如libc

截屏2024-06-23 20.09.06

当我们把静态链接库拖动过去的时候,xcode会弹出如下弹窗。

截屏2024-06-23 18.49.32

记得勾选”Copy items if needed”,然后点击Finish。

第二步:给项目配置我们添加的链接库。

上一步完成后,正常我们添加的静态链接库就可以使用了。我们依次点击项目名->targets->Build Phases,我们能在Link Binary With Libraries下看到我们添加的静态链接库就说明是添加成功的了。

截屏2024-06-23 20.13.03

但如果我们添加的静态链接库还依赖其它静态链接库的话,我们还需要用同样的方法拖进来。如果我们依赖的是系统有的静态库的话,我们可以点击+选择,例如我们的项目依赖的是libpcap,这是Mac系统有的,我们直接点+按钮,在弹窗搜索libpcap,然后选择搜索结果,最后点击Add按钮就添加进来了。

截屏2024-06-23 20.18.27

第三步:编写Swift桥接C的头文件。

这里我命名为TCPAnalysisCore-Bridging-Header.h。这个头文件的作用就是import我们静态库提供的头文件。

截屏2024-06-23 18.50.01

第四步:编写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中再想使用这个变量就会报异常。

#前端

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

使用node:alpine基础镜像今天突然就构建不了了

我的一个nextjs项目使用node:alpine基础镜像,在此之前部署到线上都能正常构建和运行,今天修改点代码提交到生产环境,发现竟然镜像构建失败了。

SwiftUI macOS开发,Button和TextField控件设置背景色不生效的问题

SwiftUI macOS开发,Button和TextField控件设置背景色不生效的问题。

SwiftUI Chart如何修改字体颜色

使用Charts绘制的图表,默认文字都是灰色的,当我们app的主题颜色是固定深色的情况下,图表的文字会看不清,怎么修改呢?

go编译为c静态链接库给c语言调用,c语言传递函数给go语言回调

c语言调用go语言怎么传递回调函数,go怎么调用c传递的回调函数?本篇提供案例代码,实测方法可行。