CLisp 28:调用C程序详细指导

来源:互联网 发布:淘宝购物车加不进去 编辑:程序博客网 时间:2024/05/22 03:07

       首先,介绍一下快速编译C代码生成DLL的方法,使用命令行。VC自带的工具Visual Studio .NET Tools > Visual Studio .NET 2003 Command Prompt。在这里可以使用程序cl.exe,在普通命令行窗口中用不了。因为是学习,为了方便只使用一个C文件,取名lisp.c,生成lisp.dll。命令很简单 cl.exe /LD lisp.c /LD 表示生成Release版本的DLL文件,如果要定义一些宏,可以用 /DMacroName=Value,定义多个宏时写多个/D指令即可,这里用不上。

 

使用变量或函数,都需要C中导出,然后在LISP中导入。导出很简单,在变量或函数前加上__declspec(dllexport)前缀即可。例如:

       __declspec(dllexport) int x;

       __declspce(dllexport) int add(int x, int y) { return x + y;}

 

       使用基本类型,这些基本类型看名称就知道什么意思:

       nil 就是 void

       boolean 对应于Cint类型

       character 是字符类型

       char uchar short ushort int uint long ulongLISP内部都是integer

       uint8 sint8 uint16 sint16 uint32 sint32 uint64 sint64是确定位数的,推荐使用

       single-float对应float,单精度浮点数

double-float对应double,双精度浮点数

 

以浮点数为例,介绍如何使用基本类型,C代码:

__declspec(dllexport)

double x = 0.0;

__declspec(dllexport)

double getAndSet(double d)

{

       double temp = x;

       x = d;

       return temp;

}

 

LISP代码,导入变量x和函数getAndSet,各参数的作用一看就明白。因为LISP对大小写不敏感,而C语言对大小写敏感,所以最好指定:name参数,表明其在C语言中的名称。

(use-package :ffi)

(setq dll "d:\\MyProjects\\teststh\\lisp.dll")

(default-foreign-language :stdc)

(def-c-var x (:type double-float) (:library dll))

(def-call-out getAndSet (:name “getAndSet”)

(:arguments (d double-float))

(:return-type double-float)

(:library dll))

(getAndSet 3.14159d0)返回0.0d0,再查看x的值返回3.14159d0,也可以用(setf x 2.71828d0)改变x的值。简单类型使用起来还是很方便的。

 

       导入变量时,LISP会自动打开并锁定DLL文件,使得你不能重新编译。可以在LISP中强行关闭DLL文件,执行(ffi:close-foreign-library “file name of dll”),返回#<INVALID FOREIGN-POINTER #x10000000>,意思是LISP中指向这个DLL的句柄变成无效指针了。修改C代码后重新编译,在LISP中无需重新导入变量或函数,只要使用变量或函数,就会自动重新打开DLL文件。当然,也可以主动调用(ffi:open-foreign-library “file name of dll”)打开DLL文件,返回#<FOREIGN-POINTER #x10000000>。如果DLL文件不存在,打开操作不会返回NIL,而是抛出一个异常。重复打开,或重复关闭,不会出错,第二次调用的返回和第一次是一样的。

 

       接下来介绍如何使用数组,当然是简单类型的数组。在LISP中使用C语言的数组,或者将LISP的数组作为函数参数传给C语言,或者C语言将数组作为返回值。

       C语言的代码:

__declspec(dllexport)

int arr[10];

__declspec(dllexport)

int sum1(int a[10])

{

       int s = 0;

       int i;

       for (i = 0; i < 10; i++)

              s += a[i];

       return s;

}

 

       LISP语言的代码,在基本类型外部包一层c-array就得到数组类型。(c-array type N)相当于 type[N](c-array type (N M L))相当于 type[N][M][L]

       (def-c-var arr (:type (c-array int 10)) (:library dll))

       查看arr的值返回#(0 0 0 0 0 0 0 0 0 0)。修改元素的值(setf (element arr 9) 9)elementFFI提供的函数。修改元素值时必须用element获取元素,不能使用aref。查看元素的值,可以用element,也可以用aref,例如(aref arr 9)返回数字9

       (def-call-out sum1 (:return-type int)

              (:arguments (a (c-ptr (c-array int 10)))) 

              (:library dll))

(setq arrLisp #(1 2 3 4 5 6 7 8 9 10))

(sum1 arrLisp)返回55

注意,参数类型前面包装了c-ptr,表示是一个指针。如果不加c-ptr会是什么样子的呢,LISP会将数组中的每个参数压入堆栈中。相应的C代码应该这样写,不解释了:

__declspec(dllexport)

int sum2(int a1)

{

       int * ptr = &a1;

       int s = 0;

       int i;

       for (i = 0; i < 10; i++)

              s += ptr[i];

       return s;

}

 

如果在C语言中修改传入的数组元素,会不会生效?修改sum1的代码,在函数内修改a[i]的值,发现没有效果。把参数改成:in-out模式试一下,其中一行改成(:arguments (a (c-ptr (c-array int 10)) :in-out)),再执行sum1,返回两个值,第一个是求和结果55,第二个是长度为10的数组,也就是修改后的数组,但是LISP里的作为输入参数的数组没有改变。

[16]> (sum1 arrLisp)

55 ;

#(2 4 6 8 10 12 14 16 18 20)

[17]> arrLisp

#(1 2 3 4 5 6 7 8 9 10)

 

使用定长数组并不是编程的好习惯,通用做法是输入数组长度和首地址。

C语言代码:int sum3(int size, int * arr);

       (def-call-out sum1 (:return-type int)

              (:arguments (size int) (arr (c-array-ptr int)))

              (:library dll))

       (sum3 (length arrLisp) arrLisp)

 

接下来看看怎么使用字符串,字符串和数组的本质是一样的。LISPFFI中,c-string就是字符串类型。

1,字符串作为输入参数:

__declspec(dllexport)

void cprint(char * str)  {  puts(str);  }

(def-call-out cprint

       (:arguments (str c-string))

       (:library dll))

(cprint “how do you do?”)

2,字符串常量作为返回值:

__declspec(dllexport)

char const * askName() { return “My name is HanMeiMei.”; }

(decl-call-out askName (:name “askName”)

       (:return-type c-string)

       (:library dll))

(askName) > “My name is HanMeiMei.”

3,字符串作为输入参数和返回值:

__declspec(dllexport)

char * upCase(char * str)

{

       static char buffer[128];

       char * pch = buffer;

       while (*str != 0)

       {

              if ('a' <= *str && *str <= 'z')

                     *pch = *str - 'a' + 'A';

              else

                     *pch = *str;

              ++pch;

              ++str;

       }

       *pch = 0;

       return buffer;

}

(def-call-out upCase (:name “upCase”)

       (:return-type c-string)

       (:arguments (str c-string))

       (:library dll))

(setq x (upCase “just do it!”))

(setq y (upCase “Good work!”))

在例3中,函数upCase每次都返回相同的内存地址,但没有出问题,因为LISP在取结果时会重新分配内存。这里没有动态分配内存,因为这是一个很复杂的问题,下一次介绍。

原创粉丝点击