JNIUtility

#ifndef JNIUtility_H
#define JNIUtility_H

//

#ifdef __cplusplus
extern "C" {
#endif

//

#include <jni.h>

//

/* Throw common exceptions */
/* Note: these have been macroized in jni_cvm_util.h */

void JNU_ThrowNullPointerException(JNIEnv *env, const char *msg);
void JNU_ThrowArrayIndexOutOfBoundsException(JNIEnv *env, const char *msg);
void JNU_ThrowOutOfMemoryError(JNIEnv *env, const char *msg);
void JNU_ThrowIllegalArgumentException(JNIEnv *env, const char *msg);
void JNU_ThrowIllegalAccessError(JNIEnv *env, const char *msg);
void JNU_ThrowIllegalAccessException(JNIEnv *env, const char *msg);
void JNU_ThrowInternalError(JNIEnv *env, const char *msg);
void JNU_ThrowIOException(JNIEnv *env, const char *msg);
void JNU_ThrowNoSuchFieldException(JNIEnv *env, const char *msg);
void JNU_ThrowNoSuchMethodException(JNIEnv *env, const char *msg);
void JNU_ThrowClassNotFoundException(JNIEnv *env, const char *msg);
void JNU_ThrowNumberFormatException(JNIEnv *env, const char *msg);
void JNU_ThrowNoSuchFieldError(JNIEnv *env, const char *msg);
void JNU_ThrowNoSuchMethodError(JNIEnv *env, const char *msg);
void JNU_ThrowStringIndexOutOfBoundsException(JNIEnv *env, const char *msg);
void JNU_ThrowInstantiationException(JNIEnv *env, const char *msg);

/* Class constants */

jclass JNU_ClassString(JNIEnv *env);
jclass JNU_ClassClass(JNIEnv *env);
jclass JNU_ClassObject(JNIEnv *env);
jclass JNU_ClassThrowable(JNIEnv *env);

/* Copy count number of arguments from src to dst. Array bounds and ArrayStoreException are checked. */
jint JNU_CopyObjectArray(JNIEnv *env, jobjectArray dst, jobjectArray src, jint count);

/* Invoke a object-returning static method, based on class name,
 * method name, and signature string.
 *
 * The caller should check for exceptions by setting hasException
 * argument. If the caller is not interested in whether an exception
 * has occurred, pass in NULL.
 */
jvalue JNU_CallStaticMethod(JNIEnv *env, jboolean *hasException, const char *class_name, const char *name, const char *signature, ...);

/* Invoke an instance method by name. */
jvalue JNU_CallMethod(JNIEnv *env, jboolean *hasException, jobject obj, const char *name, const char *signature, ...);
jvalue JNU_CallMethodV(JNIEnv *env, jboolean *hasException, jobject obj, const char *name, const char *signature, va_list args);

/* Construct a new object of class, specifiying the class by name,
 * and specififying which constructor to run and what arguments to
 * pass to it.
 *
 * The method will return an initialized instance if successful.
 * It will return NULL if an error has occured (for example if
 * it ran out of memory) and the appropriate Java exception will
 * have been thrown.
 */
jobject JNU_NewObject(JNIEnv *env, const char *class_name, const char *constructor_sig, ...);

/* returns:
 * 0: object is not an instance of the class named by classname.
 * 1: object is an instance of the class named by classname.
 * -1: the class named by classname cannot be found. An exception
 * has been thrown.
 */
jint JNU_IsInstanceOf(JNIEnv *env, jobject object, char *classname);


/* Get or set class and instance fields.
 * Note that set functions take a variable number of arguments,
 * but only one argument of the appropriate type can be passed.
 * For example, to set an integer field i to 100:
 *
 * JNU_SetFieldByName(env, &exc, obj, "i", "I", 100);
 *
 * To set a float field f to 12.3:
 *
 * JNU_SetFieldByName(env, &exc, obj, "f", "F", 12.3);
 *
 * The caller should check for exceptions by setting hasException
 * argument. If the caller is not interested in whether an exception
 * has occurred, pass in NULL.
 */
jvalue JNU_GetField(JNIEnv *env, jboolean *hasException, jobject obj, const char *name, const char *sig);
void JNU_SetField(JNIEnv *env, jboolean *hasException, jobject obj, const char *name, const char *sig, ...);
jvalue JNU_GetStaticField(JNIEnv *env, jboolean *hasException, const char *classname, const char *name, const char *sig);
void JNU_SetStaticField(JNIEnv *env, jboolean *hasException, const char *classname, const char *name, const char *sig, ...);

/*
 * Calls the .equals method.
 */
jboolean JNU_Equals(JNIEnv *env, jobject object1, jobject object2);

/************************************************************************
 * Thread calls
 *
 * Convenience thread-related calls on the java.lang.Object class.
 */

void JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout);
void JNU_Notify(JNIEnv *env, jobject object);
void JNU_NotifyAll(JNIEnv *env, jobject object);


/************************************************************************
 * Miscellaneous utilities used by the class libraries */

#define IS_NULL(obj)		((obj) == NULL)


/************************************************************************
 * Debugging utilities
 */

jstring JNU_ToString(JNIEnv *env, jobject object);

/* 
 * Package shorthand for use by native libraries
 */
#define JNU_JAVAPKG			"java/lang/"
#define JNU_JAVAIOPKG		"java/io/"
#define JNU_JAVANETPKG		"java/net/"

/*
 * Check if the current thread is attached to the VM, and returns
 * the JNIEnv of the specified version if the thread is attached.
 *
 * If the current thread is not attached, this function returns 0.
 *
 * If the current thread is attached, this function returns the
 * JNI environment, or returns (void *)JNI_ERR if the specified
 * version is not suppored. 
 */
void *JNU_GetEnv(JavaVM *vm, jint version);

/*
 * Warning free access to pointers stored in Java long fields.
 */
#define JNU_GetLongFieldAsPtr(env,obj,id)		((void *)(unsigned long)(*(env))->GetLongField((env),(obj),(id)))
#define JNU_SetLongFieldFromPtr(env,obj,id,val)	(*(env))->SetLongField((env),(obj),(id),(jlong)(unsigned long)(val))

//

#ifdef __cplusplus
}
#endif

//

#endif

#include "JNIUtility.h"

#include "NativeHelper/JNIHelp.h"

#include <stdio.h>
#include <string.h>

//

void JNU_ThrowNullPointerException(JNIEnv *env, const char *msg)			{ jniThrowException(env, "java/lang/NullPointerException", msg); }
void JNU_ThrowArrayIndexOutOfBoundsException(JNIEnv *env, const char *msg)	{ jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", msg); }
void JNU_ThrowOutOfMemoryError(JNIEnv *env, const char *msg)				{ jniThrowException(env, "java/lang/OutOfMemoryError", msg); }
void JNU_ThrowIllegalArgumentException(JNIEnv *env, const char *msg)		{ jniThrowException(env, "java/lang/IllegalArgumentException", msg); }
void JNU_ThrowIllegalAccessError(JNIEnv *env, const char *msg)				{ jniThrowException(env, "java/lang/IllegalAccessError", msg); }
void JNU_ThrowIllegalAccessException(JNIEnv *env, const char *msg)			{ jniThrowException(env, "java/lang/IllegalAccessException", msg); }
void JNU_ThrowInternalError(JNIEnv *env, const char *msg)					{ jniThrowException(env, "java/lang/InternalError", msg); }
void JNU_ThrowNoSuchFieldException(JNIEnv *env, const char *msg)			{ jniThrowException(env, "java/lang/NoSuchFieldException", msg); }
void JNU_ThrowNoSuchMethodException(JNIEnv *env, const char *msg)			{ jniThrowException(env, "java/lang/NoSuchMethodException", msg); }
void JNU_ThrowClassNotFoundException(JNIEnv *env, const char *msg)			{ jniThrowException(env, "java/lang/ClassNotFoundException", msg); }
void JNU_ThrowNumberFormatException(JNIEnv *env, const char *msg)			{ jniThrowException(env, "java/lang/NumberFormatException", msg); }
void JNU_ThrowIOException(JNIEnv *env, const char *msg)						{ jniThrowException(env, "java/io/IOException", msg); }
void JNU_ThrowNoSuchFieldError(JNIEnv *env, const char *msg)				{ jniThrowException(env, "java/lang/NoSuchFieldError", msg); }
void JNU_ThrowNoSuchMethodError(JNIEnv *env, const char *msg)				{ jniThrowException(env, "java/lang/NoSuchMethodError", msg); }
void JNU_ThrowStringIndexOutOfBoundsException(JNIEnv *env, const char *msg)	{ jniThrowException(env, "java/lang/StringIndexOutOfBoundsException", msg); }
void JNU_ThrowInstantiationException(JNIEnv *env, const char *msg)			{ jniThrowException(env, "java/lang/InstantiationException", msg); }

//

jvalue JNU_CallStaticMethod(JNIEnv *env, jboolean *hasException, const char *class_name, const char *name, const char *signature, ...)
{
	jclass clazz;
	jmethodID mid;
	va_list args;
	jvalue result;
	const char *p	= signature;

	/* find out the return type */
	while (*p && *p != ')') {
		p++;
	}
	p++;

	result.i = 0;

	if (env->EnsureLocalCapacity(3) < 0) {
		goto done2;
	}

	clazz = env->FindClass(class_name);
	if (clazz == 0) {
		goto done2;
	}
	mid = env->GetStaticMethodID(clazz, name, signature);
	if (mid == 0) {
		goto done1;
	}

	va_start(args, signature);
	switch (*p) {
		case 'V':
			env->CallStaticVoidMethodV(clazz, mid, args); 
			break;
		case '[':
		case 'L':
			result.l = env->CallStaticObjectMethodV(clazz, mid, args); 
			break;
		case 'Z':
			result.z = env->CallStaticBooleanMethodV(clazz, mid, args); 
			break;
		case 'B':
			result.b = env->CallStaticByteMethodV(clazz, mid, args); 
			break;
		case 'C':
			result.c = env->CallStaticCharMethodV(clazz, mid, args); 
			break;
		case 'S':
			result.s = env->CallStaticShortMethodV(clazz, mid, args); 
			break;
		case 'I':
			result.i = env->CallStaticIntMethodV(clazz, mid, args); 
			break;
		case 'J':
			result.j = env->CallStaticLongMethodV(clazz, mid, args); 
			break;
		case 'F':
			result.f = env->CallStaticFloatMethodV(clazz, mid, args); 
			break;
		case 'D':
			result.d = env->CallStaticDoubleMethodV(clazz, mid, args); 
			break;
		default:
			env->FatalError("JNU_CallStaticMethodByName: illegal signature");
			break;
	}
	va_end(args);

done1:
	env->DeleteLocalRef(clazz);

done2:
	if (hasException) {
		*hasException = env->ExceptionCheck();
	}
	return result;
}

jvalue JNU_CallMethod(JNIEnv *env, jboolean *hasException, jobject obj, const char *name, const char *signature, ...)
{
	jvalue result;
	va_list args;

	va_start(args, signature);
	result = JNU_CallMethodV(env, hasException, obj, name, signature, args); 
	va_end(args);
	return result;
}

jvalue JNU_CallMethodV(JNIEnv *env, jboolean *hasException, jobject obj, const char *name, const char *signature, va_list args)
{
	jclass clazz;
	jmethodID mid;
	jvalue result;
	const char *p	= signature;

	/* find out the return type */
	while (*p && *p != ')') {
		p++;
	}
	p++;

	result.i = 0;

	if (env->EnsureLocalCapacity(3) < 0) {
		goto done2;
	}

	clazz = env->GetObjectClass(obj);
	mid = env->GetMethodID(clazz, name, signature);
	if (mid == 0) {
		goto done1;
	}

	switch (*p) {
		case 'V':
			env->CallVoidMethodV(obj, mid, args);
			break;
		case '[':
		case 'L':
			result.l = env->CallObjectMethodV(obj, mid, args);
			break;
		case 'Z':
			result.z = env->CallBooleanMethodV(obj, mid, args);
			break;
		case 'B':
			result.b = env->CallByteMethodV(obj, mid, args);
			break;
		case 'C':
			result.c = env->CallCharMethodV(obj, mid, args);
			break;
		case 'S':
			result.s = env->CallShortMethodV(obj, mid, args);
			break;
		case 'I':
			result.i = env->CallIntMethodV(obj, mid, args);
			break;
		case 'J':
			result.j = env->CallLongMethodV(obj, mid, args);
			break;
		case 'F':
			result.f = env->CallFloatMethodV(obj, mid, args);
			break;
		case 'D':
			result.d = env->CallDoubleMethodV(obj, mid, args);
			break;
		default:
			env->FatalError("JNU_CallMethodByNameV: illegal signature");
			break;
	}

	done1:
	env->DeleteLocalRef(clazz);

	done2:
	if (hasException) {
		*hasException = env->ExceptionCheck();
	}
	return result;
}

jobject JNU_NewObject(JNIEnv *env, const char *class_name, const char *constructor_sig, ...)
{
	jobject obj	= NULL;

	jclass cls	= 0;
	jmethodID cls_initMID;
	va_list args;

	if (env->EnsureLocalCapacity(2) < 0) {
		goto done;
	}

	cls = env->FindClass(class_name);
	if (cls == 0) {
		goto done;
	}
	cls_initMID = env->GetMethodID(cls, "<init>", constructor_sig);
	if (cls_initMID == NULL) {
		goto done;
	}
	va_start(args, constructor_sig);
	obj = env->NewObjectV(cls, cls_initMID, args);
	va_end(args);

	done:
	env->DeleteLocalRef(cls);
	return obj;
}

/* Note: JNU_ClassString, JNU_ClassClass, JNU_ClassObject, JNU_ClassThrowable */

jclass JNU_ClassString(JNIEnv *env)
{
	static jclass cls	= 0;
	if (cls == 0) {
		jclass c;
		if (env->EnsureLocalCapacity(1) < 0) {
			return 0;
		}

		c = env->FindClass("java/lang/String");
		cls = reinterpret_cast<jclass>(env->NewGlobalRef(c));
		env->DeleteLocalRef(c);
	}
	return cls;
}

jclass JNU_ClassClass(JNIEnv *env)
{
	static jclass cls	= 0;
	if (cls == 0) {
		jclass c;
		if (env->EnsureLocalCapacity(1) < 0) {
			return 0;
		}

		c = env->FindClass("java/lang/Class");
		cls = reinterpret_cast<jclass>(env->NewGlobalRef(c));
		env->DeleteLocalRef(c);
	}
	return cls;
}

jclass JNU_ClassObject(JNIEnv *env)
{
	static jclass cls	= 0;
	if (cls == 0) {
		jclass c;
		if (env->EnsureLocalCapacity(1) < 0) {
			return 0;
		}

		c = env->FindClass("java/lang/Object");
		cls = reinterpret_cast<jclass>(env->NewGlobalRef(c));
		env->DeleteLocalRef(c);
	}
	return cls;
}

jclass JNU_ClassThrowable(JNIEnv *env)
{
	static jclass cls	= 0;
	if (cls == 0) {
		jclass c;
		if (env->EnsureLocalCapacity(1) < 0) {
			return 0;
		}

		c = env->FindClass("java/lang/Throwable");
		cls = reinterpret_cast<jclass>(env->NewGlobalRef(c));
		env->DeleteLocalRef(c);
	}
	return cls;
}

jint JNU_CopyObjectArray(JNIEnv *env, jobjectArray dst, jobjectArray src, jint count)
{
	if (env->EnsureLocalCapacity(1) < 0) {
		return -1;
	}

	for (int i = 0; i < count; i++) {
		jstring p	= (jstring) env->GetObjectArrayElement(src, i);
		env->SetObjectArrayElement(dst, i, p);
		env->DeleteLocalRef(p);
	}
	return 0;
}

void * JNU_GetEnv(JavaVM *vm, jint version)
{
	void *env = NULL;
	vm->GetEnv(&env, version);
	return env;
}

jint JNU_IsInstanceOf(JNIEnv *env, jobject object, char *classname)
{
	jclass cls;
	if (env->EnsureLocalCapacity(1) < 0) {
		return JNI_ERR;
	}

	cls = env->FindClass(classname);
	if (cls != NULL) {
		jint result	= env->IsInstanceOf(object, cls);
		env->DeleteLocalRef(cls);
		return result;
	}
	return JNI_ERR;
}


/************************************************************************
 * Debugging utilities
 */

jstring JNU_ToString(JNIEnv *env, jobject object)
{
	if (object == NULL) {
		return env->NewStringUTF("NULL");
	}
	else {
		return reinterpret_cast<jstring>(JNU_CallMethod(env, NULL, object, "toString", "()Ljava/lang/String;").l);
	}
}

jvalue JNU_GetField(JNIEnv *env, jboolean *hasException, jobject obj, const char *name, const char *signature)
{
	jclass cls;
	jfieldID fid;
	jvalue result;
	result.i = 0;

	if (env->EnsureLocalCapacity(3) < 0) {
		goto done2;
	}

	cls = env->GetObjectClass(obj);
	fid = env->GetFieldID(cls, name, signature);
	if (fid == 0) {
		goto done1;
	}

	switch (*signature) {
		case '[':
		case 'L':
			result.l = env->GetObjectField(obj, fid);
			break;  
		case 'Z':
			result.z = env->GetBooleanField(obj, fid);
			break;
		case 'B':
			result.b = env->GetByteField(obj, fid);
			break;
		case 'C':
			result.c = env->GetCharField(obj, fid);
			break;
		case 'S':
			result.s = env->GetShortField(obj, fid);
			break;
		case 'I':
			result.i = env->GetIntField(obj, fid);
			break;
		case 'J':
			result.j = env->GetLongField(obj, fid);
			break;
		case 'F':
			result.f = env->GetFloatField(obj, fid);
			break;
		case 'D':
			result.d = env->GetDoubleField(obj, fid);
			break;
		default:
			env->FatalError("JNU_GetFieldByName: illegal signature");
			break;
	}

done1:
	env->DeleteLocalRef(cls);

done2:
	if (hasException) {
		*hasException = env->ExceptionCheck();
	}
	return result;
}

void JNU_SetField(JNIEnv *env, jboolean *hasException, jobject obj, const char *name, const char *signature, ...)
{
	jclass cls;
	jfieldID fid;
	va_list args;

	if (env->EnsureLocalCapacity(3) < 0) {
		goto done2;
	}

	cls = env->GetObjectClass(obj);
	fid = env->GetFieldID(cls, name, signature);
	if (fid == 0) {
		goto done1;
	}

	/* When passing arguments via '...', each argument is converted to int
	   or double (as appropriate). When we pick off the mystery argument
	   with va_arg(), we need to use the type of argument as passed, not
	   the type we want. The compiler will convert the argument back to the
	   proper type when passing it to Set<type>Field(). */

	va_start(args, signature);
	switch (*signature) {
		case '[':
		case 'L':
			env->SetObjectField(obj, fid, va_arg(args, jobject));
			break;  
		case 'Z':
			env->SetBooleanField(obj, fid, (jboolean) va_arg(args, jint));
			break;
		case 'B':
			env->SetByteField(obj, fid, (jbyte) va_arg(args, jint));
			break;
		case 'C':
			env->SetCharField(obj, fid, (jchar) va_arg(args, jint));
			break;
		case 'S':
			env->SetShortField(obj, fid, (jshort) va_arg(args, jint));
			break;
		case 'I':
			env->SetIntField(obj, fid, va_arg(args, jint));
			break;
		case 'J':
			env->SetLongField(obj, fid, va_arg(args, jlong));
			break;
		case 'F':
			env->SetFloatField(obj, fid, (jfloat) va_arg(args, jdouble));
			break;
		case 'D':
			env->SetDoubleField(obj, fid, va_arg(args, jdouble));
			break;
		default:
			env->FatalError("JNU_SetFieldByName: illegal signature");
			break;
	}
	va_end(args);

done1:
	env->DeleteLocalRef(cls);

done2:
	if (hasException) {
		*hasException = env->ExceptionCheck();
	}
}

jvalue JNU_GetStaticField(JNIEnv *env, jboolean *hasException, const char *classname, const char *name, const char *signature)
{
	jclass cls;
	jfieldID fid;
	jvalue result;

	result.i = 0;

	if (env->EnsureLocalCapacity(3) < 0) {
		goto done2;
	}

	cls = env->FindClass(classname);
	if (cls == 0) {
		goto done2;
	}

	fid = env->GetStaticFieldID(cls, name, signature);
	if (fid == 0) {
		goto done1;
	}

	switch (*signature) {
		case '[':
		case 'L':
			result.l = env->GetStaticObjectField(cls, fid);
			break;
		case 'Z':
			result.z = env->GetStaticBooleanField(cls, fid);
			break;
		case 'B':
			result.b = env->GetStaticByteField(cls, fid);
			break;
		case 'C':
			result.c = env->GetStaticCharField(cls, fid);
			break;
		case 'S':
			result.s = env->GetStaticShortField(cls, fid);
			break;
		case 'I':
			result.i = env->GetStaticIntField(cls, fid);
			break;
		case 'J':
			result.j = env->GetStaticLongField(cls, fid);
			break;
		case 'F':
			result.f = env->GetStaticFloatField(cls, fid);
			break;
		case 'D':
			result.d = env->GetStaticDoubleField(cls, fid);
			break;
		default:
			env->FatalError("JNU_GetStaticFieldByName: illegal signature");
			break;
	}

done1:
	env->DeleteLocalRef(cls);

done2:
	if (hasException) {
		*hasException = env->ExceptionCheck();
	}
	return result;
}

void JNU_SetStaticField(JNIEnv *env, jboolean *hasException, const char *classname, const char *name, const char *signature, ...)
{
	jclass cls;
	jfieldID fid;
	va_list args;

	if (env->EnsureLocalCapacity(3) < 0) {
		goto done2;
	}

	cls = env->FindClass(classname);
	if (cls == 0) {
		goto done2;
	}

	fid = env->GetStaticFieldID(cls, name, signature);
	if (fid == 0) {
		goto done1;
	}

	/* When passing arguments via '...', each argument is converted to int
	   or double (as appropriate). When we pick off the mystery argument
	   with va_arg(), we need to use the type of argument as passed, not
	   the type we want. The compiler will convert the argument back to the
	   proper type when passing it to SetStatic<type>Field(). */

	va_start(args, signature);
	switch (*signature) {
		case '[':
		case 'L':
			env->SetStaticObjectField(cls, fid, va_arg(args, jobject));
			break;  
		case 'Z':
			env->SetStaticBooleanField(cls, fid, (jboolean) va_arg(args, jint));
			break;
		case 'B':
			env->SetStaticByteField(cls, fid, (jbyte) va_arg(args, jint));
			break;
		case 'C':
			env->SetStaticCharField(cls, fid, (jchar) va_arg(args, jint));
			break;
		case 'S':
			env->SetStaticShortField(cls, fid, (jshort) va_arg(args, jint));
			break;
		case 'I':
			env->SetStaticIntField(cls, fid, va_arg(args, jint));
			break;
		case 'J':
			env->SetStaticLongField(cls, fid, va_arg(args, jlong));
			break;
		case 'F':
			env->SetStaticFloatField(cls, fid, (jfloat) va_arg(args, jdouble));
			break;
		case 'D':
			env->SetStaticDoubleField(cls, fid, va_arg(args, jdouble));
			break;
		default:
			env->FatalError("JNU_SetStaticFieldByName: illegal signature");
			break;
	}
	va_end(args);

done1:
	env->DeleteLocalRef(cls);

done2:
	if (hasException) {
		*hasException = env->ExceptionCheck();
	}
}

jboolean JNU_Equals(JNIEnv *env, jobject object1, jobject object2)
{
	return JNU_CallMethod(env, NULL, object1, "equals", "(Ljava/lang/Object;)Z", object2).z;
}
void JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout)
{
	if (object == NULL) {
		JNU_ThrowNullPointerException(env, "JNU_MonitorWait argument");
		return;
	}

	JNU_CallMethod(env, NULL, object, "wait", "(J)V", timeout);
}
void JNU_Notify(JNIEnv *env, jobject object)
{
	if (object == NULL) {
		JNU_ThrowNullPointerException(env, "JNU_Notify argument");
		return;
	}

	JNU_CallMethod(env, NULL, object, "notify", "()V");
}
void JNU_NotifyAll(JNIEnv *env, jobject object)
{
	if (object == NULL) {
		JNU_ThrowNullPointerException(env, "JNU_NotifyAll argument");
		return;
	}

	JNU_CallMethod(env, NULL, object, "notifyAll", "()V");
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值