我正试图将linux支持添加到我的swift包库中以获取系统信息,但我不知道如何在swift包中访问linux上的sysctlbyname
函数。
对于它的所有检测库依赖于sysctlbyname
函数,可以通过在苹果平台上导入Dariwn.sys.sysctl
轻松访问,但是我找不到任何Swift方法来访问linux上的该函数,尽管事实上你可以在C中通过在任何unix平台上导入sys/sysctl.h访问它。
所以我想知道如何在linux上访问我的Swift库中的函数,如果有可能做到这一点,而不必使用C或其他非Swift的东西,也因为我想保持我的代码与苹果系统的Swift游乐场应用程序兼容,它不支持带有C导入的SPM库。
只是作为参考,我在这里留下负责在我的项目中与sysctlbyname
接口的代码的一部分:
import Foundation
#if os(Linux)
import Glibc //? not sure about where i can find `sysctlbyname` in linux without using C headers
#else
import Darwin.sys.sysctl
#endif
///Generic protocol to allow easy fetching of values out of `sysctlbyname`
public protocol SysctlFetch{
static var namePrefix: String {get}
}
public extension SysctlFetch{
///Gets a `String` from the `sysctlbyname` function
static func getString(_ valueName: String) -> String?{
var size: size_t = 0
let name = namePrefix + valueName
var res = sysctlbyname(name, nil, &size, nil, 0)
if res != 0 {
return nil
}
var ret = [CChar].init(repeating: 0, count: size + 1)
res = sysctlbyname(name, &ret, &size, nil, 0)
return res == 0 ? String(cString: ret) : nil
}
///Gets an Integer value from the `sysctlbyname` function
static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
var ret = T()
var size = MemoryLayout.size(ofValue: ret)
let res = sysctlbyname(namePrefix + valueName, &ret, &size, nil, 0)
return res == 0 ? ret : nil
}
///Gets a `Bool` value from the `sysctlbyname` function
static func getBool(_ valueName: String) -> Bool?{
guard let res: Int32 = getInteger(valueName) else{
return nil
}
return res == 1
}
}
和一个如何在代码中使用它的例子(诅咒它被用来检索更多的东西):
///Kernel info
final class KernelInfo: SysctlFetch{
static var namePrefix: String{
#if os(Linux)
return "kernel."
#else
return "kern."
#endif
}
///The os kernel name
static var ostype: String?{
return Self.getString("ostype")
}
/* Other static vars here */
}
所以可以添加C, c++或Objective-C目标到Swift包中,这样就可以导入所需的系统头文件,然后创建一些包装器函数,这使得需要的东西可以被Swift访问,但这破坏了Swift应用程序开发的兼容性,因为它只支持Swift目标(一个可能的解决方案是将C/c++目标放在一个单独的Swift包中,以便有条件地将其作为依赖项仅用于linux)。
所以添加一个C/c++目标可以解决这个问题,但问题是,在Linux内核5.5及以后的版本中,sysctl
函数已经被弃用,甚至在旧的内核中,它们在Linux支持的所有cpu架构上都不可用,因此在运行最新内核或某些特定的非x86 cpu架构的计算机上,这样的Swift包将无法成功构建。
当前访问sysctl
函数提供的信息的方法是直接从/proc/sys
目录内的文件系统中读取,它适用于所有支持的cpu体系结构,并且sysctl
命令行实用程序可以获得该数据。
所以只有在linux上,代码必须像这样修改,才能成功地在所有平台上收集数据:
import Foundation
#if os(Linux)
import Glibc //? not sure about where i can find `sysctlbyname` in linux without using C headers
#else
import Darwin.sys.sysctl
#endif
///Generic protocol to allow easy fetching of values out of `sysctlbyname`
public protocol SysctlFetch{
static var namePrefix: String {get}
}
public extension SysctlFetch{
#if !os(Linux)
///Gets a `String` from the `sysctlbyname` function
static func getString(_ valueName: String) -> String?{
var size: size_t = 0
let name = namePrefix + valueName
var res = sysctlbyname(name, nil, &size, nil, 0)
if res != 0 {
return nil
}
var ret = [CChar].init(repeating: 0, count: size + 1)
res = sysctlbyname(name, &ret, &size, nil, 0)
return res == 0 ? String(cString: ret) : nil
}
///Gets an Integer value from the `sysctlbyname` function
static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
var ret = T()
var size = MemoryLayout.size(ofValue: ret)
let res = sysctlbyname(namePrefix + valueName, &ret, &size, nil, 0)
return res == 0 ? ret : nil
}
#else
///Gets a `String` from `/proc/sys`
static func getString(_ valueName: String) -> String?{
let path = "/proc/sys/" + (namePrefix + valueName).replacingOccurrences(of: ".", with: "/")
var contents = ""
do{
contents = try String(contentsOfFile: path)
}catch let err{
return nil
}
if contents.last == "n"{
contents.removeLast()
}
return contents
}
///Gets an Integer value from `/proc/sys`
static func getInteger<T: FixedWidthInteger>(_ valueName: String) -> T?{
guard let str = getString(valueName) else { return nil }
return T(str)
}
#endif
///Gets a `Bool` value from the `sysctlbyname` function
static func getBool(_ valueName: String) -> Bool?{
guard let res: Int32 = getInteger(valueName) else{
return nil
}
return res == 1
}
}
所以最后我自己弄明白了,我希望这能对任何做同样事情的人有用。