如上,我们已经生成好了头文件,接下来我们需要使用C/C++编写函数的最终实现代码。
    com_anbai_sec_cmd_CommandExecution.cpp示例:

    1. //
    2. // Created by yz on 2019/12/6.
    3. //
    4. #include <iostream>
    5. #include <stdlib.h>
    6. #include <cstring>
    7. #include <string>
    8. #include "com_anbai_sec_cmd_CommandExecution.h"
    9. using namespace std;
    10. JNIEXPORT jstring
    11. JNICALL Java_com_anbai_sec_cmd_CommandExecution_exec
    12. (JNIEnv *env, jclass jclass, jstring str) {
    13. if (str != NULL) {
    14. jboolean jsCopy;
    15. // 将jstring参数转成char指针
    16. const char *cmd = env->GetStringUTFChars(str, &jsCopy);
    17. // 使用popen函数执行系统命令
    18. FILE *fd = popen(cmd, "r");
    19. if (fd != NULL) {
    20. // 返回结果字符串
    21. string result;
    22. // 定义字符串数组
    23. char buf[128];
    24. // 读取popen函数的执行结果
    25. while (fgets(buf, sizeof(buf), fd) != NULL) {
    26. // 拼接读取到的结果到result
    27. result +=buf;
    28. }
    29. // 关闭popen
    30. pclose(fd);
    31. // 返回命令执行结果给Java
    32. return env->NewStringUTF(result.c_str());
    33. }
    34. }
    35. return NULL;
    36. }

    使用vim com/anbai/sec/cmd/com_anbai_sec_cmd_CommandExecution.cpp或编辑器编写好cpp文件。

    首先切换到我们的C目录:cd com/anbai/sec/cmd/然后使用g++命令编译成动态链接库,前提是您需要提前装好
    编译环境如:gcc/g++

    MacOSX编译:

    1. g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -shared -o libcmd.jnilib com_anbai_sec_cmd_CommandExecution.cpp

    Linux编译:

    1. g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libcmd.so com_anbai_sec_cmd_CommandExecution.cpp

    Windows编译:

    1. Visual Studio/cl命令编译dll。
    2. 使用min-gw/cygwin安装gcc/g++,如: x86_64-w64-mingw32-g++ -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o cmd.dll com_anbai_sec_cmd_CommandExecution.cpp

    如依旧无法编译成,可参考:Java Programming Tutorial Java Native Interface (JNI),这篇文章讲解了如何在不同的操作系统中使用C/C++来编写JNI的HelloWorld。

    如果您采用了C语言编写(C和C++版本基本没差别,也就在使用*env时的参数值一般会不一样)那么请用gcc编译,编译完成我们就可以使用这个动态链接库了。正常情况下我们需要严格按照JNI要求去命名文件名并且把链接库放到Java的动态链接库目录,不然会无法加载。但是这都不是什么大问题我们完全可以通过自定义库名称和路径。
    com.anbai.sec.cmd.CommandExecutionTest示例:

    1. package com.anbai.sec.cmd;
    2. import java.io.File;
    3. import java.lang.reflect.Method;
    4. /**
    5. * Creator: yz
    6. * Date: 2019/12/8
    7. */
    8. public class CommandExecutionTest {
    9. private static final String COMMAND_CLASS_NAME = "com.anbai.sec.cmd.CommandExecution";
    10. /**
    11. * JDK1.5编译的com.anbai.sec.cmd.CommandExecution类字节码,
    12. * 只有一个public static native String exec(String cmd);的方法
    13. */
    14. private static final byte[] COMMAND_CLASS_BYTES = new byte[]{
    15. -54, -2, -70, -66, 0, 0, 0, 49, 0, 15, 10, 0, 3, 0, 12, 7, 0, 13, 7, 0, 14, 1,
    16. 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
    17. 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108,
    18. 101, 1, 0, 4, 101, 120, 101, 99, 1, 0, 38, 40, 76, 106, 97, 118, 97, 47, 108, 97,
    19. 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108,
    20. 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114,
    21. 99, 101, 70, 105, 108, 101, 1, 0, 21, 67, 111, 109, 109, 97, 110, 100, 69, 120,
    22. 101, 99, 117, 116, 105, 111, 110, 46, 106, 97, 118, 97, 12, 0, 4, 0, 5, 1, 0, 34,
    23. 99, 111, 109, 47, 97, 110, 98, 97, 105, 47, 115, 101, 99, 47, 99, 109, 100, 47, 67,
    24. 111, 109, 109, 97, 110, 100, 69, 120, 101, 99, 117, 116, 105, 111, 110, 1, 0, 16,
    25. 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0,
    26. 2, 0, 3, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4, 0, 5, 0, 1, 0, 6, 0, 0, 0, 29, 0, 1, 0, 1,
    27. 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 1,
    28. 9, 0, 8, 0, 9, 0, 0, 0, 1, 0, 10, 0, 0, 0, 2, 0, 11
    29. };
    30. public static void main(String[] args) {
    31. String cmd = "ifconfig";// 定于需要执行的cmd
    32. try {
    33. ClassLoader loader = new ClassLoader(CommandExecutionTest.class.getClassLoader()) {
    34. @Override
    35. protected Class<?> findClass(String name) throws ClassNotFoundException {
    36. try {
    37. return super.findClass(name);
    38. } catch (ClassNotFoundException e) {
    39. return defineClass(COMMAND_CLASS_NAME, COMMAND_CLASS_BYTES, 0, COMMAND_CLASS_BYTES.length);
    40. }
    41. }
    42. };
    43. // 测试时候换成自己编译好的lib路径
    44. File libPath = new File("/Users/yz/IdeaProjects/javaweb-sec/javaweb-sec-source/javase/src/main/java/com/anbai/sec/cmd/libcmd.jnilib");
    45. // load命令执行类
    46. Class commandClass = loader.loadClass("com.anbai.sec.cmd.CommandExecution");
    47. // 可以用System.load也加载lib也可以用反射ClassLoader加载,如果loadLibrary0
    48. // 也被拦截了可以换java.lang.ClassLoader$NativeLibrary类的load方法。
    49. // System.load("/Users/yz/IdeaProjects/javaweb-sec/javaweb-sec-source/javase/src/main/java/com/anbai/sec/cmd/libcmd.jnilib/libcmd.jnilib");
    50. Method loadLibrary0Method = ClassLoader.class.getDeclaredMethod("loadLibrary0", Class.class, File.class);
    51. loadLibrary0Method.setAccessible(true);
    52. loadLibrary0Method.invoke(loader, commandClass, libPath);
    53. String content = (String) commandClass.getMethod("exec", String.class).invoke(null, cmd);
    54. System.out.println(content);
    55. } catch (Exception e) {
    56. e.printStackTrace();
    57. }
    58. }
    59. }

    CommandExecutionTest执行命令演示:
    5. JNI-编写C/C  本地命令执行实现 - 图1
    示例代码中的CommandExecutionTest.java其实和load_library.jsp逻辑差不多,Demo实现了自定义ClassLoader重写了findClass方法来加载com.anbai.sec.cmd.CommandExecution类的字节码并实现调用,然后再通过JNI加载动态链接库并调用了链接库中的命令执行函数。