JNI的学习(C++调用Java函数)

jni编程 C++调用java详解 阅读详情

源码部分参考了:http://public0821.iteye.com/blog/423941

当然,也改动了一些关键的地方,才让程序顺利调通。

不过调试的过程中也引出了好多问题,花了不少时间终于解决了,特此记录下整个解决问题的过程分享果实~^_^。


1.Java SDK安装路径:

操作系统:Windows 7 x64

C:\Program Files\Java\jdk1.7.0_15

C:\Program Files (x86)\Java\jdk1.6.0_17


2. 用eclipse建立一个Java工程叫JniTest1

假设路径在:D:\sonikk\project\java\workspace\JniTest1

文目录的放置关系:

JniTest1

__ src

__ __ test

__ __ __Demo.java


下面是Demo.java:

package test;
/**
* 该类是为了演示JNI如何访问各种对象属性等
*/
public class Demo 
{
	//用于演示如何访问静态的基本类型属性
	public static int COUNT = 8;
	//演示对象型属性
	private String msg;
	private int[] counts;
	
	public Demo() 
	{
		this("缺省构造函数");
	}
	/**
	 * 演示如何访问构造器
	 */
	public Demo(String msg) 
	{
		this.msg = msg;
		this.counts = null;
	}
	public String getMessage()
	{
		return msg;
	}
	/**
	 * 该方法演示如何访问一个静态方法
	 */
	public static String getHelloWorld()
	{
		return "Hello world!";
	}

	/**
	 * 该方法演示参数的传入传出及中文字符的处理
	 */
	public String append(String str, int i)
	{
		return str + i;
	}
	/**
	 * 演示数组对象的访问
	 */
	public int[] getCounts()
	{
	 return counts;
	}
	/**
	 * 演示如何构造一个数组对象
	*/
	public void setCounts(int[] counts)
	{
	 this.counts = counts;
	}
	/**
	 * 演示异常的捕捉
	*/
	public void throwExcp()throws IllegalAccessException
	{
		throw new IllegalAccessException("exception occur.");
	}
}

2.查看Signature,


将Demo.java在同一个目录下编译成.class文件:

这样最终的.class文件就会输出在如下路径:

D:\sonikk\project\java\workspace\JniTest1\src\test1\Demo.class

运行cmd:

# D:

# cd D:\sonikk\project\java\workspace\JniTest1\src\test

# javac Demo.java

# javap -s -p Demo


输出如下:




查看导出jvm.dll的函数:

找到VS2010的安装目录进入VC文件夹:

C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC

下面有一个文件叫vcvarsall.bat 

打开cmd,将此文件拖入cmd当中,并单击回车,输出如下:

C:\Users\Administrator>"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"
Setting environment for using Microsoft Visual Studio 2010 x86 tools.

接下来就可以进行dump 任意的DLL了,

# dumpbin /exports "C:\Program Files (x86)\Java\jdk1.6.0_17\jre\bin\client\jvm.dll"

# dumpbin /exports "C:\Program Files\Java\jdk1.7.0_15\jre\bin\server\jvm.dll"


这样可以知道jvm.dll当中有哪些函数~


当然,也可以不用这么麻烦,从VS2010的菜单也可以直接进行dump,操作方式:

Tools -> Visual studio 2010 Command Prompt

# cd d:

# dumpbin /exports "C:\Program Files (x86)\Java\jdk1.6.0_17\jre\bin\client\jvm.dll" /out:"C:\jvm_dump.txt"

结果就被重定向到了C:\jvm_dump.txt 中了,非常方便。






创建一个c++的console工程:

Configuration Properties -> C/C++ -> General -> Additional Include Directories:

C:\Program Files\Java\jdk1.7.0_15\include

C:\Program Files\Java\jdk1.7.0_15\include\win32

Configuration Properties -> Linker -> General -> Additional Library Directories:

C:\Program Files\Java\jdk1.7.0_15\lib


注意:使用1.7的64x jdk,调用

HINSTANCE hInstance  = ::LoadLibrary(TEXT("C:\\Program Files\\Java\\jdk1.7.0_15\\jre\\bin\\client\\jvm.dll"));

会返回NULL,于是改用了32位的jdk 1.6版本,可以正常获取!

HINSTANCE hInstance  = ::LoadLibrary(TEXT("C:\\Program Files (x86)\\Java\\jdk1.6.0_17\\jre\\bin\\client\\jvm.dll"));


出现错误:

error LNK2001: unresolved external symbol __imp__JNI_CreateJavaVM@12 


正确的工程设置:

添加环境变量:

%JAVA_HOME% = C:\Program Files\Java\jdk1.7.0_15

Path: %JAVA_HOME%\jre\bin\server


Configuration Properties -> Project Defautls -> Character Set:

Not Set

Configuration Properties -> Debugging -> Working Directory:

$(OutDir)

Configuration Properties -> C/C++ -> General -> Additional Include Directories:

C:\Program Files (x86)\Java\jdk1.6.0_17\include

C:\Program Files (x86)\Java\jdk1.6.0_17\include\win32

Configuration Properties -> Linker -> General -> Additional Library Directories:

C:\Program Files (x86)\Java\jdk1.6.0_17\lib


下面上code实现c++调用java函数,实现字符串拼接( "sonikk小哥是"+250 )

注意:

classpath设置为: "D:\\sonikk\\project\\java\\workspace\\JniTest1\\src"

env->FindClass("test/Demo")才能刚好找到:D:\sonikk\project\java\workspace\JniTest1\src\test\Demo.class


cpp文件:

// JniTest1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include "windows.h"
#include <string>
#include <iostream>
using namespace std;

#include "jni.h"

//#pragma comment(lib, "jvm.lib")

jstring NewJString(JNIEnv *env, LPCTSTR str);  
string  JStringToCString (JNIEnv *env, jstring str);  

// c++调用java
int _tmain(int argc, _TCHAR* argv[])
{
	//定义一个函数指针,下面用来指向JVM中的JNI_CreateJavaVM函数  
	typedef jint (WINAPI *PFunCreateJavaVM)(JavaVM **, void **, void *);  

	int res;
	JavaVMInitArgs vm_args;
	JavaVMOption options[3];
	JavaVM *jvm;
	JNIEnv *env;

	/*设置初始化参数*/  
	//disable JIT,这是JNI文档中的解释,具体意义不是很清楚 ,能取哪些值也不清楚。  
	//从JNI文档里给的示例代码中搬过来的  
	options[0].optionString = "-Djava.compiler=NONE";  
	//设置classpath,如果程序用到了第三方的JAR包,也可以在这里面包含进来  
	options[1].optionString = "-Djava.class.path=.;D:\\sonikk\\project\\java\\workspace\\JniTest1\\src";  
	//设置显示消息的类型,取值有gc、class和jni,如果一次取多个的话值之间用逗号格开,如-verbose:gc,class  
	//该参数可以用来观察C++调用JAVA的过程,设置该参数后,程序会在标准输出设备上打印调用的相关信息  
	options[2].optionString = "-verbose:NONE";  

	//设置版本号,版本号有JNI_VERSION_1_1,JNI_VERSION_1_2和JNI_VERSION_1_4  
	//选择一个根你安装的JRE版本最近的版本号即可,不过你的JRE版本一定要等于或者高于指定的版本号  
	vm_args.version = JNI_VERSION_1_4;  
	vm_args.nOptions = 3;  
	vm_args.options = options;  
	//该参数指定是否忽略非标准的参数,如果填JNI_FLASE,当遇到非标准参数时,JNI_CreateJavaVM会返回JNI_ERR  
	vm_args.ignoreUnrecognized = JNI_TRUE;

	//加载JVM.DLL动态库  
	HINSTANCE hInstance  = ::LoadLibrary(TEXT("C:\\Program Files (x86)\\Java\\jdk1.6.0_17\\jre\\bin\\client\\jvm.dll"));
	
	if (hInstance == NULL)
	{
		return false;
	}
	//取得里面的JNI_CreateJavaVM函数指针
	PFunCreateJavaVM funCreateJavaVM = (PFunCreateJavaVM)::GetProcAddress(hInstance, "JNI_CreateJavaVM");
	//调用JNI_CreateJavaVM创建虚拟机
	res = (*funCreateJavaVM)(&jvm, (void**)&env, &vm_args);

	// 添加jvm.lib引用
	//res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

	if (res < 0)  
	{  
		return -1;  
	}  
	//查找test.Demo类,返回JAVA类的CLASS对象  
	jclass cls = env->FindClass("test/Demo");  
	//根据类的CLASS对象获取该类的实例  
	jobject obj = env->AllocObject(cls);  

	//获取类中的方法,最后一个参数是方法的签名,通过javap -s -p 文件名可以获得  
	jmethodID mid = env->GetMethodID(cls, "append","(Ljava/lang/String;I)Ljava/lang/String;");

	//构造参数并调用对象的方法  
	const char szTest[] = "sonikk小哥是";  
	jstring arg = NewJString(env, szTest);  
	jstring msg = (jstring) env->CallObjectMethod(obj, mid, arg, 250);  
	cout<<JStringToCString(env, msg);  

	//销毁虚拟机并释放动态库  
	jvm->DestroyJavaVM();
	::FreeLibrary(hInstance);

	printf("\nfinished...\n");
	getchar();

	return 0;
}

jstring NewJString(JNIEnv *env, LPCTSTR str)  
{  
	if(!env || !str)  
	{  
		return 0;  
	}
	int slen = strlen(str);
	jchar* buffer = new jchar[slen];  
	int len = MultiByteToWideChar(CP_ACP,0,str,strlen(str), (LPWSTR)buffer,slen);
	if(len>0 && len < slen)  
	{  
		buffer[len]=0;  
	}  
	jstring js = env->NewString(buffer,len);  
	delete [] buffer;  
	return js;  
}

string  JStringToCString (JNIEnv *env, jstring str)
{
	if(str==NULL)
	{
		return "";
	}
	//在VC中wchar_t是用来存储宽字节字符(UNICODE)的数据类型
	int len = env->GetStringLength(str);
	wchar_t *w_buffer = new wchar_t[len+1];
	char *c_buffer = new char[2*len+1];
	ZeroMemory(w_buffer,(len+1)*sizeof(wchar_t));
	//使用GetStringChars而不是GetStringUTFChars
	const jchar * jcharString = env->GetStringChars(str, 0);
	wcscpy(w_buffer, (wchar_t*)jcharString);
	env->ReleaseStringChars(str,jcharString);
	ZeroMemory(c_buffer,(2*len+1)*sizeof(char));
	//调用字符编码转换函数(Win32 API)将UNICODE转为ASCII编码格式字符串
	len = WideCharToMultiByte(CP_ACP,0,w_buffer,len,c_buffer,2*len,NULL,NULL);
	string cstr = c_buffer;
	delete[] w_buffer;
	delete[] c_buffer;

	return cstr;
}


最后编译运行截图:



发现字符串被连起来了~OK!






AndroidStudio jni C++与Java互相调用 jni C++与Java互相调用 阅读详情

相关推荐

Android NDK JNI与Java的相互调用

/获取一个自定义java对象//获取一个自定义长度的java对象数组extern "C"//其实这个内容前面基本已经说过,要想通过jni生成一个java类对象,其实最主要的一步就是获取那个java类的对象//1、获取到java类Student的class//2、第二部获取Student的构造方法,第二个参数固定传,第三个方法是签名,无参构造方法的签名就是 ()V//3、获取Student的对象,后面给对象赋值就完成了java类对象的创建。

s_nshine的博客 1013

c++调用java方法详解

我们可以使用 class参数来传递 Java方法的引用,如果我们想调用一个方法,可以在传入 class参数的时候将此方法的引用传递给 Java虚拟机。但是,它们之间有一个明显的区别:`set_java_name_to_className`中的值是虚拟机中类实例类型的值,而`set_java_name_to_className`中的值是类对象本身所对应的值。当我们使用 Java程序调用C++程序时,我们可以使用JAVA_HOME类来访问 Java虚拟机中的类,并使用其提供的方法来调用 Java方法。

花生日记 3380

使用JNI技术实现Java和C/C++互调(Java调C/C++)

Java作为一个技术生态平台在业界得到了广泛的应用,许许多多的大型项目都是搭载在其之上。Java语言本身功能也十分强大,能实现大量的业务逻辑算法。那么,我们为什么要让Java和C/C++互调呢,直接用Java写不就完了吗?这是初学者一个很常见的疑问,下面我们来看看为什么需要这样做。     用Java调用底层的代码(不一定指是C/C++写的,虽然大部分是)主要原因有三个:     1.运行效率

wonghifong0223的博客 3341

C++基础二 JNI基础操作

C的预编译 c语言执行的流程 预编译:完成代码文本的替代工作 编译:形成目标代码(.obj) 链接:将目标代码与c函数库连接合并,形成最终的可执行文件 执行:运行 void main() { #include "my.txt"; getchar(); } my.txt的内容 printf("%s\n", "I am a great man"); 1.JNI定义...

MatrixMind的博客 2205

Java JNI 调用C#方法

 在编写Java应用程序当中,可以利用C或者CPP来编写DLL函数,然后再Java当中进行调用,从而实现对本地API的调用,这两天因为业务需要,要利用Java调用C#函数。于是研究了一下,发现可以通过Manage C++来实现对C#函数的调用。具体方法如下:首先还是编写Java,利用javah生成CPP头文件。然后跟普通JNI编写过程一样。然后在JNI的借口函数出调用Manage C++方法。

sweetwxh的专栏 9326

Java通过JNI调用C/C++函数

在某些情况下,Java语言需要通过调用C/C++函数来实现某些功能,因为Java有时候对这些功能显的无能为力,如想使用X86_64 的 SIMD 指令提升一下业务方法中关键代码的性能,又或者想要获取某个体系架构或者操作系统特有功能的支持。为了能在Java 代码中调用 C/C++函数,JVM提供了Java Native Interface(JNI)机制。 在Java中,使用native关键字标注的、没有方法体的方法就是native方法。当在 Java 代码中调用这些 native 方法时,Java 虚拟机将通

weixin_54305824的博客 1183

JNI的学习(Java调用C++函数,通过DLL)

1. 打开Eclipse创建一个Java工程叫JvmTest1,包名叫:test,类名叫:DemoNative 工程被创建在:C:\dev\proj\android\workspace\JvmTest1 2.DemoNative.java当中的内容如下: package test; public class DemoNative { public native void s

sonikk的专栏 1684

JNI方法调用C++类库<一>

布局: C++ ------------>lib,文件dms_lib.cpp C++ ------------>JNI C++,文件dms_jni.cpp JAVA ------------>JNI JAVA,文件dms.java 通过以下命令,可以生成java的jni头文件:javac dms.java && javah dms /**  * file: dms.java  *

Yolanda的专栏 4835

Android JNI学习03-AndroidStudio3.6 动手实现一个java调用c++的函数

C或者C++的函数想要被java调用,首先我们得写一个native方法,到时候JVM就可以通过native方法的名字去动态库里找对应的C或者C++函数。 先编写一个native方法myJNIFunction(),如下 AndroidStudio还挺智能,知道我们还没有编写对应的C++函数,所以报了个错,提示我们要赶快将C++函数写好。 编写对应的C++函数 .这时我们创建了一个空的...

u010632547的博客 630

开源onlyoffice编译完整过坑版

onlyoffice 编译过坑完整版;

sHImart 4551

SCHUNK SVH五指灵巧手 | 熵洛智能

雄克仿真五指机械手产品系列可以如人手操作那般完美地完成抓取操作。电子装置完全集成于腕关节中,使五指机械手几乎可以完成所有的人体手部动作。凭借带有9个驱动器的运动指骨,机械手可高精度地 执行多种抓取操作。富有弹性的抓取表面确保了对物体的可靠抓取。除了开启抓取和操作任务的全新领域之外,雄克还为在五指机械手的手势基础上进行人与机器人的交流开创了无限可能。编辑切换为居中添加图片注释,不超过 140 字(可选)产品特点:机械手有左手或右手两种版本适合移动应用耗能低,使用 24 V DC 在腕关节处完整集成了控制、调节

ShannonTech的博客 1331

C++学习之打车软件JNI编程

C++学习之打车软件JNI编程

qq_27302885的博客 697

IFIX数据库Datebase当前值问号

IFIX数据库Datebase当前值问号,已解决,亲测有效 在OPC PowerTool有数据的情况下,IFIX数据库Datebase当前值显示???? 问题分析及解决方法 我的IFIX5.8版本,其它版本可参考 打开IFIX工作台:应用程序–任务控制–I/O驱动–OPC Client;查看OPC驱动是否被禁用,点击启动,变成激活状态即可;若点击启动无法激活OPC驱动,需要重新安装OPC驱动,方法如下: 下载I/O Driver文件 下载链接:https://download.youkuaiyun.com/down

Ron_Tso的博客 2759

利用JNI调用c++函数

在公司项目中遇到这么一个难题 客户有一个基板,这个基板通过数据线可以连接到电脑上,装上驱动就可以调用基板提供的函数. 但是程序是java开发的,所以就需要通过java调用基板函数,但是基板函数允许C++调用不支持java,所以就用到java调用C++ 基板函数按照返回值大致分为两种 1.调用后直接返回数字,一般属于命令型函数,比如打开电源,返回0表示打开成功 2.调用后返回数组,比如需要...

陈小郎的总结博客 4213

MTK平台Wi-Fi学习--wifi channel 通过国家码进行功率限制和wifi eFEM 基本配置和wifi Tx SEM问题

本文介绍了WiFi功率限制配置和eFEM硬件设置方法。在功率限制方面,通过国家码可针对不同信道设置功率上限,需在rlm_txpwr_init.h文件中配置信道和功率值(单位0.5dBm),WiFi6的HE_TB模式还可分RU设置。eFEM硬件配置需参考平台设计,注意VDET接口处理,首次使用建议提交HW/SW案例审核。软件配置需获取专用FEM包,包含NVRAM、GPIO等文件,按指导合并后版本才会切换为eFEM版本。

专业开发者的博客,一个从事Android WiFi和蓝牙的工程师 484

【用ArcGIS制作一张好看的中国月度气温图

要最后选中那个事先调整好的那个数据框,这样其他两个数据框才能和其大小一致。

J_Giser的博客 1017

类型 jni_为什么感觉写JNI 这么难?

上篇文章中我们通过 Android Studio 玩了下 JNI 的demo,但是如果想更深一点学习 JNI 的话,还得需要懂得一些 Android Studio 中的一些知识点。知识点一、JNI 函数的结构这里要特别提醒一下,在 java 类中声明的静态 native 方法和非静态 native 方法区别即,二者的参数二的类型是不同的:非静态声明的类型是 jobject 类型静态声明的类型是 j...

weixin_39673471的博客 79

R语言LASSO回归作图为什么X轴使用-log(λ)而不是log(λ),如何转化为log(λ)?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1015

cocos2d-x 通过JNI实现c/c++和Android的java层函数互调

文章摘要: 本文主要实现两个功能: (1)通过Android sdk的API得到应用程序的包名(PackageName),然后传递给c++层函数。 (2)通过c++函数调用Android的java层函数,显示一个对话框,点击按钮退出程序。 1. 首先来简单学习一下JNI的相关知识,我这篇文章中简单实现了怎么在Android Java层调用c++函数。要想使用JNI,必须得…

青峰专栏 2万+

2026年职业院校技能大赛中职移动应用与开发—数字生活APP原型设计参考答案(含素材)

本模块以产品原型为目标,考查参赛选手熟练收集、分析和归纳客户需求,清晰梳理业务流程,熟练使用UI设计软件进行产品UI/UE设计,掌握正确的UI配色方案,设计出符合业务逻辑的人体工学移动App 原型优秀作品。

我是网络安全兼技能大赛工程师,专注网络安全技术学习与技能大赛实战!持续分享最新的技术文章,帮你少走弯路,一起在技术赛道上进步~ 568

uniapp [安卓苹果App端] - 详细实现PDF预览功能在线预览PDF文档,Uniapp App端实现pdf文件预览在线预览pdf二进制文件流、本地pdf文档、PDF文件地址url路径(解决白屏

uni-app,安卓Android,苹果ios,安卓app端,苹果App端,uniapp苹果安卓app,uniapp在线预览pdf功能,在安卓苹果app平台实现PDF的预览查看,uniappApp端实现pdf文件预览,UNIAPP在线预览PDF文档,Uniappapp在线预览pdf文件流,从后端接口里获取pdf文件如何转成链接预览,预览pdf的插件,uni-app的pdf预览功能,uniapp使用pdf.js插件预览pdf文件,预览pdf白屏没有报错,打开后没反应,打开是空白,加载PDF时发生错误,如

🏆 前端领域优质创作者 1万+

cocos2dx 与android函数的互相调用及使用jni

cocos2dx 与android函数的相互调用及使用jni 哈喽大家好 ,之前一阵子一直在忙项目,没怎么发过博文,挺想看看cocos2dx与安卓是如何交互的,所以就学习了一下(因为我是小菜嘛 想多学点东西 嘻嘻!!!)做了这么一个小test,虽然不多代码但小菜弄了许久,发上来给大家分享下 如果有什么错误或建议欢迎大家为我指出来啊 小菜感激不尽。。。 其实一开始是在想如何在cocos

兴趣是最好的老师 1041

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Android中JNI的使用方法

首先看一下Android平台的框架图:(网上盗用)        可以看到Android上层的Application和ApplicationFramework都是使用Java编写, 底层包括系统和使用众多的LIiraries都是C/C++编写的。   所以上层Java要调用底层的C/C++函数库必须通过Java的JNI来实现。 下面将学习Android是如何通过Jni来实现Jav

lq18111292117的博客 314

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Android JNI学习06-AndroidStudio3.6 JNI函数更新java层的数组

链接:https://pan.baidu.com/s/1zB9r5WwuTMIPNMlYl5HlvA 提取码:5ndl 本篇以下篇的代码为例,实现在JNI函数里更新java层的数组,没有看过下篇的朋友,可以先去看下。 Android JNI学习04-AndroidStudio3.6 java 向 C++函数传递数组 在MainActivity中调用sendIntArrayToJNI sendIntArrayToJNI对应的JNI函数内容如下 extern "C" JNIEXPORT j.

u010632547的博客 483

JNI学习积累之一 ---- 常用函数大全

jni 常用 API函数 简介

519

Android JNI复杂用法,回调,C++中调用Java方法

/创建对象的本地变量//获取类对象I)V");上面(1)和(2)是没什么研究价值的,NewGlobalRef和FindClass都是固定的写法。(3)和(4)的不用方法的调用区别就比较大了,使用不同的api函数还可以修改Java的变量属性。静态方法和动态方法调用的api函数也不一样,有返回值的方法和没有返回值的方法调用的api函数也是不一样的。并且Java方法或者变量即使是private修饰的也不影响cpp调用过去,因为反射是不受修饰符影响的。

wenzhi的博客 3169
上一篇: 不同的C语言闰年判断
下一篇: JNI的学习(Java调用C++函数,通过DLL)
sonikk
博客等级 码龄15年 68粉丝 113原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值