引言

主要是官方教程 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>


参考资料:

  1. Create an app using C Interop and libcurl – tutorial
  2. Get started with Kotlin/Native in IntelliJ IDEA
  3. stack overflow

ねぇ,あなたは何色になりたい