不知道有没有人喜欢用ctypes,反正我是不大喜欢的。用了ctypes之后程序会显得很凌乱——为了调用c/c++写的dll,动不动c_int或者c_char_p,要是碰上dll里各种用结构体或者数组或者指针的话,那更是悲剧了。想象下先写个c_char_p,然后再pointer(),然后组合起来一个struct,再排出个数组,最后还要获得其指针?

简直就是个悲剧,好了,吐槽完毕,下面说正经的,有一个数据采集仪,提供一个dll方便我们取数据,VC6.0下编译的,某个函数在文档中给出原型如下:

  1. // 功能描述:获取传感器的测量值。   
  2. // 输入参数:pSensorVal,用户分配的用来保存传感器值的数组首地址;   
  3. //           pSensorCount,用户分配的数组长度;   
  4. // 输出参数:pSensorVal,保存采集到的传感器的值;   
  5. //           pSensorCount,分析仪中的传感器数,如果分析仪中的传感器数大于用户指定的个数,则只返回用户指定个数的传感器值。   
  6. // 返 回 值:成功返回1;失败返回负数;   
  7. //           -1:获取失败   
  8. FBGA_DR_API int FBGA_GetSensorVal (SSensorVal *pSensorVal, int *pSensorCount);  

居然是结构体(其实应该是结构体数组,这里看不出来)的指针,虽然难受但是也是很好写的:

  1. class SSensorVal(Structure):   
  2.     _fields_ = [('nSensorNumber', c_ushort),   
  3.                 ('fWaveLen', c_float),   
  4.                 ('fPhyVal', c_double)]  

把数据结构都做好之后,相应的测试函数,却出了问题,在调用其连接函数:

  1. // 功能描述:与分析仪建立TCP连接。   
  2. // 输入参数:strServerIP,分析仪服务端IP地址;   
  3. //           nServerPort,分析仪服务端TCP端口号;   
  4. // 返回值:  成功返回1,失败返回负值。   
  5. //           -1:SOCKET错误   
  6. //           -2:连接服务端失败;   
  7. FBGA_DR_API int FBGA_Connect(char * strServerIP=“127.0.0.1″int nServerPort=9100);  

居然返回了错误——“ValueError: Procedure probably called with too many arguments”。我严格按照文档做的,居然报这种错误?于是拿C和C#测试了下dll,发现都能正常调用,为什么Python就不可以?

经过反复测试,发现调用WinDLL调用失败,用CDLL就可以,以前没关注过这两个的区别,在Windows都是调用WindDLL的,原来WinDLL使用stdcall调用,CDLL使用cdecl调用,二者在清除栈的时候有区别。

从源头讲,大概是这个意思:

在C语言中,假设我们有这样的一个函数:

int function(int a,int b)

调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机用栈来支持参数传递。

函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。

在参数传递中,有两个很重要的问题必须得到明确说明:

当参数个数多于一个时,按照什么顺序把参数压入堆栈;
函数调用后,由谁来清空堆栈;
在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:

stdcall
cdecl
fastcall
thiscall
naked call

那么stdcall和cdecl有什么区别呢?二者的压栈顺序是相同的,但是当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除?如果我 们的函数使用了cdecl,那么栈的清除工作是由调用者,如果用了stdcall,那么有函数自己清除。谁更可靠些呢?不同的编译器产生栈的方式不 尽相同,那么调用者能否正常的完成清除工作并不能确定,所以谁做的这个栈谁来清除是比较稳定的,也就是stdcall更可靠些,那为什么还要保留cdecl?当我们遇到这样的函数如 fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用 _cdecl。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

你可以管理本篇文章的订阅。

Post Navigation