2012年1月1日

Java调用C/C++编写的第三方dll动态链接库(非native API)--- JNI

最近在用weka做一个数据挖掘相关的项目,不得不说,weka还是一个不错的开放源代码库,提供了很多最常用的分类和聚类算法。

在我的项目中要用到一个聚类算法,Affinity Propagation(AP),由多伦多大学的Brendan J. Frey发表于2007年。相比其他的聚类算法,AP算法的聚类结果更加准确。

在AP的官方网站公布了AP算法的动态链接库,我的目标就是实现在Java工程中调用这个动态链接库。

在网上查了资料,发现,如果仅仅是想调用Windows的Native API还是比较省事的,这里我主要针对第三方dll的调用。

下面进入正题。

 

这里主要用的方法是JNI。在网上查资料时看到很多人说用JNI非常的复杂,不仅要看很多的文档,而且要非常熟悉C/C++编程。恐怕有很多人在看到诸如此类的评论时已经决定绕道用其他方法了。但是,假如你要实现的功能并不复杂(简单的参数传递,获取返回值等等),我还是支持使用这个方法的。

Java Native Interface,简称JNI,是Java平台的一部分,可用于让Java和其他语言编写的代码进行交互。下面是从网上摘取的JNI工作示意图。

                           图1   JNI的工作模式

下面就举具体的例子说明一下使用步骤

1) 编写一个类,声明native方法

 1 public class APCluster {
2
3 public native int[] CallAPClusterDll( int arg_Int,
4 double[] arg_DoubleArray,
5 boolean arg_boolean);
6 static
7 {
8 System.loadLibrary("APClusterDllMedium");
9 }
10 }

上面是APCluster.java文件,定义了一个APCluster类,其中有一个方法CallAPClusterDll(),需要传递三种不同类型的参数,并且返回一个整型数组。

注意,这里只需要声明这个方法,并不需要实现,具体实现就在APClusterDllMedium中。

APClusterDllMedium就像中介一样,Java通过调用这个中介Dll中的CallAPClusterDll方法,间接调用真正的第三方Dll。

2)编译生成.h文件

第一步:

javac  APCluster.java 生成APCluster.class

第二步:

javah  APCluster 生成APCluster.h头文件,内容如下:

 1 /* DO NOT EDIT THIS FILE - it is machine generated */
2 #include <jni.h>
3 /* Header for class APCluster */
4
5 #ifndef _Included_APCluster
6 #define _Included_APCluster
7 #ifdef __cplusplus
8 extern "C" {
9 #endif
10 /*
11 * Class: APCluster
12 * Method: CallAPClusterDll
13 * Signature: (I[DZ)[I
14 */
15 JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll
16 (JNIEnv *, jobject, jint, jdoubleArray, jboolean);
17
18 #ifdef __cplusplus
19 }
20 #endif
21 #endif

注意,APCluster.h这个头文件的内容是不能修改的,否则JNI会找不到相对应的CallAPClusterDll()的实现。

3)创建C/C++工程,实现CallAPClusterDll()方法。

创建一个C/C++工程,工程名为APClusterDllMedium(其实,生成的dll名为APClusterDllMedium即可),导入APCluster.h这个头文件,并创建一个CPP文件,实现.h文件中的方法。

               图2   新建工程结构

由于我创建的工程是win32控制台程序,所以最后默认生成的是.exe文件,所以还要做一步工程属性修改,让它生成.dll后缀文件。

打开Project Property ->General,做以下修改:

                                                                        图3   修改工程属性

下面就是实现 JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll (JNIEnv *, jobject, jint, jdoubleArray, jboolean); 这个方法了。先贴代码再慢慢解释吧。

 1 #include "APCluster.h"
2 #include <stdio.h>
3 #include <windows.h>
4
5 #ifdef __cplusplus
6 extern "C" {
7 #endif
8
9 typedef int* (__stdcall *APCLUSTER32)(double*, unsigned int, bool);
10
11 JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll
12 (JNIEnv *env, jobject _obj, jint _arg_int, jdoubleArray _arg_doublearray, jboolean _arg_boolean)
13 {
14 HMODULE dlh = NULL;
15 APCLUSTER32 apcluster32;
16
17 if (!(dlh=LoadLibrary("apclusterwin.dll"))) //第三方DLL位置
18 {
19 printf("LoadLibrary() failed: %d\n", GetLastError());
20 }
21 if (!(apcluster32 = (APCLUSTER32)GetProcAddress(dlh, "apcluster32"))) //具体调用apcluster32方法
22 {
23 printf("GetProcAddress() failed: %d\n", GetLastError());
24 }
25
26 int m_int = _arg_int; //类型转换
27 double* m_doublearray = env->GetDoubleArrayElements(_arg_doublearray, NULL);
28 bool m_boolean = _arg_boolean;
29
30 int* ret = (*apcluster32)(m_doublearray, m_int, m_boolean); /* actual function call */
31
32 jintArray result = env->NewIntArray(_arg_int);
33 env->SetIntArrayRegion(result, 0, _arg_int, (const jint*)ret);
34
35 FreeLibrary(dlh); /* unload DLL and free memory */
36 if(ret)
37 {
38 free(ret);
39 }
40
41 return result;
42 }
43
44 #ifdef __cplusplus
45 }
46 #endif

a)首先为了#include <jni.h>,必须添加JNI所在的目录。

打开Project Property -> C/C++ -> General -> Additional Include Directories添加相应目录:

                                                         图4   添加JNI目录

b)在APCluster.h文件中自动生成的函数,只标识了函数参数类型,为了引用这些参数,自己起一个相应的名字:

JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll
(JNIEnv *env, jobject _obj, jint _arg_int, jdoubleArray _arg_doublearray, jboolean _arg_boolean) ......

c)声明函数指针,就是你要调用的第三方dll中函数的类型。

d)LoadLibrary,导入真正的第三方Dll,并找到要调用的方法的函数地址

把这个函数地址赋值给函数指针,接下来就可以通过这个函数指针调用真正的apcluster函数了!

e)类型转换:

读读jni.h文件就知道jdouble和double其实是一个东西,jboolean就是unsigned char类型,jni.h中是这么声明的:

1 typedef unsigned char    jboolean;
2 typedef unsigned short jchar;
3 typedef short jshort;
4 typedef float jfloat;
5 typedef double jdouble;

但是数组类型就没有这么简单,获取数组要使用类型相对应的env->GetTypeArrayElement(jTypeArray...)。

最后,要返回一个jint类型的数组,就要新创建一个此类型的数组,再为其赋值:

1 jintArray result = env->NewIntArray(_arg_int);
2 env->SetIntArrayRegion(result, 0, _arg_int, (const jint*)ret);

其中,_arg_int代表的是创建数组的长度。

最后return result。

4)Build这个工程。

Build,生成相应的APCluster.dll文件,将这个dll放到java工程目录下。

                            图5   将生成的dll放到java工程下

5)编写测试java程序,调用dll库。

以下为测试程序,Test.java:

 1 public class Test 
2 {
3 public static void main(String[] args)
4 {
5 double arg_doublearray[] = {0.1, 0.2, 0.3};
6 int arg_int = 3;
7 boolean arg_boolean = true;
8
9 int[] result = new APCluster().CallAPClusterDll(arg_int, arg_doublearray, arg_boolean);
10 .....
11 }
12 }

到此,java调用第三方dll就基本完成了。

本文也主要是介绍大概的操作流程,至于具体应该使用哪些API就只有去研究官方文档了。

另外还有一些需要注意的问题,比如64位的程序去调用32位的dll会报错啊等等...这些都是细节问题了。

最后,个人认为,自己动手实践还是很重要,网上都说这个复杂那个难,但是至于难还是不难,还是要实践了才知道...不能不去尝试...

(完)

posted @ 2012-01-01 22:29 AnnieKim 阅读(1603) 评论(2) 编辑

2011年12月4日

恼人的函数指针(二):指向类成员的指针

前面曾写过一篇恼人的函数指针(一),总结了普通函数指针的声明、定义以及调用,还有函数指针数组,函数指针用作返回值等。但是作为C++的研读,我发现我漏掉了一个最重要的内容,就是指向类成员的指针,这里将做相应补充(相关代码测试环境为vs 2010)。

指向类成员的指针总的来讲可以分为两大类四小类(指向数据成员还是成员函数,指向普通成员还是静态成员),下面一一做介绍:

一、指向类的普通成员的指针(非静态

1、指向类成员函数的指针

简单的讲,指向类成员函数的指针与普通函数指针的区别在于,前者不仅要匹配函数的参数类型和个数以及返回值类型,还要匹配该函数指针所属的类类型。总结一下,比较以下几点:

a)参数类型和个数

b)返回值类型

c)所属的类类型(特别之处)

究其原因,是因为非静态的成员函数必须被绑定到一个类的对象或者指针上,才能得到被调用对象的this指针,然后才能调用指针所指的成员函数(我们知道,所有类的对象都有自己数据成员的拷贝,但是成员函数都是共用的,为了区分是谁调用了成员函数,就必须有this指针,this指针是隐式的添加到函数参数列表里去的)。

明白了这点,接下来就简单了。

声明:与普通函数作为区分,指向类的成员函数的指针只需要在指针前加上类类型即可,格式为:

typedef 返回值 (类名::*指针类型名)(参数列表);

赋值:只需要用类的成员函数地址赋值即可,格式为:

指针类型名  指针名 = &类名::成员函数名;

注意:这里的这个&符号是比较重要的:不加&,编译器会认为是在这里调用成员函数,所以需要给出参数列表,否则会报错;加了&,才认为是要获取函数指针。这是C++专门做了区别对待。

调用:调用方法也很简单,针对调用的对象是对象还是指针,分别用.*和->*进行调用,格式为:

(类对象.*指针名)(参数列表);

(类指针->*指针名)(参数列表);

注意:这里的前面一对括号是很重要的,因为()的优先级高于成员操作符指针的优先级。

下面举个简单的例子就一目了然了:

 1 class A;
2 typedef void (A::*NONSTATICFUNCPTR)(int); //typedef
3
4 class A
5 {
6 public:
7 void NonStaticFunc(int arg)
8 {
9 nonStaticMember = arg;
10 cout<<nonStaticMember<<endl;
11 }
12 private:
13 int nonStaticMember;
14 };
15
16 int main()
17 {
18 NONSTATICFUNCPTR funcPtr= &A::NonStaticFunc;
19
20 A a;
21 (a.*funcPtr)(10); //通过对象调用
22
23 A *aPtr = new A;
24 (aPtr->*funcPtr)(10); //通过指针调用
25
26 return 0;
27 }

2、指向类数据成员的指针

成员函数搞懂了,数据成员也就easy了,只要判断以下两点是否一致即可:

a)数据成员类型

b)所属的类类型

另外,声明、赋值还有调用方法等这些是和前面类似的,再举个例子吧:

 1 class A;
2 typedef int (A::*NONSTATICDATAPTR); //typedef
3
4 class A
5 {
6 public:
7 A(int arg):nonStaticMember(arg){}
8 int nonStaticMember;
9 };
10
11 int main()
12 {
13 NONSTATICDATAPTR dataPtr= &A::nonStaticMember;
14
15 A a(10);
16 cout<<a.*dataPtr; //通过对象引用
17
18 A *aPtr = new A(100);
19 cout<<aPtr->*dataPtr; //通过指针引用
20
21 return 0;
22 }

运行结果,当然是各自输出10和100啦。

二、指向类的静态成员的指针

类的静态成员和普通成员的区别在于,他们是不依赖于具体对象的,所有实例化的对象都共享同一个静态成员,所以静态成员也没有this指针的概念。

所以,指向类的静态成员的指针就是普通的指针

看下面的例子就明白了:

 1 typedef const int *STATICDATAPTR;    
2 typedef int (*STATICFUNCPTR)(); //跟普通函数指针是一样的
3
4 class A
5 {
6 public:
7 static int StaticFunc() { return staticMember; };
8 static const int staticMember = 10;
9 };
10
11 int main()
12 {
13 STATICDATAPTR dataPtr = &A::staticMember;
14 STATICFUNCPTR funcPtr = &A::StaticFunc;
15
16 cout<<*dataPtr; //直接解引用
17 cout<<(*funcPtr)();
18
19 return 0;
20 }

最后注明一下,显然的,要使用(&类名::成员名)获取指向成员的指针,首先这个成员必须是对外可见的哦,即public的,不然是没有权限获取的^^。


写到此,简单总结一下就是:

1)静态的和普通的函数指针没啥区别;

2)非静态的加一个类局限一下即可。

 

不知道以后还会不会有函数指针相关的内容,先到此完结吧。

有错误欢迎指正,我会及时修改^^。

(完)

posted @ 2011-12-04 19:06 AnnieKim 阅读(1676) 评论(4) 编辑

2011年11月25日

如何获取函数的可变参数(va_list, va_start, va_arg, va_end)

最近在花时间研读C++。

函数这章讲到了函数的可变参数(ellipsis...),但是primer中讲得比较浅,提到了怎么声明怎么调用,但是没有写明在函数内部是如何获取可变的参数的。

1)省略号(ellipsis)

在无法给出所有传递给函数的参数的类型和数目时,可以使用省略号(...)指定函数参数表。有如下几种形式:

1 void fun1(int a, double b, ...); //给出确定的几个参数,其他用省略号
2 void fun2(int a ...); //省略号前有或者没有逗号都是可以的
3 void fun3(...); //也可以不确定任何参数,但和没有参数是不一样的

最典型的应用就是printf函数,printf的声明和调用方法如下:

1 int printf( const char *format [,argument]... );    //官方声明
2 printf("My name is %s, age %d.", "AnnieKim", 24); //调用

2)通用的工作原理

大多数带有可变参数的函数都利用显式声明的参数中的一些信息,来获取调用中提供的其他可选实参的类型和数目。

比如printf函数,就是根据第一个参数退到可选实参:如果第一个'%'后有一个's',说明后面要有第二个参数,类型是字符串;如果还有第二个'%',后面跟一个'd',说明还需要第三个参数,是一个整型等等。

所以说,通常情况下,第一个参数是必不可少的。

3)如何获取可变参数

现在,我们要关注的是函数内部的实现细节。当我看到primer这部分的时候还真是好奇实现细节呢,只怪我孤陋寡闻,以前没见过⊙﹏⊙b。

为了解决可变参数问题,需要用到以下几个宏(以下定义来自MSDN),并且使用这几个宏时必须至少提供一个显式的参数:

#include <stdarg.h>

type va_arg(
va_list arg_ptr,
type
);
void va_end(
va_list arg_ptr
);
void va_start(
va_list arg_ptr,
prev_param
);

其中,type是指要获取的参数的类型,比如int,char *等,arg_ptr是指向参数列表的指针(va_list类型),prev_param是指最后一个显式声明的参数,以用来获取第一个可变参数的位置。

使用步骤:

a)定义一个va_list类型的变量,变量是指向参数的指针。

b)va_start初始化刚定义的变量,第二个参数是最后一个显式声明的参数。

c)va_arg返回可变参数的值,第二个参数是该可变参数的类型。

d)va_end将a)定义的变量重置为NULL。

注意事项:

a)可变参数的类型和数目不能通过宏来获取,只能通过自己写程序控制。

b)编译器对可变参数函数的原型检查不够严格,会影响代码质量。

4)举个例子

最后举个例子,是自己写的printf函数,只能用于处理'%s'和'%d'。为简单起见,没有做任何异常处理,理解这些宏的使用方法即可。

 1 #include <iostream>
2 #include <stdarg.h>
3 using namespace std;
4
5 void myprintf(const char *format...)
6 {
7 va_list argptr;
8 va_start(argptr, format); //va_start
9
10 char ch;
11 while (ch = *(format++)) //逐个遍历format字符串
12 {
13 if (ch == '%')
14 {
15 ch = *(format++);
16 if (ch == 's')
17 {
18 char *name = va_arg(argptr, char *); //va_arg
19 cout<<name;
20 }
21 else if (ch == 'd')
22 {
23 int age = va_arg(argptr, int); //va_arg
24 cout<<age;
25 }
26 }
27 else
28 {
29 cout<<ch;
30 }
31 }
32 cout<<endl;
33 va_end(argptr); //va_end
34 }
35
36 int main()
37 {
38 myprintf("My name is %s, age %d.", "AnnieKim", 24);
39 return 0;
40 }

代码写得较粗糙,表介意。可变参数问题也就先到这里。

(完)

posted @ 2011-11-25 11:24 AnnieKim 阅读(959) 评论(2) 编辑

2011年11月22日

const限定修饰符用法总结(常量,指针,迭代器,函数参数,成员函数)

在这里总结一下const限定修饰符的各种用法,都已经过vs2010测试。

1)  声明及初始化const常量。

在声明一个const常量时,必须对其初始化,否则会报错。

1 const int constvar;         //error
2 const int constvar = 10; //ok

但是,有一个例外的情况,就是extern进来的const常量不需要初始化(如果该常量其他地方确实有出现,这时将它extern进来并且初始化反而会出错)。

1 extern const int externvar;

2)  指针。

对于指针变量有以下四种情况(这四句是不完整的,其中有些必须在声明时进行初始化,否则会出错,故未写分号^^):

a int *ptr1
b const int *ptr2
c int *const ptr3
d const int *const ptr4

a)  指向非const对象的指针。

试图将非const对象的指针指向一个常量对象将引起编译错误,即下面的语句将不能通过编译。

1 int *ptr1 = &constvar;      //error

原因很简单。如果可以编译成功,我们则可以通过类似*ptr1=100这样的语句修改constvar常量的值(由于编译器不能跟踪指针在程序中任意一点指向的对象是否为常量,所以是可以赋值成功的)。这样一来就不能称之为常量了。

b)  指向const对象的指针。

与a做对比,此时由于ptr2指向的是一个const常量,所以就可以将它赋值为const常量的地址了。

1 const int *ptr2 = &constvar;    //ok

此时,由于ptr2指向的对象是常量,所以不能通过指针修改常量的值。但是,由于指针本身不是常量,所以可以修改指针,令其指向其他常量,甚至可以令其为0。

1 *ptr2 = 100;                     //error
2 const int constvar2 = 100;
3 ptr2 = &constvar2; //ok
4 ptr2 = 0; //ok

另外,也可以令ptr2指向一个变量(非常量),但是也同样,不能通过指针修改变量的值,如下:

1 int nonconst = 1000;
2 ptr2 = &nonconst; //ok
3 *ptr2 = 10000; //error

c)  const指针,指向const或非const对象。

声明const指针时,必须同时对其进行初始化。

1 int nonconst = 1000;
2 int *const ptr3; //error
3 int *const ptr3 = &nonconst; //ok

如果,ptr3指向的是一个非const对象,可以通过ptr3修改该对象的值,但是不能修改ptr3指针本身的值,如下:

1 *ptr3 = 10000;        //ok
2 ptr3 = &constvar; //error

d)  指向const对象的const指针。

终于最后一个了。这种指针,就是把前面的几个结合起来。声明时必须初始化,指针指向的对象以及指针本身都不能修改。

3)  迭代器。

const_iterator用于遍历const容器。

1 const vector<int> constvector;
2 vector<int>::iterator it = constvector.begin(); //error
3 vector<int>::const_iterator it = constvector.begin(); //ok

const_iterator保证是以只读的方式访问容器元素,不得通过迭代器修改容器元素的值。

1 *it = 20;    //error

4)  函数参数。

参数传值还是传指针还是传引用的问题这里就不啰嗦了。

当参数为引用时,如果不希望引用参数在被调用的函数内部被修改,就可以使用const修饰符修饰引用参数。

1 void func(const int& var)
2 {
3 var = 100; //error
4 }

用const修饰传值参数是没有任何意义的,因为传值本身就是传入一个实参的副本,并不会修改实参(刚入职时,我就犯了这个低级的错误--|)。

5)  const成员函数。

在有些类的成员函数中,往往会看到在参数列表的最后有一个const修饰符,形如:

 1 class test
2 {
3 public:
4 int getVar() const
5 {
6 return var; //ok
7 }
8 void setVar(const int& aVar) const
9 {
10 var = aVar; //error
11 }
12
13 private:
14 int var;
15 };

a) 用const修饰成员函数,表明不能通过调用该成员函数修改类对象。如上例中setVar函数由于修改了test类中的var成员变量,所以不能使用const限定符修饰。

b) 对于在类外定义的成员函数,必须在成员函数定义和声明中指定关键字const,不然将被视为重载,如下:

 1 class test
2 {
3 public:
4 int getVar() const;
5 private:
6 int var;
7 };
8
9 int test::getVar() //error:overloaded member function not found in 'test'
10 {
11 return var;
12 }

c) const类对象只能调用其const成员函数(除了构造函数和析构函数,后面详解)。

 1 class Test
2 {
3 public:
4 int getVar(){ return var; }
5 private:
6 int var;
7 };
8
9 int main()
10 {
11 const Test consttest;
12 consttest.getVar(); //error
13
14 return 0;
15 }

第12行语句将报错,由于Test类并没有定义一个const版本的getVar()成员函数。

但是,这种情况有一个例外,就是构造函数和析构函数。即使构造函数和析构函数不是const的,const类对象也同样可以调用它们。所以一个const类对象“从构造完成时刻到析构开始时刻”这段时间被认为是const的(尝试将构造函数写为const,不过报错了)。

d) 如果一个类含有指针,那么在const成员函数中还是可以修改指针所指的对象。其实想想这句话也比较好理解,指针的值是不能修改的,但是指针所指的对象是可以修改的。

 

先总结到这里,应该还漏掉不少使用方法,这其中也可能有不少错误或者笔误。欢迎补充并指正。^^

(完)

posted @ 2011-11-22 23:04 AnnieKim 阅读(1302) 评论(1) 编辑

2011年11月20日

恼人的函数指针(一)

这篇是为了加深记忆所写。发现,很多知识若不经过反复的琢磨和动手实践,是很难记得住的。

1)  函数指针的初始化

函数如下:

1 int CompareString(const string& str1, const string& str2)
2 {
3     return str1.compare(str2);  

4 }

函数的初始化有两种方式:

第一种,也是最普遍的方式:

1 int (*CompareFunction)(const string&, const string&) = CompareString;

第二种,是使用typedef定义函数类型,这种写法有助于对代码的理解:

1 typedef int (*CompareFunctionType)(const string&, const string&);
2 CompareFunctionType CompareFunction = CompareString;

2)  函数指针赋值。

函数名可以理解为该类型函数的指针。当然,取地址操作符作用于函数名上也能产生指向该类型函数的指针。也就是说下面两种赋值都是可行的:

1 CompareFunctionType CompareFunction = CompareString;
2 CompareFunctionType CompareFunction = &CompareString;

3)  函数调用。

无论是用函数名调用,还是用函数指针调用,还是用显式的指针符号调用,其写法是一样的:

1 CompareString("abc""cba");
2 CompareFunction("abc""cba");
3 (*CompareFunction)("abc""cba");

4)  函数指针的数组。

对于函数指针的数组,强烈建议使用typedef方式定义类型之后再使用,不然影响代码的阅读性,继续以以上例子为例:

1 //without typedef
2 int (*CompareFunctionArray[3])(const string&, const string&);
3 //with typedef
4 CompareFunctionType CompareFunctionTypeArray[3];

5)  函数指针用做函数返回值的类型。

到这一步,会发现typedef是多么的好用了。不然我是完全读不懂下面语句的意思的:

1 //without typedef
2 int (*func(int*, int))(const string&, const string&);

上面的声明,将func(int*, int)声明为一个函数,返回值为函数指针,函数类型为int (*)(const string&, const string&)。

多么的晦涩啊!

如果写成typedef就不用这么纠结了,足见typedef的作用:

1 CompareFunctionType func(int*, int);

6)  指向extern "C"函数的指针。

《C++ primer 3》中有指出,指向C函数的指针和指向C++函数的指针类型不同,但是现在的很多编译器都有语言扩展,认为这两种函数的指针具有相同的特性。

所以,我在vs 2010中做了尝试,结果证明是支持这种语言扩展的。

函数声明如下:

1 extern "C" int InsideFunctionC(const string& str1, const string& str2)
2 {
3     return str1.compare(str2);
4 }
5 
6 int InsideFunctionCPlusPlus(const string& str1, const string& str2)
7 {
8     return str1.compare(str2); 

9 }

函数指针的初始化和调用,允许赋值为指向C函数的指针:

1 int (*CompareFunction)(const string&, const string&) = InsideFunctionC;

另外还有一点,当extern "C"应用在一个声明上时,所有被它声明的函数都将受到影响。举个例子:

1 extern "C" void OutSideFunction(int (*fc)(const string&, const string&))
2 {
3     cout<<fc("abc""cba")<<endl;;
4 }

这里的OutSideFunction和fc都将受到extern "C"的影响,但是vs2010编译器是支持一个指向C++函数的指针作为OutSideFunction的参数。如下:

1 int main()
2 {
3     OutSideFunction(InsideFunctionC);
4     OutSideFunction(InsideFunctionCPlusPlus);
5     
6     return 0

7 }

到此就差不多了。昨天看了一遍,今天又写博客温习了一遍,应该算是加深记忆了。傻笑一个。

posted @ 2011-11-20 10:36 AnnieKim 阅读(1292) 评论(1) 编辑

2011年5月22日

关于构造方法的一个有趣的问题:初始化队伍

摘要: 下面这段代码是有问题的,问题当然是关于构造方法: 1: class A 2: { 3: private: 4: int i; 5: int j; 6: public: 7: A ( int val ) 8: :j ( val ), i ( j ) 9: {} 10: 11: void print(){ 12: cout<<i<<" "<<j<<endl; 13: } 14: }; 15: 16: int main() 17: { 18: A a(1...阅读全文

posted @ 2011-05-22 15:19 AnnieKim 阅读(215) 评论(0) 编辑

2011年5月17日

笔试题之三:C++ dynamic_cast问题

摘要: 题目:已知下面的class层次,其中每一个class都定义有一个default constructor和一个virtual destructor:class X {......};class A {......};class B : public A {......};class C : public B {......};class D : public X, public C {......}; 下面哪一个dynamic_cast会失败?A) D *pd= new D; A *pa = dynamic_cast< A* >(pd);B) A *pa = new C; C *pc阅读全文

posted @ 2011-05-17 11:07 AnnieKim 阅读(794) 评论(3) 编辑

2011年5月16日

笔试题之二:函数参数入栈问题

摘要: 1voidout(char*ch,inti)2{3cout<<ch<<","<<i<<endl;4}56intmain()7{8vector<char*>vec;9vec.push_back("str1");10vec.push_back("str2");1112inti=0;13out("---begin---",i);1415while(i<vec.size())16{17out(vec[i],i++);18}1920out("----阅读全文

posted @ 2011-05-16 20:30 AnnieKim 阅读(193) 评论(0) 编辑

笔试题之一:函数的指针类型参数的引用问题

摘要: void func(int *ptr, int &value){ ptr = &value; }int main(){ int i = 10, j = 5; int *ptr = &i; func( ptr, j); printf("%d", *ptr); return 0;}请问,此时输出来的*ptr的值是多少?5还是10?答案是10。这里主要涉及到函数参数问题,同int类型一样,指针也存在引用的问题。如果上面的函数声明改为:void func (int* &ptr, int &value);则答案就是5了。阅读全文

posted @ 2011-05-16 09:23 AnnieKim 阅读(126) 评论(0) 编辑