优秀的编程知识分享平台

网站首页 > 技术文章 正文

看C++大叔如何拥 java 妹子入怀~(c++ jvm)

nanyue 2024-09-02 19:03:43 技术文章 24 ℃

在C++程序里,嵌入一个JVM(java 虚拟机),就可以把 java 代码编译后的 .class 文件当成 “脚本”语言来执行了。当然,前提是你的电脑上已经有安装 JVM 。

目标

假设有Java组的同学写了一段代码:

public class Greeting {	
	public void SayHello() {
		System.out.print("--你好 C++,我是Java。--\n");
	}
}

保存在 Greeting.java 的文件里,然后使用 javac 编译:javac Greeting.java, 得到 Greeting.class 。现在,“球”到了C++组的同学的脚下了,我们需要在一个C++程序里,执行这个 .class 文件,并在 C++ 程序的控制台(Windows下)或终端(Linux)下看到这一行:

--你好 C++,我是Java。--。

方案

下面我使用 Code::Blocks 为IDe,以 linux 环境为例,但也会说到Windows下的不同处,讲讲如何做。


步骤1 :准备 JVM 和 JDK

  • JVM 是 “Java 虚拟机”,它负责执行 java 程序;
  • JDK 是 Java 的开发包,它含有C或C++语言“二次开发 java”所需要的文件

JDK 提供的东西多数和Java语言自身无关,而是 C 语言的头文件和不同的操作系统的库,本质也是C语言的库,因为操作系统的接口通常使用C语言描述。

Java的“卖点”之一,就是跨平台,但前提是在不同的平台上,先安装(当然也是不同)的JVM及JDK。大家可以自行找资料(网上多的是)。


步骤2 :配置C++项目

无非就是要解决以下问题:

  1. 让C++编译器,能找到 JDK 提供的头文件(.h)在哪?库文件 (.so.lib)在哪?
  2. 让C++编译器(严格讲是链接器),把 JDK 里的 库文件链接到 C++ 程序里。

下面的内容,均使用 $(jdk) 代表你的电脑上安装的 JDK 的文件夹。

通常:
Linux: 此文件夹大概是:/user/lib/jvm/java-17-openjdk-amd64
Windows: 则大概是:C:\Program Files\Java\jdk1.8.0_241

需要加入 C++ 编译头文件搜索路径的有两项,先看 linux 下:

  • $(jdk)/include
  • $(jdk)/include/linux

在Windows 系统下,保留第一项,然后第二项需改为 :

  • $(jdk)/include/win32

不同的IDE有不同的配置头文件搜索路径的方法,下面是 Code::Blocks 在Linux下的例子。

  • 首先,配置项目路径变量:$(jdk)

假设我们的Code::Blocks项目名为 CPPCallJava,进入项目构建配置对话框后:

项目路径变量仅在当前项目里生效。如果你想把它配置为 Code::Blocks的全局路径(可在所有项目中生效)也可以。(《白话C++》中有很多配置全局路径变量的例子,线上视频课程:配置Code::Blocks全局路径变量)

  • 然后,配置头文件搜索路径:

如前所述,如果是 Windows 环境,你需要把第二项路径中 “linux” 改为 “win32”。

  • 再配置库搜索路径:

将上图中“Compiler”页,切换为其右边的“Linker”页,然后也添加两项:

  • 最后配置需要链接的Java库:

注意到了吗?这库的名字就叫 “jvm” ,所以把它嵌入(链接)到一个C++的程序,是不是这个C++程序就拥有了自己的一个 java 虚拟机,于是就是可以执行指定的 java 代码(当然,得是编译好的结果,即 .class 文件)。


步骤3 :C++代码

Java代码我们在一开头就给了,并且编译好了,学过 Java 编程的同学可能会有点嘀咕:Java 程序的运行入口,不应该是一个静态类里的名为main的静态函数吗?上面给的java 代码,没有入口呀?

此情此景,C++忍不住要猥琐地笑了:“我都把她揽入怀里了,还要你们教我哪里是入口函数?”

下面就是完整C++代码。该代码要求所要执行的 java 代码,需放在本C++程序运行目录下的一个名为 java 的子文件夹中。

#include <cassert>
#include <iostream>

#include <jni.h>

struct JVMInfo
{
    JavaVM* jvm;
    JNIEnv* env;

    JavaVMInitArgs vm_args;

    #define VM_OPT_COUNT 3
    JavaVMOption options[VM_OPT_COUNT];

    JVMInfo()
        : jvm(nullptr), env(nullptr)
    {
        // 第一个条件:不需要java编译器(因为我们已经编译好了测试用的java代码)
        options[0].optionString = const_cast<char *>("-Djava.compiler=NONE");

        // 第二个条件://classpath有多个时,用";"分隔,UNIX下以":"分割。
        //这里,至少要包含前面java代码编译出来的Greeting.class文件所在路径
        //根据我设置的相对路径,可以推出我的callJava 的C++工程和demo的Java工程所在位置的相对关系
        options[1].optionString = const_cast<char *>("-Djava.class.path=./java");

        // 第三个条件:用于跟踪运行时的信息
        // "-verbose:jni" 换成这个,则jvm启动时,不会在屏幕上输出一堆信息
        options[2].optionString = const_cast<char *>("-verbose:none");

        // JNI版本号
        vm_args.version = JNI_VERSION_10;
        vm_args.nOptions = VM_OPT_COUNT;
        vm_args.options = options;

        vm_args.ignoreUnrecognized = JNI_TRUE;
    }

    // 创建JVM
    bool Create()
    {
        assert(!jvm && !env);
        return 0 == JNI_CreateJavaVM(&jvm, (void **)(&env), &vm_args);
    }

    // 销毁JVM
    void Destory()
    {
        if (jvm)
        {
            jvm->DestroyJavaVM();

            jvm = nullptr;
            env = nullptr;
        }
    }

    void Demo()
    {
        assert(jvm && env);

        auto test = [](bool condition, char const* error)
        {
            if (!condition)
            {
                std::cerr << error << std::endl;
            }

            return condition;
        };

        // 第1步: 找指定 class
        jclass greetingClass = env->FindClass("Greeting");

        if (!test(greetingClass, "Can't found java class 'Greeting'."))
        {
            return;

        }

        // 第2步:找 Greeting 类的构造函数
        jmethodID greetingCtor = env->GetMethodID(greetingClass, "<init>", "()V");

        if (!test(greetingCtor, "Can't found constructor for 'Greeting'."))
        {
            return;
        }

        // 第3步:通过构造函数,创建出一个 Greeting对象:
        jobject greetingObject = env->NewObject(greetingClass, greetingCtor);

        if (!test(greetingObject, "Can't create a object of 'Greeting'."))
        {
            return;
        }

        // 第4步:找到 Greeting 的 SayHello 方法:
        jmethodID sayHello = env->GetMethodID(greetingClass, "SayHello", "()V");

        if (!test(sayHello, "Can't found method 'SayHello()'."))
        {
            return;
        }

        // 最后:调用 对象 greetingObject 的 sayHello 方法:
        env->CallObjectMethod(greetingObject, sayHello);
    }
};

int main()
{
    JVMInfo ji;

    if (!ji.Create())
    {
        std::cerr << "Create JVM fail." << std::endl;
        return -1;
    }

    ji.Demo();

    ji.Destory();
}

运行结果:


更复杂的java代码?

把java程序自身依赖的外部库配置好,并且也走传统习惯走 java 的 main() 入口,大概都是能执行的。

下面给一个相对复杂的——其实就是带有线程的 java 代码:

public class Greeting {
	private static class Task implements Runnable {
		@Override
		public void run() {
			for (int i=0; i<10; i++) {
				if (i % 2 != 0) {
					System.out.println(Thread.currentThread().getName() + "在打印 : " + i);
					
					try {
						Thread.sleep(1000);
					}
					catch(InterruptedException e) {
						System.err.printf("线程 %s 睡眠时异常 %s。\n", Thread.currentThread().getName(), e.getMessage());
					}
				}
			}
		}
	}

	public void SayHello() {
		System.out.print("--你好 C++,我是Java。--\n");
		
		Thread trd = new Thread(new Task());
		trd.setName("Java线程");
		trd.start();
		
		try {
			trd.join();
		} 
		catch (InterruptedException e) {
			System.err.printf("等待 %s 结束发生异常 %s。\n", trd.getName(), e.getMessage());
		}
	}
}

把它替换掉上面的 Greeking.java,并记得用 javac 重新编译后,就可以运行了,结果如下:

意义?

C++程序内嵌(相对)简单、灵活且强大的另一门语言,能够赋给C++程序非常棒的功能,比如:

  • 不修改C++程序(这对上线的C++程序来说是很烦的事),直接改变某些功能(这些功能由内嵌的语言实现);
  • 公司里,C++程序员没有 Java 或其它程序员多时……(巧妙地转移工作量);
  • 第三方语言有更多丰富、成熟的库时……

事实上,C++程序内嵌其它语言这种做法很常见。我最早试的是 lua,但尴尬地发现,自己不太会 lua……后台又兴冲冲地内嵌了 Python ,结果,最大失望是:一个C++程序只内嵌一个 Python 虚拟机,次大需求是 Python GIL 带来的程序的那个大卡小卡…… 最后终于发现,原来 Java 才是最美的小妹,哦,最美的计算机编程语言。

更多课程请到首页-第2学堂

Tags:

最近发表
标签列表