JNI应用小结

 一、Primitive Types(基本类型)
Java Type  Native Type  Description
boolean  jboolean  unsigned 8 bits
byte  jbyte  signed 8 bits
char  jchar  unsigned 16 bits
short  jshort  signed 16 bits
int  jint  signed 32 bits
long  jlong  signed 64 bits
float  jfloat  32 bits
double  jdouble  64 bits
void  void  N/A
The following definition is provided for convenience.
#define JNI_FALSE  0
#define JNI_TRUE   1
The jsize integer type is used to describe cardinal indices and sizes:
typedef jint jsize;


二、Reference Types (引用类型)
The JNI includes a number of reference types that correspond to different kinds of Java objects. JNI reference types are organized in the hierarchy shown in next:
jobject(all java objects)
----jclass(java.lang.Class objects)
----jstring(java.lang.String objects)
----jthrowable(java.lang.Throwable objects)
----jarray(arrays)
----jobjectArray(objects arrays)
----jbooleanArray(boolean arrays)
----jbyteArray (byte arrays)
    ----jcharArray (char arrays)
    ----jshortArray (short arrays)
    ----jintArray (int arrays)
    ----jlongArray (long arrays)
    ----jfloatArray (float arrays)
    ----jdoubleArray (double arrays)
In C, all other JNI reference types are defined to be the same as jobject. For example:
typedef jobject jclass;
In C++, JNI introduces a set of dummy classes to enforce the subtyping relationship. For example:
class _jobject {};
class _jclass : public _jobject {};
...
typedef _jobject *jobject;
typedef _jclass *jclass;


三、Field and Method IDs (字段和方法ID)
Method and field IDs are regular C pointer types:
struct _jfieldID;              /* opaque structure */
typedef struct _jfieldID *jfieldID;   /* field IDs */
 
struct _jmethodID;              /* opaque structure */
typedef struct _jmethodID *jmethodID; /* method IDs */


四、The Value Type (值类型)
The jvalue union type is used as the element type in argument arrays. It is declared as follows:
typedef union jvalue {
    jboolean z;
    jbyte    b;
    jchar    c;
    jshort   s;
    jint     i;
    jlong    j;
    jfloat   f;
    jdouble  d;
    jobject  l;
} jvalue;


五、Type Signatures (类型签名)
The JNI uses the Java VM’s representation of type signatures. Table 3-2 shows these type signatures.
Table 3-2 Java VM Type Signatures
Type Signature  Java Type
Z  boolean
B  byte
C  char
S  short
I  int
J  long
F  float
D  double
L fully-qualified-class ;  fully-qualified-class
[ type  type[]
( arg-types ) ret-type  method type
For example, the Java method:
long f (int n, String s, int[] arr);
has the following type signature:
(ILjava/lang/String;[I)J


六、Jni数据类型与C,C++数据类型转换以及在Jni中访问Java对象。

(Java简单类型,字符串,数组,对象,对象数组等类型的转换,以及在Jni中如何访问Java对象的属性,方法,静态属性,静态方法,构造函数等):
(下面以一个实际例子来说吧,本节的例子都以JniNative.java为jni的java类文件,在com.jni.demo包下面,用javah命令生成的C头文件为com_jni_demo_JniNative.h,自己实现jni函数的c文件名为com_jni_demo_JniNative.c)


1、在Eclipse新建一个Java工程,名为JniDemo,里面新建一个com.jni.demo包,包中含有如下Java文件:JniNative.java,JniNativeTest.java,Person.java,分别是jni函数的java声明文件,jni函数的测试文件,jni对象传递的数据结构文件,内容如下:


1)JniNative.java:

package com.jni.demo;

public class JniNative {
 
 // 简单类型的参数传递和返回,使用方式相似,仅以int类型作为示例
 // boolean, byte,char, short,int, long,float, double
 // 分别对应jboolean, jbyte, jchar, jshort, jint, jlong,jfloat,jdouble
 public native int IntegerFunc(int i);
 
 // String类型的参数传递和返回
 public native String StringFunc(String str);
 
 // 简单类型的数组参数传递和返回
 public native int[] IntegerArrayFunc(int iArray[]);
 
 // String类型的数组参数传递和返回
 public native String[] StringArrayFunc(String strArray[]);
 
 // 复杂对象的传递和返回
 // 包括在jni函数中访问类的实例域的演示
 public native Person ObjectFunc(Person person);
 
 // 复杂对象的数组参数传递和返回
 public native Person[] ObjectArrayFunc(Person personArray[]);  
  
 // 在jni函数中访问类的静态实例域
 public native void accessStaticField();
 
 // 在jni函数中调用java对象的方法
 public native void accessNormalFunc();
 
 // 在jni函数中调用java类的静态方法
 public native void accessStaticFunc();
}

 

2)JniNativeTest.java:
package com.jni.demo;

import junit.framework.TestCase;

public class JniNativeTest extends TestCase {

 static {
  System.loadLibrary("JniDemo");
 }

 public static JniNative jniobj = new JniNative();

 public void testIntegerFunc() {

  int iRet = jniobj.IntegerFunc(10);
  assertEquals(20, iRet);
 }

 public void testStringFunc() {

  String strRet = jniobj.StringFunc("joe");
  assertEquals("Hello jni: joe", strRet);
 }

 public void testIntegerArrayFunc() {

  int iArray[] = new int[5];
  for (int i = 0; i < 5; i++) {
   iArray[i] = i;
  }
  int iArrayRet[] = jniobj.IntegerArrayFunc(iArray);

  for (int i = 0; i < 5; i++) {
   assertEquals(i, iArrayRet[i]);
  }
 }

 public void testStringArrayFunc() {

  String strArray[] = new String[5];
  for (int i = 0; i < 5; i++) {
   strArray[i] = new String("hello_" + i);
  }

  String strArrayRet[] = jniobj.StringArrayFunc(strArray);

  for (int i = 0; i < 5; i++) {
   assertEquals("hello_" + i, strArrayRet[i]);
  }
 }

 public void testObjectFunc() {

  Person person = new Person();
  person.name = "joe中华人民";
  person.age = 28;

  Person personRet = jniobj.ObjectFunc(person);
  assertEquals("joe中华人民", personRet.name);
  assertEquals(28, personRet.age);
 }

 public void testObjectArrayFunc() {

  Person personArray[] = new Person[5];
  for (int i = 0; i < 5; i++) {
   personArray[i] = new Person();
   personArray[i].name = "joe" + i;
   personArray[i].age = 28 + i;
  }

  Person personArrayRet[] = jniobj.ObjectArrayFunc(personArray);

  for (int i = 0; i < 5; i++) {
   assertEquals("joe" + i, personArrayRet[i].name);
   assertEquals(28 + i, personArrayRet[i].age);
  }
 }

 public void testaccessStaticField() {
  
  jniobj.accessStaticField();
  assertEquals(123, Person.s_i);
 }

 public void testaccessNormalFunc() {

  jniobj.accessNormalFunc();
 }

 public void testaccessStaticFunc() {

  jniobj.accessStaticFunc();
  assertEquals(321, Person.s_i);
 }
}

 

3)Person.java:
package com.jni.demo;

public class Person {
 public int age;
 public String name;
 
 public void normalFunc(String strName){
  name = strName;
 }
 
 public static int s_i = 0;
 public static void staticFunc(int i){
  s_i = i;
 }
}


2、生成com_jni_demo_JniNative.h文件(在cmd命令行中,定位路径到JniDemo工程的bin目录,执行:javah com.jni.demo.JniNative)。


3、用VC新建一个空的动态库工程名为JniDemo,将com_jni_demo_JniNative.h添加到VC工程中,新建一个com_jni_demo_JniNative.c文件,实现com_jni_demo_JniNative.h中的jni函数。

 

两个文件的内容分别如下:
1)com_jni_demo_JniNative.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_jni_demo_JniNative */

#ifndef _Included_com_jni_demo_JniNative
#define _Included_com_jni_demo_JniNative
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_jni_demo_JniNative
 * Method:    IntegerFunc
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_jni_demo_JniNative_IntegerFunc
  (JNIEnv *, jobject, jint);

/*
 * Class:     com_jni_demo_JniNative
 * Method:    StringFunc
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_jni_demo_JniNative_StringFunc
  (JNIEnv *, jobject, jstring);

/*
 * Class:     com_jni_demo_JniNative
 * Method:    IntegerArrayFunc
 * Signature: ([I)[I
 */
JNIEXPORT jintArray JNICALL Java_com_jni_demo_JniNative_IntegerArrayFunc
  (JNIEnv *, jobject, jintArray);

/*
 * Class:     com_jni_demo_JniNative
 * Method:    StringArrayFunc
 * Signature: ([Ljava/lang/String;)[Ljava/lang/String;
 */
JNIEXPORT jobjectArray JNICALL Java_com_jni_demo_JniNative_StringArrayFunc
  (JNIEnv *, jobject, jobjectArray);

/*
 * Class:     com_jni_demo_JniNative
 * Method:    ObjectFunc
 * Signature: (Lcom/jni/demo/Person;)Lcom/jni/demo/Person;
 */
JNIEXPORT jobject JNICALL Java_com_jni_demo_JniNative_ObjectFunc
  (JNIEnv *, jobject, jobject);

/*
 * Class:     com_jni_demo_JniNative
 * Method:    ObjectArrayFunc
 * Signature: ([Lcom/jni/demo/Person;)[Lcom/jni/demo/Person;
 */
JNIEXPORT jobjectArray JNICALL Java_com_jni_demo_JniNative_ObjectArrayFunc
  (JNIEnv *, jobject, jobjectArray);

/*
 * Class:     com_jni_demo_JniNative
 * Method:    accessStaticField
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_demo_JniNative_accessStaticField
  (JNIEnv *, jobject);

/*
 * Class:     com_jni_demo_JniNative
 * Method:    accessNormalFunc
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_demo_JniNative_accessNormalFunc
  (JNIEnv *, jobject);

/*
 * Class:     com_jni_demo_JniNative
 * Method:    accessStaticFunc
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_demo_JniNative_accessStaticFunc
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif


2)com_jni_demo_JniNative.c:
#include "com_jni_demo_JniNative.h"
#include <stdio.h>
#include <malloc.h>
#include <string.h>

#ifdef __cplusplus  
extern "C" 
{  
#endif
   
    typedef struct _Person
    {
        char name[20];
        int age;
    }Person;
   
    //带GBK编码的字符串拷贝,防止截取半个中文字符的情况发生  
    //形参表的含义同strncpy函数  
    void strncpy2(char *pDst,char *pSrc,int nDstLen)  
    {  
        int nCount=0;          
        int i = 0;
       
        //pDst缓存足够容纳pSrc中的数据  
        if ( (int)strlen(pSrc) < nDstLen )  
        {  
            strncpy(pDst, pSrc, nDstLen);  
        }  
       
        if ( nDstLen < 2 )
        {
            return;  
        }
       
        memset(pDst, 0, nDstLen);          
       
        for( ; i<nDstLen; i++)  
        {  
            if ( pSrc[i] & 0x80 )
            {
                nCount++;  
            }
        }  
       
        if ( (nCount%2 == 0) && (pSrc[nDstLen-1] & 0x80) )
        {
            strncpy(pDst, pSrc, nDstLen-2);  
        }
        else
        {
            strncpy(pDst, pSrc, nDstLen-1);  
        }
    }  
   
   
   
    jstring CharTojstring(JNIEnv* env, char* str)
    {
        jstring rtn = 0;
        jsize len;
        jclass clsstring;
        jstring strencode;
        jmethodID mid;
        jbyteArray barr;
       
        if ( 0 == str )
        {
            // jstring对象为空是返回0,不能返回""
            // 不要想当然的把jstring等同为C++的string
            return (jstring)0;
        }
       
        len = (int)strlen(str);
       
        if ( 0 == len )
        {
            // jstring对象为空是返回0,不能返回""
            // 不要想当然的把jstring等同为C++的string
            return (jstring)0;
        }
       
        clsstring = (*env)->FindClass(env, "java/lang/String");
       
        //new   encode   string   default   "GBK "
        strencode = (*env)->NewStringUTF(env, "GBK");
        mid = (*env)->GetMethodID(env, clsstring, "<init>", "([BLjava/lang/String;)V");
        barr = (*env)-> NewByteArray(env, len);
       
        (*env)->SetByteArrayRegion(env, barr, 0, len, (jbyte*)str);
       
        rtn = (jstring)(*env)->NewObject(env, clsstring, mid, barr, strencode);
       
        return rtn;
    }
   
    char* jstringToChar(JNIEnv* env, jstring jstr)
    {
        char* rtn = NULL;
        jclass clsstring = (*env)->FindClass(env, "java/lang/String");
       
        //new   encode   string   default   "GBK "
        jstring strencode = (*env)->NewStringUTF(env, "GBK");
        jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
       
        //call   String.getBytes   method   to   avoid   incompatible   migrating   into   solaris
        jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode);
       
        jsize alen = (*env)->GetArrayLength(env, barr);
        jbyte* ba = (*env)-> GetByteArrayElements(env, barr, JNI_FALSE);
       
        if ( alen > 0 )
        {
            rtn = (char*)malloc(alen+1);
            memcpy(rtn, ba, alen);
            rtn[alen] = 0;
        }
        (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
       
        return   rtn;
    }
   
   
    /*
    * Class:     com_jni_demo_JniNative
    * Method:    IntegerFunc
    * Signature: (I)I
    */
    JNIEXPORT jint JNICALL Java_com_jni_demo_JniNative_IntegerFunc
        (JNIEnv *env, jobject obj, jint i)
    {
        printf("The parameter i is: [%d]\n", i);
        return i+i;
    }
   
    /*
    * Class:     com_jni_demo_JniNative
    * Method:    StringFunc
    * Signature: (Ljava/lang/String;)Ljava/lang/String;
    */
    JNIEXPORT jstring JNICALL Java_com_jni_demo_JniNative_StringFunc
        (JNIEnv *env, jobject obj, jstring str)
    {
        char szStr[256] = {0};
        jstring jstr;
       
        // jstring转char*
        char * pstr = (char *) (*env)->GetStringUTFChars(env, str, 0);
       
        printf("The parameter str is: [%s]\n", pstr);
       
        sprintf(szStr, "Hello jni: %s", pstr);
       
        jstr = (*env)->NewStringUTF(env, szStr);
       
        // 记得释放
        (*env)->ReleaseStringUTFChars(env, str, pstr);
       
        return jstr;
    }
   
    /*
    * Class:     com_jni_demo_JniNative
    * Method:    IntegerArrayFunc
    * Signature: ([I)[I
    */
    JNIEXPORT jintArray JNICALL Java_com_jni_demo_JniNative_IntegerArrayFunc
        (JNIEnv *env, jobject obj, jintArray iArray)
    {
        jintArray  array;//自定义数组对象
       
        jint *iElement;
        jsize len = (*env)->GetArrayLength(env, iArray);
       
        int i=0;       
       
        iElement = (*env)->GetIntArrayElements(env, iArray, 0);
       
        for ( ; i<len; i++)
        {           
            printf("The parameter iArray[%d] is: [%d]\n", i, iElement[i]);                
        }
       
        // 设置数组内容
        array = (*env)->NewIntArray(env, len);
        (*env)->SetIntArrayRegion(env, array, 0, len, iElement);          
       
        return array;
    }
   
    /*
    * Class:     com_jni_demo_JniNative
    * Method:    StringArrayFunc
    * Signature: ([Ljava/lang/String;)[Ljava/lang/String;
    */
    JNIEXPORT jobjectArray JNICALL Java_com_jni_demo_JniNative_StringArrayFunc
        (JNIEnv *env, jobject obj, jobjectArray strArray)
    {
        jstring jstr;
        char * pstr;
       
        jsize len = (*env)->GetArrayLength(env, strArray);
        jclass objClass = (*env)->FindClass(env, "java/lang/String");
        jobjectArray jobj = (*env)->NewObjectArray(env, len, objClass, 0);
       
        int i=0;
        for ( ; i<len; i++)
        {
            jstr = (*env)->GetObjectArrayElement(env, strArray, i);
           
            // jstring转char*
            pstr = (char *) (*env)->GetStringUTFChars(env, jstr, 0);            
            printf("The parameter strArray[%d] is: [%s]\n", i, pstr);
           
            (*env)->SetObjectArrayElement(env, jobj, i, jstr);
           
            (*env)->ReleaseStringUTFChars(env, jstr, pstr);
        }     
       
        return jobj;
    }
   
    /*
    * Class:     com_jni_demo_JniNative
    * Method:    ObjectFunc
    * Signature: (Lcom/jni/demo/Person;)Lcom/jni/demo/Person;
    */
    JNIEXPORT jobject JNICALL Java_com_jni_demo_JniNative_ObjectFunc
        (JNIEnv *env, jobject obj, jobject person)
    {
        jmethodID m_mid;
        jobject jobj;
        jstring jstrname;
        jint jintage;
        char * pstr;
        Person ps;
       
        jclass cls = (*env)->FindClass(env, "com/jni/demo/Person");     
        jfieldID fid_name = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
        jfieldID fid_age = (*env)->GetFieldID(env, cls, "age", "I");
       
        jstrname = (*env)->GetObjectField(env, person, fid_name);  
        jintage = (*env)->GetIntField(env, person, fid_age);  
       
       
        //         pstr = (char *) (*env)->GetStringUTFChars(env, jstrname, 0);    // 中文显示乱码
        pstr = jstringToChar(env, jstrname);     // 中文显示正常
       
        strcpy(ps.name, pstr);
        ps.age = jintage;       
       
        // 带中文字符的jstring在java中打印出来是乱码。
        // 想打印出来不是乱码,需要用pstr = jstringToChar(env, jstrname);
        printf("The paramter person.name is[%s], person.age is[%d]\n", pstr, jintage);
       
        m_mid   = (*env)->GetMethodID(env, cls,"<init>","()V");
        jobj = (*env)->NewObject(env, cls, m_mid);
        (*env)->SetObjectField(env, jobj, fid_name, jstrname);
        (*env)->SetIntField(env, jobj, fid_age, jintage);
       
        //         (*env)->ReleaseStringUTFChars(env, jstrname, pstr);
        free(pstr);
       
        return jobj;
    }
   
    /*
    * Class:     com_jni_demo_JniNative
    * Method:    ObjectArrayFunc
    * Signature: ([Lcom/jni/demo/Person;)[Lcom/jni/demo/Person;
    */
    JNIEXPORT jobjectArray JNICALL Java_com_jni_demo_JniNative_ObjectArrayFunc
        (JNIEnv *env, jobject obj, jobjectArray personArray)
    {
        int i;
        jobject jobj;
        jstring jstrname;
        jint jintage;
        jobjectArray jobjArray;
        int len;
        jclass cls;
        jfieldID fid_name;
        jfieldID fid_age;
        char * pstr;
        len = (*env)->GetArrayLength(env, personArray);;
        cls = (*env)->FindClass(env, "com/jni/demo/Person");     
        fid_name = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
        fid_age = (*env)->GetFieldID(env, cls, "age", "I");
       
        jobjArray = (*env)->NewObjectArray(env, len, cls, 0);
       
        i = 0;
        for ( ; i<len; i++)
        {
            jobj = (*env)->GetObjectArrayElement(env, personArray, i);
            jstrname = (*env)->GetObjectField(env, jobj, fid_name);
            jintage = (*env)->GetIntField(env, jobj, fid_age);
           
            pstr = (char *) (*env)->GetStringUTFChars(env, jstrname, 0);    // 中文显示乱码
            printf("The paramter personArray[%d].name is[%s], personArray[%d].age is[%d]\n", i, pstr, i, jintage);
           
            (*env)->SetObjectArrayElement(env, jobjArray, i, jobj);
            (*env)->ReleaseStringUTFChars(env, jstrname, pstr);
        }
       
        return jobjArray;
    }
   
    /*
    * Class:     com_jni_demo_JniNative
    * Method:    accessStaticField
    * Signature: ()V
    */
    JNIEXPORT void JNICALL Java_com_jni_demo_JniNative_accessStaticField
        (JNIEnv *env, jobject obj)
    {
        jclass jcls;
        jfieldID fdA;
        jint valueA;

        printf("The accessStaticField is called\n");

        jcls = (*env)->FindClass(env, "com/jni/demo/Person");
       
        fdA = (*env)->GetStaticFieldID(env, jcls, "s_i", "I"); 
       
        valueA = (*env)->GetStaticIntField(env, jcls, fdA); 
       
        (*env)->SetStaticIntField(env, jcls, fdA, 123);  
       
        return;
    }
   
    /*
    * Class:     com_jni_demo_JniNative
    * Method:    accessNormalFunc
    * Signature: ()V
    */
    JNIEXPORT void JNICALL Java_com_jni_demo_JniNative_accessNormalFunc
        (JNIEnv *env, jobject obj)
    {
        jclass cls;
        jmethodID m_mid;
        jmethodID m_mid1;
        jobject jobj;
        jstring jstr;

        printf("The accessNormalFunc is called\n");

        cls = (*env)->FindClass(env, "com/jni/demo/Person");   
        m_mid   = (*env)->GetMethodID(env, cls,"<init>","()V");
        jobj = (*env)->NewObject(env, cls, m_mid);

        m_mid1   = (*env)->GetMethodID(env, cls, "normalFunc", "(Ljava/lang/String;)V");   
        jstr = (*env)->NewStringUTF(env, "hello");

        (*env)->CallVoidMethod(env, jobj, m_mid1, jstr);

        return;
    }
    /*
    * Class:     com_jni_demo_JniNative
    * Method:    accessStaticFunc
    * Signature: ()V
    */
    JNIEXPORT void JNICALL Java_com_jni_demo_JniNative_accessStaticFunc
        (JNIEnv *env, jobject obj)
    {
        jclass jcls;
        jmethodID md;

        printf("The accessStaticFunc is called\n");

        jcls = (*env)->FindClass(env, "com/jni/demo/Person");   
       
        md = (*env)->GetStaticMethodID(env, jcls, "staticFunc", "(I)V");   
       
        (*env)->CallStaticVoidMethod(env, jcls, md, 321);  
       
        return;

    }
   
#ifdef __cplusplus
}  
#endif


4、编译和构建VC工程,生成动态库JniDemo.dll,拷贝JniDemo.dll到Eclipse工程JniDemo的跟目录下面,在Eclipse工程中以Junit方式运行JniDemoTest类,就可以看到测试的结果了。

 

5、注意事项:代码中有几处注释是值得注意的地方,这里再总结一下:
1)void strncpy2(char *pDst,char *pSrc,int nDstLen):
是带GBK编码的字符串拷贝,防止截取半个中文字符的情况发生,形参表的含义同strncpy函数。


2)jstring CharTojstring(JNIEnv* env, char* str):
是带GBK编码的char*转jstring。


3)char* jstringToChar(JNIEnv* env, jstring jstr):
是带GBK编码的jstring 转char*,用后记得要用free()函数来释放内存。这个函数的到的char*中文打印出来显示也正常,不会是乱码。


4)GetStringUTFChars:
也是jstring转char*,但是这样的转换,如果jstring中包含中文字符,打印出来是乱码。如果想显示不是乱码,就参考示例中jstringToChar的使用。使用GetStringUTFChars后记得用ReleaseStringUTFChars释放。


5)类型签名的使用:
比如:"Ljava/lang/String;"和"java/lang/String"有什么区别呢?
答案:"java/lang/String"表示类名,而"Ljava/lang/String;"表示类型签名,使用的方式是不一样的。
例如,
jclass cls = (*env)->FindClass(env, "com/jni/demo/Person");     
jfieldID fid_name =
 (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值