我在Android Studio中运行单元测试。我有一个Java类,使用以下代码加载本机库
ClassLoader
但是当我在 static
{
System.loadLibrary("mylibrary");
}
目录中测试这个类时,我得到了
src/test
如何让它找到位于java.lang.UnsatisfiedLinkError: no mylibrary in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1864)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
的原生.so库的路径,以便无错误地进行单元测试?
注意:在src/main/libs
目录中我还有3个子目录:src/main/libs
,armeabi
和mips
。其中每一个都包含正确的.so文件。我使用非实验版来构建NDK库。
我不想使用其他第三方测试库,因为我所有的其他测试库都是纯粹的" java类可以进行单元测试。但如果那不可能,那么我就可以选择替代方案了。
这是我的测试代码,它会抛出错误
x86
答案 0 :(得分:9)
我发现没有黑客的唯一解决方案是通过仪器测试(androidTest目录)使用JUnit。 我的课程现在可以在Android设备或模拟器的帮助下进行测试。
答案 1 :(得分:3)
我不确定这是否能解决你的问题,但到目前为止还没有人提到在创建过程中处理类预加载库的策略模式。
让我们看看例子:
我们想要实现Fibonacci求解器类。假设我们在本机代码中提供了实现并设法生成本机库,我们可以实现以下内容:
public interface Fibonacci {
long calculate(int steps);
}
首先,我们提供原生实现:
public final class FibonacciNative implements Fibonacci {
static {
System.loadLibrary("myfibonacci");
}
public native long calculate(int steps);
}
其次,我们为Fibonacci解算器提供Java实现:
public final class FibonacciJava implements Fibonacci {
@Override
public long calculate(int steps) {
if(steps > 1) {
return calculate(steps-2) + calculate(steps-1);
}
return steps;
}
}
第三,我们在父实例中包装解算器,在实例化过程中选择自己的实现:
public class FibonnaciSolver implements Fibonacci {
private static final Fibonacci STRATEGY;
static {
Fibonacci implementation;
try {
implementation = new FibonnaciNative();
} catch(Throwable e) {
implementation = new FibonnaciJava();
}
STRATEGY = implementation;
}
@Override
public long calculate(int steps) {
return STRATEGY.calculate(steps);
}
}
因此,使用策略查找库的路径的问题。但是,如果在测试期间确实需要包含本机库,则这种情况不能解决问题。如果本机库是第三方库,它既不能解决问题。
基本上,这可以通过模拟java代码的本机代码来解决本机库加载问题。
希望这有所帮助:)
答案 2 :(得分:1)
请确保,包含该库的目录包含在java.library.path
系统属性中。
在测试中,您可以在加载库之前设置它:
System.setProperty("java.library.path", "... path to the library .../libs/x86");
您可以指定硬编码的路径,但这会使项目对其他环境的可移植性降低。所以我建议你以编程方式构建它。
答案 3 :(得分:1)
是一种为本地单元测试配置Gradle-run VM的库路径的方法,我将在下面对其进行描述,但是扰流:在我的经验中,@ ThanosFisherman是对的:使用Android NDK的东西的本地单元测试现在似乎是一个愚蠢的错误。
因此,对于其他任何想要使用gradle将共享(即.so
)库加载到单元测试中的方法,这里有一些冗长的摘要:
目标是为运行单元测试的JVM设置共享库查找路径。
虽然很多人建议将lib路径放入java.library.path
,我发现它不起作用,至少在我的linux机器上没有。 (另外,same results in this CodeRanch thread)
工作虽然设置LD_LIBRARY_PATH
os环境变量(或PATH
是Windows中最接近的同义词)
使用Gradle:
// module-level build.gradle
apply plugin: 'com.android.library' // or application
android {
...
testOptions {
unitTests {
all {
// This is where we have access to the properties of gradle's Test class,
// look it up if you want to customize more test parameters
// next we take our cmake output dir for whatever architecture
// you can also put some 3rd party libs here, or override
// the implicitly linked stuff (libc, libm and others)
def libpath = '' + projectDir + '/build/intermediates/cmake/debug/obj/x86_64/'
+':/home/developer/my-project/some-sdk/lib'
environment 'LD_LIBRARY_PATH', libpath
}
}
}
}
有了它,你可以运行,例如./gradlew :mymodule:testDebugUnitTest
将在您指定的路径中查找本机库。
使用Android Studio JUnit插件 对于Android Studio的JUnit插件,您可以在测试配置的设置中指定VM选项和环境变量,因此只需运行JUnit测试(右键单击测试方法或其他),然后编辑运行配置:
虽然听起来像“完成任务”,但我发现当使用libc.so, libm.so
和我的操作系统/usr/lib
中的其他人给我版本错误时(可能是因为我自己的库是由cmake编译的,带有android ndk反对它自己的平台库的工具包)。并且使用ndk软件包中的平台库导致JVM出现SIGSEGV
错误(由于ndk平台库与主机操作系统环境不兼容)
更新正如@AlexCohn在评论中精辟地指出的那样,必须针对主机环境库进行构建才能使其工作;即使您的计算机很可能是 x86_64 ,但针对NDK环境构建的 x86_64 二进制文件也不会。
显然,我可能会忽略一些事情,我会感激任何反馈,但是现在我放弃了整个想法,转而采用仪器化测试。
答案 4 :(得分:0)
.so文件放在
下的src /主/ jniLibs
不在src / main / libs
下(使用Android Studio 1.2.2测试)
供参考检查页面 - http://ph0b.com/android-studio-gradle-and-ndk-integration/,但有些部分可能已过时。
答案 5 :(得分:0)
尝试使用java -XshowSettings:properties选项运行测试代码,并确保系统库的目标路径和此命令的输出中的库路径值是相同的
答案 6 :(得分:0)
如果测试需要该库,请使用AndroidTest(在src/androidTest/...
下),而不要使用junit测试。这样您就可以像在代码中的其他地方一样加载和使用本机库。
如果测试不需要该库,只需将系统负载包装在try / catch中。这将使JNI类仍然可以在junit测试中工作(在src/test/...
下),这是一个安全的解决方法,因为它不太可能掩盖错误(如果实际上需要本机lib,则其他某些操作肯定会失败) )。从那里,您可以使用诸如Mockito之类的方法来存根仍然命中JNI库的所有方法调用。
例如在科特林:
companion object {
init {
try {
System.loadLibrary("mylibrary")
} catch (e: UnsatisfiedLinkError) {
// log the error or track it in analytics
}
}
}
答案 7 :(得分:0)
这非常非常棘手。设置java.library.path
无效,但尝试了解someone else’s Mac OSX approach后,我终于找到了可行的解决方案。
法律发布:直接复制到此帖子中的所有代码示例都可以在CC0下找到,但应该感谢我的雇主 ⮡ tarent ,LLTO项目位于 Deutsche Telekom 和作者mirabilos
。
使用此功能,您正在测试针对您的 system 库编译的本机代码版本(通常在GNU / Linux上为glibc,在BSD,Mac OSX和Windows上则更为棘手) ),因此无论如何都应该添加一些测试工具,仅将unittests用于更快地测试在主机操作系统上实际上可以测试的东西
我仅在GNU / Linux主机上进行过此测试(实际上,我排除了在所有其他主机OS上进行的本机测试,请参见下文)
IDE集成需要在每个开发人员的机器上进行手动操作(但是这些操作很容易记录,请参阅下面的(很多)内容)
您需要确保宿主系统中还安装了 all 本机代码的构建依赖项。其中包括cmake
(因为遗憾的是我们无法重用NDK cmake)和宿主C编译器。请注意,这些会在构建过程中带来更多差异:您正在测试使用主机C编译器(通常是GCC,而不是Android中的clang)针对主机C库和其他由主机clang生成的库进行构建。 要做,请在编写测试时考虑这一点。我必须将其中一项测试移至已测试,因为无法在glibc下进行测试。
对于文件系统布局,我们假设以下内容:
~/MYPRJ/build.gradle
是顶级构建文件(由IntelliJ / Android Studio生成)~/MYPRJ/app/build.gradle
是构建有问题的Android代码的地方(由IntelliJ / Android Studio生成)~/MYPRJ/app/src/main/native/CMakeLists.txt
是本机代码所在的位置这意味着build.gradle
(对于该应用程序)已经具有类似的功能,这时您开始怀疑您的项目是否可以进行单元测试:
externalNativeBuild {
cmake {
path "src/main/native/CMakeLists.txt"
return void // WTF‽
}
}
乍一看,这样做应该很容易:
$ rm -rf /tmp/build
$ mkdir /tmp/build
$ cd /tmp/build
$ cmake ~/MYPRJ/app/src/main/native/
$ make
(请确保将主cmake
主文件放置在CMakeLists.txt
到目录 的路径中,但 该文件本身!)
当然,这对于所有nōntrivial都是失败的。大多数人会使用Android日志记录。 (它也会失败,因为它找不到<jni.h>
,并且因为GNU libc需要一个额外的_GNU_SOURCE
定义才能访问某些原型,等等……)
所以我写了一个标头而不是<android/log.h>
来包含,它抽象了日志记录…
#ifndef MYPRJ_ALOG_H
#define MYPRJ_ALOG_H
#ifndef MYPRJ_ALOG_TAG
#define MYPRJ_ALOG_TAG "MYPRJ-JNI"
#endif
#if defined(MYPRJ_ALOG_TYPE) && (MYPRJ_ALOG_TYPE == 1)
#include <android/log.h>
#define ecnlog_err(msg, ...) __android_log_print(ANDROID_LOG_ERROR, \
MYPRJ_ALOG_TAG, msg, ##__VA_ARGS__)
#define ecnlog_warn(msg, ...) __android_log_print(ANDROID_LOG_WARN, \
MYPRJ_ALOG_TAG, msg, ##__VA_ARGS__)
#define ecnlog_info(msg, ...) __android_log_print(ANDROID_LOG_INFO, \
MYPRJ_ALOG_TAG, msg, ##__VA_ARGS__)
#elif defined(MYPRJ_ALOG_TYPE) && (MYPRJ_ALOG_TYPE == 2)
#include <stdio.h>
#define ecnlog_err(msg, ...) fprintf(stderr, \
"E: [" MYPRJ_ALOG_TAG "] " msg "\n", ##__VA_ARGS__)
#define ecnlog_warn(msg, ...) fprintf(stderr, \
"W: [" MYPRJ_ALOG_TAG "] " msg "\n", ##__VA_ARGS__)
#define ecnlog_info(msg, ...) fprintf(stderr, \
"I: [" MYPRJ_ALOG_TAG "] " msg "\n", ##__VA_ARGS__)
#else
# error What logging system to use?
#endif
#endif
…并更新了我的CMakeLists.txt
,以指示是为NDK(默认为 )还是本机构建:
cmake_minimum_required(VERSION 3.10)
project(myprj-native)
option(UNDER_NDK "Build under the Android NDK" ON)
add_compile_options(-fvisibility=hidden)
add_compile_options(-Wall -Wextra -Wformat)
add_library(myprj-native SHARED
alog.h
myprj-jni.c
)
if (UNDER_NDK)
add_definitions(-DECNBITS_ALOG_TYPE=1)
find_library(log-lib log)
target_link_libraries(myprj-native ${log-lib})
else (UNDER_NDK)
add_definitions(-DECNBITS_ALOG_TYPE=2)
include(FindJNI)
include_directories(${JNI_INCLUDE_DIRS})
add_definitions(-D_GNU_SOURCE)
endif (UNDER_NDK)
请注意,这还包括<jni.h>
(FindJNI)的修复程序和其他定义。
现在让我们尝试再次构建它:
$ rm -rf /tmp/build
$ mkdir /tmp/build
$ cd /tmp/build
$ cmake -DUNDER_NDK=OFF ~/MYPRJ/app/src/main/native/
$ make
就我而言,这就足够了。如果您仍然不在,请先解决此问题,然后再继续。如果无法解决此问题,请放弃JNI代码的本地构建主机测试,并将各个测试移至已检测。
将以下内容添加到应用build.gradle
:
def dirForNativeNoNDK = project.layout.buildDirectory.get().dir("native-noNDK")
def srcForNativeNoNDK = project.layout.projectDirectory.dir("src/main/native").asFile
task createNativeNoNDK() {
def dstdir = dirForNativeNoNDK.asFile
if (!dstdir.exists()) dstdir.mkdirs()
}
task buildCMakeNativeNoNDK(type: Exec) {
dependsOn createNativeNoNDK
workingDir dirForNativeNoNDK
commandLine "/usr/bin/env", "cmake", "-DUNDER_NDK=OFF", srcForNativeNoNDK.absolutePath
}
task buildGMakeNativeNoNDK(type: Exec) {
dependsOn buildCMakeNativeNoNDK
workingDir dirForNativeNoNDK
commandLine "/usr/bin/env", "make"
}
project.afterEvaluate {
if (org.gradle.internal.os.OperatingSystem.current().isLinux()) {
testDebugUnitTest {
dependsOn buildGMakeNativeNoNDK
systemProperty "java.library.path", dirForNativeNoNDK.asFile.absolutePath + ":" + System.getProperty("java.library.path")
}
testReleaseUnitTest {
dependsOn buildGMakeNativeNoNDK
systemProperty "java.library.path", dirForNativeNoNDK.asFile.absolutePath + ":" + System.getProperty("java.library.path")
}
}
}
这定义了一些新任务来编译共享库的buildhost-native版本,如果主机OS是“ Linux”,则将其挂接。 (此语法也适用于其他unixoid操作系统,例如BSD,Mac OSX,但不适用于Windows。但是无论如何,我们可能只能在Linux上对其进行测试。WSL算作Linux。)它还设置了JVM库路径,因此{{ 1}}将使JVM从其路径中提取库。
在这里您可能会注意到一些松散的结局:
在上一节的最后一段中,我提到../gradlew test
将使用该库。从IDE进行测试尚无法进行;这涉及手动设置。
我提到,如果buildhost操作系统不是“ Linux”,则必须跳过相关的单元测试。我们还没有这样做。不幸的是,JUnit 4缺乏这样的功能,但是将单元测试切换到JUnit 5“ Jupiter”将使我们能够做到这一点。 (不过,我们不切换测试工具;这更具侵入性。)
您可能还没有注意到,但是由于Gradle的默认设置(我们需要更改),本地代码的日志输出不会显示。
那么,让我们开始吧。首先,再次编辑您的应用../gradlew test
文件。将有一个build.gradle
块。我们需要为两个JUnit填充合适的依赖项:
dependencies {
您还将在顶部一行dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
//noinspection GradleDependency
androidTestImplementation 'com.android.support.test:runner:1.0.1'
//noinspection GradleDependency
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
//noinspection GradleDependency
androidTestImplementation 'junit:junit:4.12'
}
(或者也许是apply plugin: 'com.android.application'
)。在该行的正下方,插入这一行:
apply plugin: 'com.android.library'
还要确保在apply plugin: 'de.mannodermaus.android-junit5'
下,android { defaultConfig {
仍为testInstrumentationRunner
(由IntelliJ / Android Studio生成的默认值)。
接下来,编辑顶级 "android.support.test.runner.AndroidJUnitRunner"
文件。您已经有了一个~/MYPRJ/build.gradle
,并且必须在该部分添加一行以使JUnit5插件首先可用:
buildscript { dependencies {
然后,在 //noinspection GradleDependency
classpath 'de.mannodermaus.gradle.plugins:android-junit5:1.5.2.0'
下添加一个新部分:
allprojects {
这可以确保……
~/MYPRJ/app/src/test/resources/logging.properties
,它将为此设置 tasks.withType(Test) {
testLogging {
outputs.upToDateWhen { false }
showStandardStreams = true
exceptionFormat = 'full'
}
systemProperty 'java.util.logging.config.file', file('src/test/resources/logging.properties').getAbsolutePath()
}
(推荐)现在进行测试,类似java.util.logging
。首先,您应该添加一个始终可以运行的“测试”(我使用的只是测试是否可以加载我的JNI类的测试),并确保它首先显示一些信息:
~/MYPRJ/app/src/test/java/org/example/packagename/JNITest.java
然后,注释在其他操作系统上需要跳过的实际JNI测试:
// or Lombok @Log
private static final java.util.logging.Logger LOGGER = java.util.logging.Logger.getLogger(JNITest.class.getName());
@Test
public void testClassBoots() {
LOGGER.info("running on " + System.getProperty("os.name"));
if (!LINUX.isCurrentOs()) {
LOGGER.warning("skipping JNI tests");
}
// for copy/paste into IntelliJ run options
LOGGER.info("VM options: -Djava.library.path=" +
System.getProperty("java.library.path"));
LOGGER.info("testing Java™ part of JNI class…");
[…]
}
为进行比较,使用了仪器测试(在Android设备或模拟器上运行的单元测试)。 @Test
@EnabledOnOs(LINUX)
public void testJNIBoots() {
LOGGER.info("testing JNI part of JNI class…");
final long tid;
try {
tid = JNI.n_gettid();
} catch (Throwable t) {
LOGGER.log(Level.SEVERE, "it failed", t);
Assertions.fail("JNI does not work");
return;
}
LOGGER.info("it also works: " + tid);
assertNotEquals(0, tid, "but is 0");
}
-看起来像这样:
~/MYPRJ/app/src/androidTest/java/org/example/packagename/JNIInstrumentedTest.java
顺便说一下,如果您需要@RunWith(AndroidJUnit4.class)
public class JNIInstrumentedTest {
@Test
public void testJNIBoots() {
Log.i("ECN-Bits-JNITest", "testing JNI part of JNI class…");
final long tid;
try {
tid = JNI.n_gettid();
} catch (Throwable t) {
Log.e("ECN-Bits-JNITest", "it failed", t);
fail("JNI does not work");
return;
}
Log.i("ECN-Bits-JNITest", "it also works: " + tid);
assertNotEquals("but is 0", 0, tid);
}
}
进行仪器测试(JUnit 5已经附带一个),请参见Testable.java。 (请注意,这不属于上述CC0授予,而是属于许可许可。)
现在,您可以同时运行测试,单元测试和(如果已启动Android模拟器或设备已连接)测试的测试:
assertThrows
这样做。请注意来自buildhost本机单元测试的../gradlew test connectedAndroidTest
记录器调用的输出;实际上,将其复制到剪贴板。现在,您将需要它来在IDE中进行测试。
在“项目”视图(左侧树)中,右键单击您的VM options:
类或整个JNITest
目录。单击src/test/java/
(或Run 'JNITest'
),它将失败,并返回Run 'Tests in 'java''
,如原始帖子中所示。
现在单击菜单栏下方测试下拉菜单中的箭头,然后选择UnsatisfiedLinkError
,然后再次进行选择Save JNITest configuration
并选择您的配置。将整个粘贴的内容附加到Edit configurations…
,以使该字段现在看起来像VM options:
(当然,实际值会有所不同),然后单击“确定”。重新运行测试,它将成功。
不幸的是,您必须对每个本机测试类以及整个目录都执行一次此操作,因此将涵盖所有可能的调用方式。您还必须通过单击每个IDE实例来手动执行此操作,并且这些值取决于将代码检出到的路径。我还没有找到一种自动执行这些操作的方法(如果您知道,请告诉我)。
如果您要从代码中引发自定义例外,则很可能希望包含文件/行号/功能信息。使用类似-ea -Djava.library.path=/home/USER/MYPRJ/app/build/native-noNDK:/usr/java/packages/lib:/usr/lib/x86_64-linux-gnu/jni:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/usr/lib/jni:/lib:/usr/lib
的构造函数,并在调用MyprjNativeException(final String file, final int line, final String func, final String msg, … /* custom data */, final Throwable cause)
(可能带有更改的消息)之后,执行以下操作:
super(msg, cause)
然后,从本地代码中抛出这样的异常:
StackTraceElement[] currentStack = getStackTrace();
StackTraceElement[] newStack = new StackTraceElement[currentStack.length + 1];
System.arraycopy(currentStack, 0, newStack, 1, currentStack.length);
newStack[0] = new StackTraceElement("<native>", func, file, line);
setStackTrace(newStack);
用法如下:
#define throw(env,...) vthrow(__FILE__, __func__, env, __LINE__, __VA_ARGS__)
static void vthrow(const char *loc_file, const char *loc_func, JNIEnv *env,
int loc_line, /* custom args */ const char *msg, ...);
实现(假设您缓存类和构造函数方法的引用)如下(针对自定义args进行调整):
if (func() != expected)
throw(env, /* custom args */ "foo");
现在使用static void vthrow(const char *loc_file, const char *loc_func, JNIEnv *env,
int loc_line, const char *fmt, ...)
{
jthrowable e;
va_list ap;
jstring jfile = NULL;
jint jline = loc_line;
jstring jfunc = NULL;
jstring jmsg = NULL;
jthrowable cause = NULL;
const char *msg;
char *msgbuf;
if ((*env)->PushLocalFrame(env, /* adjust for amount you need */ 5)) {
cause = (*env)->ExceptionOccurred(env);
(*env)->ExceptionClear(env);
(*env)->Throw(env, (*env)->NewObject(env, classreference, constructorreference,
jfile, jline, jfunc, jmsg, /* custom */…, cause));
return;
}
if ((cause = (*env)->ExceptionOccurred(env))) {
/* will be treated as cause */
(*env)->ExceptionClear(env);
}
va_start(ap, fmt);
if (vasprintf(&msgbuf, fmt, ap) == -1) {
msgbuf = NULL;
msg = fmt;
} else
msg = msgbuf;
va_end(ap);
jmsg = (*env)->NewStringUTF(env, msg);
free(msgbuf);
if (!jmsg)
goto onStringError;
if (!(jfunc = (*env)->NewStringUTF(env, loc_func)))
goto onStringError;
/* allocate NewStringUTF for any custom things you need */
/* exactly like the one for loc_func above */
/* increase PushLocalFrame argument for each */
jfile = (*env)->NewStringUTF(env, loc_file);
if (!jfile) {
onStringError:
(*env)->ExceptionClear(env);
}
e = (*env)->PopLocalFrame(env, (*env)->NewObject(env, classreference, constructorreference,
jfile, jline, jfunc, jmsg, /* custom */…, cause));
if (e)
(*env)->Throw(env, e);
}
会将完整的绝对路径放入消息和回溯中。这不是很好。有一个编译器选项可以解决此问题,但是NDK r21的叮当声太老了,因此我们需要一种解决方法。
__FILE__
:
CMakeLists.txt
[…]
if (NOT TOPLEV)
message(FATAL_ERROR "setting the top-level directory is mandatory")
endif (NOT TOPLEV)
应用if (UNDER_NDK)
[…]
execute_process(COMMAND ${CMAKE_CXX_COMPILER} --version OUTPUT_VARIABLE cxx_version_full)
string(REGEX REPLACE "^Android [^\n]* clang version ([0-9]+)\\.[0-9].*$" "\\1" cxx_version_major ${cxx_version_full})
if (${cxx_version_major} VERSION_GREATER_EQUAL 10)
add_definitions("-ffile-prefix-map=${TOPLEV}=«MyPrj»")
else (${cxx_version_major} VERSION_GREATER_EQUAL 10)
add_definitions(-DOLD_CLANG_SRCDIR_HACK="${TOPLEV}/")
endif (${cxx_version_major} VERSION_GREATER_EQUAL 10)
else (UNDER_NDK)
[…]
add_definitions("-ffile-prefix-map=${TOPLEV}=«MyPrj»")
endif (UNDER_NDK)
:
(在应用插件行之后)
build.gradle
(在def dirToplev = project.layout.projectDirectory.asFile.absolutePath
内添加一个新块)
android { defaultConfig {
(后来称为cmake)
externalNativeBuild {
cmake {
//noinspection GroovyAssignabilityCheck because Gradle and the IDE have different world views…
arguments "-DTOPLEV=" + dirToplev
}
return void // WTF‽
}
然后,用以下代码段替换行commandLine "/usr/bin/env", "cmake", "-DTOPLEV=" + dirToplev, "-DUNDER_NDK=OFF", srcForNativeNoNDK.absolutePath
:
jfile = (*env)->NewStringUTF(env, loc_file);
这一切都在#ifdef OLD_CLANG_SRCDIR_HACK
if (!strncmp(loc_file, OLD_CLANG_SRCDIR_HACK, sizeof(OLD_CLANG_SRCDIR_HACK) - 1) &&
asprintf(&msgbuf, "«ECN-Bits»/%s", loc_file + sizeof(OLD_CLANG_SRCDIR_HACK) - 1) != -1) {
msg = msgbuf;
} else {
msg = loc_file;
msgbuf = NULL;
}
#else
#define msg loc_file
#endif
jfile = (*env)->NewStringUTF(env, msg);
#ifdef OLD_CLANG_SRCDIR_HACK
free(msgbuf);
#else
#undef msg
#endif
项目中实现。我正在发布一个永久链接,因为它当前位于nōn-default分支上,但是希望被合并(一旦 actual 功能不再是WIP),因此请确保在某些地方检查ECN-Bits
以及时间点(尽管此永久链接可能是一个更好的示例,因为它已经进行了测试,并且没有太多“实际”代码被挡住)。请注意,这些链接不属于上述CC0授予;尽管这些文件都有许可许可(没有显式许可的文件(gradle / cmake文件)与unittest类永久链接相同),但是在本文中重新发布了足够多的许可,因此这应该不是问题为了你;这些仅用于显示一个实际的编译和测试示例。
在此项目中,它不在master
中,而是作为单独的库模块存在。