登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> 程序员学前班[不再更新,只读] >> 主题: java 与 C 的相互调用原来如此简单!     [回主站]     [分站链接]
标题
java 与 C 的相互调用原来如此简单!
clq
浏览(1) + 2007-09-15 23:01:37 发表 编辑

关键字:

java 与 C 的相互调用原来如此简单!

今天无意在书城看到了一本 sun 认证的高级书说是什么第7版本(7还是5?)其中调用C那一章节比我以前知道的可简单得多了,而且还有个C调用java函数的例子.

难道是因为它用的 j2se 1.5 的原因?

它也有个 loadlibrary 函数调用 C 的dll.

clq
2007-9-15 23:20:36 发表 编辑

Java基础知识——JNI入门介绍(上) zt

  学习了一下JNI,发表文章的时候不知道该选什么好了,不知道JNI应该属于那个范畴^_^。
  
  1.简介
  
  JNI是Java Native Interface的缩写,它的设计目的是:
  
  The standard Java class library may not support the platform-dependent features needed by your application.
  
  You may already have a library or application written in another programming language and you wish to make it accessible to Java applications
  
  You may want to implement a small portion of time-critical code in a lower-level programming language, such as assembly, and then have your Java application call these functions
  
  2.JNI的书写步骤
  
  编写带有native声明的方法的java类
  
  使用javac命令编译所编写的java类
  
  使用javah ?jni java类名生成扩展名为h的头文件
  
  使用C/C++实现本地方法
  
  将C/C++编写的文件生成动态连接库
  
  ok
  
  1) 编写java程序:
  
  这里以HelloWorld为例。
  
  代码1:
  
  class HelloWorld {
  public native void displayHelloWorld();
  
  static {
  System.loadLibrary("hello");
  }
  
  public static void main(String[] args) {
  new HelloWorld().displayHelloWorld();
  }
  }
  
  声明native方法:如果你想将一个方法做为一个本地方法的话,那么你就必须声明改方法为native的,并且不能实现。其中方法的参数和返回值在后面讲述。
  
  Load动态库:System.loadLibrary("hello");加载动态库(我们可以这样理解:我们的方法displayHelloWorld()没有实现,但是我们在下面就直接使用了,所以必须在使用之前对它进行初始化)这里一般是以static块进行加载的。同时需要注意的是System.loadLibrary();的参数“hello”是动态库的名字。
  
  main()方法
  
  2) 编译没有什么好说的了
  
  javac HelloWorld.java
  
  3) 生成扩展名为h的头文件
  
  javah ?jni HelloWorld
  
  头文件的内容:
  /* DO NOT EDIT THIS FILE - it is machine generated */
  #include
  /* Header for class HelloWorld */
  
  #ifndef _Included_HelloWorld
  #define _Included_HelloWorld
  #ifdef __cplusplus
  extern "C" {
  #endif
  /*
  * Class:   HelloWorld
  * Method:  displayHelloWorld
  * Signature: ()V
  */
  JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld
  (JNIEnv *, jobject);
  
  #ifdef __cplusplus
  }
  #endif
  #endif
  
  (这里我们可以这样理解:这个h文件相当于我们在java里面的接口,这里声明了一个Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject);方法,然后在我们的本地方法里面实现这个方法,也就是说我们在编写C/C++程序的时候所使用的方法名必须和这里的一致)。
  
  4) 编写本地方法
  
  实现和由javah命令生成的头文件里面声明的方法名相同的方法。
  
  代码2:
  
  1 #include
  2 #include "HelloWorld.h"
  3 #include
  4 JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj)
  {
  printf("Hello world!\n");
  return;
  }
  
  注意代码2中的第1行,需要将jni.h(该文件可以在%JAVA_HOME%/include文件夹下面找到)文件引入,因为在程序中的JNIEnv、jobject等类型都是在该头文件中定义的;另外在第2行需要将HelloWorld.h头文件引入(我是这么理解的:相当于我们在编写java程序的时候,实现一个接口的话需要声明才可以,这里就是将HelloWorld.h头文件里面声明的方法加以实现。当然不一定是这样)。然后保存为HelloWorldImpl.c就ok了。
  
  5) 生成动态库
  
  这里以在Windows中为例,需要生成dll文件。在保存HelloWorldImpl.c文件夹下面,使用VC的编译器cl成。
  
  cl -I%java_home%\include -I%java_home%\include\win32 -LD HelloWorldImp.c -Fehello.dll
  
  注意:生成的dll文件名在选项-Fe后面配置,这里是hello,因为在HelloWorld.java文件中我们loadLibary的时候使用的名字是hello。当然这里修改之后那里也需要修改。另外需要将-I%java_home%\include -I%java_home%\include\win32参数加上,因为在第四步里面编写本地方法的时候引入了jni.h文件。
  
  6) 运行程序
  
  java HelloWorld就ok。^_^

clq
2007-9-15 23:26:56 发表 编辑

JNI之C++调用Java类 ——java.lang.String [zt]

JNI之C++调用Java类

——java.lang.String

为什么要用C++调用Java类?很难回答,写着文章只是觉得JNI很有意思。于是开始编写一段使用VC++在Windows系统里调用java的String类,在C++里调用String类内的一些方法。

JNI已经被开发了很多年,而在我2年多的Java编程时间里从来没有接触过。直到最近研究JVM实现原理才注意到JNI。 JNI既Java Native Interface,Native这个词我见过我认为最恰当的翻译就是原生。原生的意思就是来自系统自己的,原汁原味的东西,例如Win32 API。Java类需要在虚拟机上运行,也就不是原生的,同样.NET Framework也不是原生的。JNI也就是Java原生接口。关于JNI的规范,以及为什么要使用它,它能做些什么,都在http://java.sun.com/j2se/1.4.2/docs/guide/jni/spec/jniTOC.html里记述着。

JNI是规范,它规定了虚拟机的接口,而把具体的实现留给开发者。

JVM的实现不是唯一的,目前存在很多种Java虚拟机,Sun Hotspot,IBM JDK,还有HP的,Kaffe等等。最流行的就是Sun的Hotspot,最复杂的就是IBM JDK,这是IBM的一贯作风。本文不讨论JVM的实现,只关注JNI。如果您安装了Sun的JDK,您就能在[JAVA_HOME]\include目录下找到jni.h。这个头文件就是虚拟机的唯一接口,你可以调用它声明的函数创建一个JVM。

在说明C++调用Java类之前,我想先演示一下如果编写Java Native Method。

1.编写带有Native方法的Java类

package org.colimas.jni.test;



public class JniTest {



static { System.loadLibrary("JniTestImpl"); } //JVM调用JniTestImpl.dll



public JniTest(){



}

//原生方法

public native void print(String str);

/**

* @param args

*/

public static void main(String[] args) {



JniTest test=new JniTest();

test.print("hello JVM"); //调用原生方法



}

}

2.使用javah生成c语言头文件。

javah -jni org.colimas.jni.test.JniTest

目录里多了一个org_colimas_jni_test_JniTest.h文件,打开文件,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include

/* Header for class org_colimas_jni_test_JniTest */



#ifndef _Included_org_colimas_jni_test_JniTest

#define _Included_org_colimas_jni_test_JniTest

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class: org_colimas_jni_test_JniTest

* Method: print

* Signature: (Ljava/lang/String;)V

*/

JNIEXPORT void JNICALL Java_org_colimas_jni_test_JniTest_print

(JNIEnv *, jobject, jstring);



#ifdef __cplusplus

}

#endif

#endif



其中的Java_org_colimas_jni_test_JniTest_print就是JniTest类里面的print原生方法的C语言声明。

3.编写C代码实现原生方法print

#include

#include "org_colimas_jni_test_JniTest.h" //javah生成的头文件

#include



JNIEXPORT void JNICALL Java_org_colimas_jni_test_JniTest_print

(JNIEnv *env, jobject object,jstring str)

{

//获得字符串

const char * txt=(*env)->GetStringUTFChars(env,str,0);

printf("%s\n",txt); //打印到控制台

return;

}



参数JNIEnv *env,是JNI里最重要的变量。Java.exe创建JVM,之后JVM生成一个env,该env相当于JVM内的Session,可以完成创建Java对象,调用类方法,获得类的属性等等。

在这里env将方法的参数Str从JNI的jstring类型转换为常数char数组。

4.编译

cl /Ic:\j2sdk1.4.2_10\include /Ic:\j2sdk1.4.2_10\include\win32 /c JniTestImpl.c



5.连接为DLL

link /dll JniTestImpl.obj

6.设置PATH

set PATH=C:\MyProject\Colimas\CD\JNI\MyJNI;%PATH%

7.运行

java org.colimas.jni.test.JniTest

返回结果

hello JVM

结束

以上是实现Java原生方法的开发过程,下面进入正题,使用C++调用Java的java.lang.String类。

1. Object类出创建JVM。

使用Java类之前必须要创建JVM环境。JDK由java.exe来完成。本文有Object类的静态方法BeginJVM来创建,用EndJVM来关闭。

创建JVM之后会在创建2个变量,分别是JNIEnv* env和JavaVM* jvm,JNIEnv上文已经说明,JavaVM,顾名思义,代表Java虚拟机,用它来关闭JVM。

Object类的头文件



#include "jni.h"

class Object

{

public:

static bool BeginJVM();

static bool EndJVM();

Object();

virtual ~Object();



protected:

static JNIEnv* env;

static JavaVM* jvm;

};



object.cpp代码

#include "stdafx.h"

#include "JavaClasses.h"

#include "Object.h"



Object::Object()

{}



Object::~Object()

{}



JNIEnv* Object::env=NULL;

JavaVM* Object::jvm=NULL;

//创建JVM

bool Object::BeginJVM()

{

JavaVMOption options[3];

JavaVMInitArgs vm_args;

//各种参数

options[0].optionString="-Xmx128m";

options[1].optionString="-Verbose:gc";

options[2].optionString="-Djava.class.path=.";



vm_args.version=JNI_VERSION_1_2;

vm_args.options=options;

vm_args.nOptions=3;

//创建JVM,获得jvm和env

int res = JNI_CreateJavaVM(&jvm,(void **)&env, &vm_args);

return true;

}



bool Object::EndJVM()

{

//关闭JVM

jvm->DestroyJavaVM();

return true;

}

2. C++的String类调用java.lang.String类方法

编写C++版的String类,调用java String类方法。调用的方法如下:

String replaceAll(String regex, String replacement);

boolean endsWith(String str);

int indexOf(String str);

int compareTo(String anotherString);

char charAt(int i);



String的头文件:

class String :public Object

{

public:

//与要调用的Java方法名一致。

const char * replaceAll(char *regex,char *replacement);

bool endsWith(char * str);

int indexOf(char * str);

int compareTo(char *anotherString);

char charAt(int i);

String(char *str);

virtual ~String();



};



实现:



#include "stdafx.h"

#include "String.h"

#include "jni.h"



using namespace std;

jclass clazz; //全局变量,用来传递class

jobject object; //全局变量,用来传递object

String::String(char *str)

{

jstring jstr;



if (Object::env ==NULL)

{

cout << "JVM is not created" << endl;

exit(-1);

}

//获得java.lang.String类

clazz=Object::env->FindClass("java/lang/String");

if (clazz ==0 ){

cout << "Class is not found" << endl;

exit(-1);

}

//获得String(String str)构造体

jmethodID mid= Object::env->GetMethodID(clazz,"", "(Ljava/lang/String;)V");

if (mid==0){

cerr<< "GetMethodID Error for class" << endl;

exit(-1);

}

//江字符串封装为jstring。

jstr = Object::env->NewStringUTF(str);

if (jstr == 0) {

cerr << "Out of memory" <
exit(-1);

}



cout << "invoking method" << endl;

//创建一个java.lang.String对象。

object=Object::env->NewObject(clazz,mid,jstr);



}



String::~String()

{}



char String::charAt(int i)

{

if (Object::env ==NULL)

{

cout << "JVM is not created" << endl;

exit(-1);

}

if (clazz ==0 ){

cout << "Class is not found" << endl;

exit(-1);

}

if (object ==0 ){

cout << "String object is not created" << endl;

exit(-1);

}

jmethodID mid;

//获得charAt方法,(I)C表示 参数为int型,返回char型。详细参见JNI规范

mid= Object::env->GetMethodID(clazz,"charAt", "(I)C");

if (mid==0){

cerr<< "GetMethodID Error for class" << endl;

exit(-1);

}

jint ji=i;

cout << "invoking method" << endl;

//调用charAt

jchar z=Object::env->CallCharMethod(object,mid,i);

//返回结果。

return z;



}



int String::compareTo(char *anotherString)

{

if (Object::env ==NULL)

{

cout << "JVM is not created" << endl;

exit(-1);

}

if (clazz ==0 ){

cout << "Class is not found" << endl;

exit(-1);

}

if (object ==0 ){

cout << "String object is not created" << endl;

exit(-1);

}

jmethodID mid;

//(Ljava/lang/String;)I表示参数为java.lang.String,返回int

mid= Object::env->GetMethodID(clazz,"compareTo", "(Ljava/lang/String;)I");

if (mid==0){

cerr<< "GetMethodID Error for class" << endl;

exit(-1);

}

jstring jstr = Object::env->NewStringUTF(anotherString);

cout << "invoking method" << endl;

//调用方法

jint z=Object::env->CallIntMethod(object,mid,jstr);

//返回结果

return z;

}



int String::indexOf(char *str)

{

if (Object::env ==NULL)

{

cout << "JVM is not created" << endl;

exit(-1);

}

if (clazz ==0 ){

cout << "Class is not found" << endl;

exit(-1);

}

if (object ==0 ){

cout << "String object is not created" << endl;

exit(-1);

}

jmethodID mid;



mid= Object::env->GetMethodID(clazz,"indexOf", "(Ljava/lang/String;)I");

if (mid==0){

cerr<< "GetMethodID Error for class" << endl;

exit(-1);

}

jstring jstr = Object::env->NewStringUTF(str);

cout << "invoking method" << endl;

jint z=Object::env->CallIntMethod(object,mid,jstr);



return z;

}



bool String::endsWith(char *str)

{

if (Object::env ==NULL)

{

cout << "JVM is not created" << endl;

exit(-1);

}

if (clazz ==0 ){

cout << "Class is not found" << endl;

exit(-1);

}

if (object ==0 ){

cout << "String object is not created" << endl;

exit(-1);

}

jmethodID mid;



mid= Object::env->GetMethodID(clazz,"endsWith", "(Ljava/lang/String;)Z");

if (mid==0){

cerr<< "GetMethodID Error for class" << endl;

exit(-1);

}

jstring jstr = Object::env->NewStringUTF(str);

cout << "invoking method" << endl;

bool z=Object::env->CallBooleanMethod(object,mid,jstr);



return z;

}



const char * String::replaceAll(char *regex, char *replacement)

{





if (Object::env ==NULL)

{

cout << "JVM is not created" << endl;

exit(-1);

}

if (clazz ==0 ){

cout << "Class is not found" << endl;

exit(-1);

}

if (object ==0 ){

cout << "String object is not created" << endl;

exit(-1);

}

jmethodID mid;



mid= Object::env->GetMethodID(clazz,"replaceAll", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");

if (mid==0){

cerr<< "GetMethodID Error for class" << endl;

exit(-1);

}

jvalue array[2];

jstring jreg = Object::env->NewStringUTF(regex);

jstring jstr = Object::env->NewStringUTF(replacement);

array[0].l=jreg;

array[1].l=jstr;

cout << "invoking method" << endl;

//传入参数,调用replaceAll方法

jobject z=Object::env->CallObjectMethodA(object,mid,array);

const char *result=Object::env->GetStringUTFChars((jstring)z, 0);

return (const char *)result;

}



3.测试



编写测试代码

using namespace std;



int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

int nRetCode = 0;



if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))

{

cerr << _T("Fatal Error: MFC initialization failed") << endl;

nRetCode = 1;

}

else

{

//创建JVM

Object::BeginJVM();



String test("hello");

//调用replaceAll

const char *result=test.replaceAll("l","z");

//返回结果

cout<< result <
//关闭JVM

Object::EndJVM();

}



return nRetCode;

}







4.运行

编译需要 jni.h和jvm.lib文件。

jni.h在[JAVA_HOME]\include

jvm.lib在[JAVA_HOME]\lib



运行需要jvm.dll

jvm.dll在[JAVA_HOME]\ jre\bin\client

运行结果如下:

invoking method

invoking method

hezzo

Press any key to continue



尽管本文的代码很有意思,但我还没有想到有什么价值,以及应用到实际项目中的理由。

clq
2007-9-15 23:28:53 发表 编辑

来自 http://blog.csdn.net/doomhuntercj/archive/2007/04/20/1572544.aspx

linux下 使用JNI 来以C++调用JAVA的类!(一)

决定使用JNI,实际是为了能够将通讯与调用后台的lucene索引,因此老大决定要采用这种方式来实现index的多机分布式的索引服务。接到任务,使用C++来调用Lucene的java查询的封装类。
用了java,c++各一段时间,却从未接触过JNI. 开始从网上收集该方面的资料,从头开始没有指导的时候特别郁闷,网上找到的大多数资料是讲述如何用java来调用C++的,而且多试windows下以dll方式为java提供动态库的,JAVA是宿主,而我要的是C++为宿主,linux环境下的。长期是各种环境设置,头文件查找,lib库的指定等等东西常常搞得人焦头烂额!一天时间总算摸清了怎么将虚拟机调起来(狂汗!!!),又一天时间总算摸清了如何进行调用java中的类和函数。基本可以开始JNI的程序编写里程了:)现记录自己的心得如下:
首先,来了解JNI是干什么的。Java Native Interface(JNI)是Java语言的本地编程接口,是J2SDK的一部分。在java程序中,我们可以通过JNI实现一些用java语言不便实现的功能。通常有以下几种情况我们需要使用JNI来实现。
你希望使用一些已经有的类库或者应用程序,而他们并非用java语言编写的
程序的某些部分对速度要求比较苛刻,你选择用汇编或者c语言来实现并在java语言中调用他们。
另外,也可是使用很多本地的程序来使用JAVA的类。
也就是说JNI就是跟大家进行交互的接口,其已经作为标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。
不管怎么说,JNI是规范,它规定了虚拟机的接口,而把具体的实现留给开发者。
我的环境,也就是本文的条件是:LINUX RH EL4,装了JDK1.5.0的版本。
要使用JVM来调用java的类别,首先得让java虚拟机启动起来。如何启动?C++如何来调用?
首先,得找到JNI的头文件,一般为装java的目录,如(/usr/java/jdk1.5.0/include/)里的jni.h,linux下大家要牢记G++编译的时候要加上/usr/java/jdk1.5.0/include/linux和上面两个目录,否则一大堆的undefined …! 下面帖一段我自己环境下的代码:
//创建JVM
JNIEnv* env=NULL;
JavaVM* jvm=NULL;
bool Object::BeginJVM()
{
JavaVMOption options[4];
JavaVMInitArgs vm_args;
//各种参数
options[0].optionString="-Xmx512m";
options[1].optionString="-Xms128m";
//大家注意这里为你需要使用的java类class所在的目录
options[2].optionString="-Djava.class.path=.:../lucene-core-2.0.0.jar";
options[3].optionString="-Djava.compiler=NONE";
//该地方我也不太清除,我的JDK版本是1.5.0,但好像没有JNI_VERSION_1_5
vm_args.version=JNI_VERSION_1_4;
vm_args.options=options;
vm_args.nOptions=4;

//创建JVM,获得jvm和env
int res = JNI_CreateJavaVM(&jvm,(void **)&env, &vm_args);
if(res == JNI_ERR)
{
printf("Error invoking the JVM");
return false;
}
return true;
}

bool Object::EndJVM()
{
//关闭JVM
jvm->DestroyJavaVM();
return true;
}



注意编译的时候,要加上-I指定头文件目录,-L指定libjvm.so的所在目录,如我的:
g++ -I /usr/java/jdk1.5.0/include/ -I /usr/java/jdk1.5.0/include/linux/ -L /usr/java/jdk1.5.0/jre/lib/i386/server/ -o startJVM startJVM.cpp -ljvm

这样至少可以以C/C++的方式启动了java的JVM,下篇将是实际读取,调用java中的数据和接口。

对了有些可能会碰到的出错问题:第一,libjvm.so :no such file or directory. 第二,就是N多的undefine ....的

总结下来,第一种是因为没有加入-L 指明lib所在路径,并加上-ljvm ;第二种是由于没有加入-I 指明头文件和linux下目录的头文件。

另外,在运行的时候,可能会报错cannot find shared lib:libjvm.so,问题是没有加入run_lib_directory,也就是说要设置LD_LIBRARY_PATH。设置的命令为:export LD_LIBRARY_PATH=/usr/java/jdk1.5.0/jre/lib/i386/serve

clq
2007-9-15 23:33:51 发表 编辑

JNI:如何在C++中调用Java Method?
楼主menlion(浪·星魂)2002-10-08 09:51:51 在 Java / J2SE / 基础类 提问

Java里的代码如下:
public class Mail
{
...
static public void printInfo(String str)
{
System.out.println(str);
}

native static public void invokeJavaMethod();
...
}


C++里部分代码如下:
BTW:该方法是Java里的一个native方法的实现。
/*
* Class: agent_Mail
* Method: invokeJavaMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Mail_invokeJavaMethod
(JNIEnv *env, jclass obj)
{
jmethodID mid = env->GetMethodID(obj, "printInfo", "(Ljava/lang/String;)V");
if(mid == 0)
{
printf("Can't find method printInfo!\n");
return;
}

env->ExceptionClear();

//???????
//env->MonitorEnter();
env->CallVoidMethod(obj, mid, env->NewStringUTF("Hello world!"));
}

问题1:每次运行程序的时候,C++程序都打印"Can't find method printInfo!",为什么?怎么解决?

问题2:env->MonitorEnter(),这个方法是干什么用的?完整的用法该怎么写?
问题点数:100、回复次数:8
Top

1 楼xiaosongyu(松)
回复于 2002-10-08 10:25:28 得分 0


static public void printInfo(String str)

你的java方法是static的,应该使用GetStaticMethodID来获取方法的ID
Top
2 楼menlion(浪·星魂)
回复于 2002-10-08 10:48:01 得分 0

还是不行,报java.lang.IncompatibleClassChangeError错误。:(

Top
3 楼menlion(浪·星魂)
回复于 2002-10-08 11:23:10 得分 0

这一次是这一行报错:
env->CallVoidMethod(obj, mid, env->NewStringUTF("Hello world!"));
这一行有什么错误吗?
Top
4 楼xiaosongyu(松)
回复于 2002-10-08 11:29:21 得分 0

把你调用
native static public void invokeJavaMethod();
这个函数的代码贴出来看看,OK?
Top
5 楼xiaosongyu(松)
回复于 2002-10-08 11:34:08 得分 0

呵呵,原来如此
改成CallStaticVoildMethod试试看
Top
6 楼menlion(浪·星魂)
回复于 2002-10-08 11:51:28 得分 0

我对我的愚笨实在无以言表,多谢了!
另外,你能谈谈你对

问题2:env->MonitorEnter(),这个方法是干什么用的?完整的用法该怎么写?

的看法吗?
谢谢!
Top
7 楼xiaosongyu(松)
回复于 2002-10-08 13:20:18 得分 0

MonitorEnter应该是用于线程同步的,你可以参考JNI的文档,以下是我的理解,仅供参考:
需要同步的代码在执行的时候应该锁定一个对象,在JNI代码中这样使用

jint retcode = env->MonitorEnter(env, monObj);
if(retcode==0) {
/*需要线程同步的代码在这里
......
*/

env->MonitorExit(env, monObj)
}

其中monObj是一个java对象。

关于Monitor的信息,你可以参考java语言规范
http://java.sun.com/docs/books/jls/second_edition/html/memory.doc.html#28270
或许对你有一些帮助,因为我没有用过这个函数,所以还得你自己摸索。

Top
8 楼xiaosongyu(松)
回复于 2002-10-08 13:22:23 得分 100

上面仅仅是我的理解,对错与否没有经过验证,供参考吧


总数:4 页次:1/1 首页 尾页  
总数:4 页次:1/1 首页 尾页  


所在合集/目录



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1