引言
主要是官方教程 Create an app using C Interop and libcurl – tutorial 的实践,记录遇到的一些问题
步骤
创建项目
你可以选择clone官方的模板项目,或是按照模板自行新建一个
主要是修改了两个源集的名字,引入了kmp
插件,关键配置如下:
plugins {
kotlin("multiplatform") version "2.0.0"
}
kotlin {
val hostOs = System.getProperty("os.name")
val isArm64 = System.getProperty("os.arch") == "arch64"
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" && isArm64 -> macosArm64("native")
hostOs == "Mac OS X" && !isArm64 -> macosX64("native")
hostOs == "Linux" && isArm64 -> linuxArm64("native")
hostOs == "Linux" && !isArm64 -> linuxX64("native")
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
nativeTarget.apply {
binaries {
executable {
entryPoint = "io.sakurasou.main"
}
}
}
sourceSets {
val nativeMain by getting
val nativeTest by getting
}
}
另,将kotlin.mpp.applyDefaultHierarchyTemplate=false
添加到gradle.properties
,原因:
w: The Default Kotlin Hierarchy Template was not applied to 'root project 'kotlin-native-demo'':
Source sets created by the following targets will clash with source sets created by the template:
[native]
Consider renaming the targets or disabling the default template by adding
'kotlin.mpp.applyDefaultHierarchyTemplate=false'
to your gradle.properties
创建def文件
cinterop
是Kotlin Native提供的工具,它可以让你像调用Kotlin函数一样调用C函数。
def
文件声明了需要调用的C库的一些信息,比如请求头和存放位置等。
以libcurl
库为例,在src下创建新目录nativeInterop/cinterop
,这是def文件默认的目录,可以在build.gradle.kts
中修改。
在此目录下创建libcurl.def
,填入以下内容,解释见注释:
# headers is the list of header files to generate Kotlin stubs.
# You can add multiple files to this entry, separating each with a \ on a new line.
headers = curl/curl.h
# headerFilter shows what exactly is included.
# In C, all the headers are also included when one file references another one with the #include directive.
# headerFilter is an optional argument and is mostly used when the library is installed as a system library.
# It may be important to optimize the library size and fix potential conflicts between the system and the provided Kotlin/Native compilation environment.
headerFilter = curl/*
# The next lines are about providing linker and compiler options, which can vary depending on different target platforms.
# Parameters without a suffix are also possible (for example, linkerOpts=) and applied to all platforms.
compilerOpts.linux = -I/usr/include -I/usr/include/x86_64-linux-gnu
linkerOpts.osx = -L/opt/local/lib -L/usr/local/opt/curl/lib -lcurl
linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lcurl
linkerOpts.mingw_x64 = -lcurl \
-L/usr/lib64 \
-L/usr/lib/x86_64-linux-gnu \
-L/opt/local/lib \
-L/usr/local/opt/curl/lib \
-L/opt/homebrew/opt/curl/lib \
-L"C:/Program Files (x86)/mingw64/lib"
在这一步中声明了链接器和编译器的目录,对于Windows
而言,你需要mingw64
。
下载curl库
见官方下载页,下载后解压,将相关目录复制到mingw64
的目录下。
这里完全是个人经验,也许这样是错误的
修改build.gradle.kts
修改build.gradle.kts,在构建过程中处理这部分链接,解释见注释。
nativeTarget.apply {
compilations.getByName("main") {
// First, cinterops is added, and then an entry for each def file.
cinterops {
// By default, the name of the file is used.
// You can override this with additional parameters:
val libcurl by creating {
val include = "C:/Program Files (x86)/mingw64/include"
definitionFile.set(project.file("src/nativeInterop/cinterop/libcurl.def"))
packageName("io.sakurasou.c.http")
compilerOpts("-I/$include")
includeDirs.allHeaders(include)
}
}
}
binaries {
executable {
entryPoint = "io.sakurasou.main"
}
}
}
编写应用代码
import kotlinx.cinterop.*
import io.sakurasou.c.http.*
/**
* @author Shiina Kin
* 2024/6/5 19:24
*/
@ExperimentalForeignApi
fun main() {
val curl = curl_easy_init()
if (curl != null) {
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com")
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)
val res = curl_easy_perform(curl)
if (res != CURLE_OK) {
println("curl_easy_perform() failed ${curl_easy_strerror(res)?.toKString()}")
}
curl_easy_cleanup(curl)
}
}
由于在构建中将包声明为io.sakurasou.c.http
因此cinterop
会将klib
生成在该包下
运行,查看结果
运行gradle taskrunDebugExecutableNative
结果如下:
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
参考资料: