編譯 c 程式可以寫一個 Makefile, 方便下指令 make 讓電腦自動去執行:
MYLIB = libmyfunction.a
$(MYLIB): myfunction.o
ar rcs $@ $^
myfunction.o: myfunction.c
clang -c $< -o $@
clean:
rm -f *.o *.a $(MYLIB)
install:
sudo cp $(MYLIB) /usr/local/lib/$(MYLIB)
上述中 myfunction.c 及 myfunction.h 就是用 c 寫的程式碼. 最後如果要安裝到 /usr/local/lib 會需要用到 sudo 指令, 因此 user 必須要有寫入權限才可以, 對於將字串參數傳遞進 c 程式, 我試的結果要宣告程 const char * 才可以, 否則在 swift 編譯連結時會有錯誤訊息
一旦將程式除錯完畢, 就要準備好 swift 與 c 溝通的中間檔 module.modulemap, 範例內容如下:
module Name [system] {
header "mymodule.h"
link "myfunction"
export *
}
上述 header 指示要到那去尋找標頭檔 , 如果有多個標頭檔時, 可以另外寫一個標頭檔,裏面用 #include 指示將其它標頭檔包進來, 並將 module.modulemap 的 header 改成 umbrella header. 若是不想寫這種標頭檔, 那就把所有要包進來的檔放在一個目錄, 並將 module.modulemap 的標頭指示改成 umbrella 並提供該目錄名稱(最後一個字要加上/),
link 指示需要連結哪些程式庫(library)
最後全部需 export 出來讓 linker 可以找到裏面的程序符號, 以便相連結.
最開頭的 Name 就是將來寫 swift 程式要 import 進來的程式庫名稱, 他是可以自行命名的. 上述程序都完成後, 接著要將該 module所在的目錄用版本控制程式讓 git 來追蹤(因 swift 的 package manager 會用 git clone 指令來複製該模組), 底下列出一些常用的 git 指令, 需視需要加以添加
git init
git config user.name "YourName"
git config user.email "name@somewhere"
git add Package.swift module.modulemap Makefile
git add example.c example.h
git commit -m "This is an example"
git tag 0.0.1
最後便是寫一個 swift 專案來測試程式, 該專案主目錄下的 Package.swift 需要作一些指示, 才能使用上述的 module, 範例內容如下:
import PackageDescription
let package = Package(
dependencies: [
.package(url:"to/module/path", version: Version(0,0,1) ..< Version(2,0,0)
)
上述其實是 swift 語言的寫法, 他是說要使用到內部 PackageDescription 模組,將他含括進來運作,讓 package 初始化相依值( dependencices ) 讓他成員的位置 (url)指向模組所在的目錄, 並且版本編號指定用 0.0.1 與 2.0.0 之間的檔案, 確定後把它下載到該專案下面(通常是目錄 Packages 內 )
而專案要寫的程式碼就在該專案下建一個目錄名稱, 定名為 Sources或 Source或 src 或 srcs 都可以, 將全部的原始碼放到該目錄下面. 最後下一個指令:
swift build
如果一切正常, 將會看到執行檔放在 .build/debug/ 目錄下
func show(ptr:UnsafePointer<Int>) {
print("Pointer is:\(ptr)\n")
ptr.memory += 1
}
var a=10
show(&a) // don't use constant property, show the address of the stored property
inc(&a) // don't use constant property, increase 1 in the stored property
let c=withUnsafeMutablePointer(&a) { (a) -> Int in
a.memory += 1 // increase a
return 1 // but return 1, return TYPE must be same as a
}
// now a=11, c=1
最後要提到的是如果用 c 寫的副程式使用了 variadic parameter 時, 像類似printf (const char *str, ...) 在最後的宣告有3個句點的副程式, 它是不能被 swift 程式直接呼叫的, 要解決這問題, 只能再寫一個 c 程式碼來橋接 swift 的程式, 這個程式是要給 swift 呼叫用的, 他必須要有明確數量的參數, 由該橋接副程式再去呼叫有 variadic parameter 的 C 副程式就可以了, 例如:
int Myprintf(const char *fmt) // this function can be call by swift program
{
// call a C function which need variadic parameters
if( fmt!=NULL ) printf(fmt);
}
後記:
1. 實際上 C 與 Swift 都是將 Variadic parameters 一個一個塞到陣列裡去, 再將該陣列的指標傳過去的. 因此處理整數或符點數或字元時要注意兩邊的型別要一致才行
提到可將 c 所寫的副程式的 variadic parameters, 不要宣告成 int f( int, ...)的形式, 而是改用 int f(int, va_list arguments)的形式宣告, 這樣就可以直接接收不定參數陣列的指標, 之後再來加以運用.詳見下例:
// C function
#include "stdarg.h"
int SumOf(int count,va_list arglist)
{
int sum;
for( sum=0;count>0;count--) sum += va_arg(arglist,int);
return sum;
}
// Swift
func swiftF(x: CInt, arglist: CVarArgType...) -> CInt {
return withVaList(arglist) { SumOf(x, $0) }
}
swiftF(10,1,2,3,4,5,6,7,8,9,10) // sum of 1...10, count=10
swiftF(5,1,2,3,4,5) // sum of 1..5, count=5
4. 如果要將 C 的字串轉成 swift 的 String 型別時使用 String.fromCString(cstr) 來轉換, 他所轉初的字串型別是 String?, 也就是有可能是 nil 的字串型別.
沒有留言:
張貼留言