2018年11月30日 星期五

Kotlin 與 c 相互呼叫的程式碼範例

1. 先編寫一個標頭檔
// bigint.h
#ifndef big_integer_h
#define big_integer_h
#include "gmp.h"
#include "malloc.h"
#include "stdio.h"
char *testgmp(char *str);
void  freegmp(void);
#endif

2. 編寫 c 程式碼, 主要呼叫 gmp 去計算大數, 傳入字串, 計算完答案, 轉成字串再回傳
// bigint.c
#include "bigint.h"
char *hexdigit;
char  *testgmp(char *str) {
    mpz_t z;
    mpz_init(z); 
    mpz_set_str(z, str, 16);
    gmp_printf("Receive: %s, GMP caculate: 0x%Zx * 3: \n", str, z);
    mpz_mul_si(z, z, 3);
    hexdigit = malloc( mpz_sizeinbase(z, 16) * sizeof(char) + 1 );// string buffer = new char[mpz_sizeinbase(z, base) + 1], include EOS
    sprintf(hexdigit, "%s", mpz_get_str(NULL, 16, z)); // string copy
    if (z) mpz_clear(z);   
    return hexdigit;
}
void freegmp(void) { if (hexdigit) free(hexdigit);  }

3.  c 主程式呼叫測試碼
// testbig.c
#include "bigint.h"
int main(){
    printf("return: %s\n", testgmp("123"));
    freegmp();
}

4. 編譯  c 程式, 做成程式庫, 將測試程式與程式庫連結, 看是否成功:
gcc   -c  bigint.c  -o  bigint.o
ar   rcs   libbigint.a   bigint.o
gcc  testbig.c  -L .   -l  bigint  -o  testbig   &&   ./testbig

5. 準備讓 kotlin 來呼叫, 先連接好所需要的相關程式庫:
ln   -sf   /usr/local/lib/libgmp.a   libgmp.a
ln   -sf   /usr/local/include/gmp.h   gmp.h
ln   -sf   libbigint.a   bigint.a

6. 編輯橋接檔案 bigint.def, 將以下內容存檔:
headers =  bigint.h
compilerOpts.linux = -I.
linkerOpts.linux   = -L.  bigint.a libgmp.a

7.用 cinterop 分析上述標頭檔, 產生程式庫:
cinterop   -def   bigint.def   -o  bigint.klib

8. 編寫 Kotlin 程式碼導入程式庫,呼叫由 c 所寫的副程式:
// main.kt
import bigint.*
import kotlinx.cinterop.*
fun main(args: Array) {
  var digit = "123"
  val test  = testgmp(digit.cstr)!!.toKString()
  println("testgmp return $test")
  freegmp();
}

9. 編譯 main.kt 連結程式庫, 執行看是否成功呼叫並回傳答案, 結果應該要與步驟 4 答案一致:
konanc   main.kt   -l  bigint.klib   -o   bigint  &&  ./bigint.kexe

10 後記:
1. 若將 gmp 直接做成(klib), 從 kotlin 呼叫會產生不明錯誤訊息, 只好接間接透過 c 呼叫 GMP.
2. 不能用 g++ 去編譯 c 程式, 否則會產生找不到符號的錯誤訊息
3. Kotlin native compiler (konanc)編譯的速度很慢, 因此在寫 c 程式的階段,就應該用 c 程式碼去測試該程式碼的完整性,最後才整合至 kotlin 程式庫,以節省寶貴時間

沒有留言: