本文目錄一覽:
python 是否有能列出動態鏈接庫中有哪些方法的庫
最近看了《Gray hat python》一書,這才知道為什麼python是黑客必學的編程語言。通過python的ctypes模塊,可以直接調用動態鏈接庫中的導出函數,而且甚至可以直接在python中構建出複雜的C結構體!!!使得python也具備了底層內存操作的能力,再配合python本身強大的表達能力,能不讓人激動么。
之前為了在python中調用動態鏈接庫導出的函數,你需要自行解析出這些導出函數的地址。而現在ctypes庫會替我們完成這個麻煩的過程,大大方便了我們直接在python中調用C函數的能力。
ctypes模塊中有三種不同的動態鏈接庫載入方式:cdll, windll, oledll。不同之處在於鏈接庫中的函數所遵從的函數調用方式(calling convention)以及返回方式有所不同。
cdll用於載入遵循cdecl標準函數調用約定的鏈接庫。windll則用於載入遵循stdcall調用約定的動態鏈接庫。oledll與windll完全相同,只是會默認其載入的函數會統一返回一個Windows HRESULT錯誤編碼。
先複習一下有關函數調用約定的知識:函數調用約定指的是函數參數入棧的順序、哪些參數入棧、哪些通過寄存器傳值、函數返回時棧幀的回收方式(是由調用者負責清理,還是被調用者清理)、函數名稱的修飾方法等等。基本上我們最常見的調用約定就是cdecl和stdcall兩種。在《程序員的自我修養–鏈接、裝載與庫》一書的第10章有對函數調用約定的更詳細介紹。
cdecl規定函數參數列表以從右到左的方式入棧,且由函數的調用者負責清除棧幀上的參數。stdcall的參數入棧方式與cdecl一致,但函數返回時是由被調用者自己負責清理棧幀。而且stdcall是Win32 API函數所使用的調用約定。OK,就這麼多,夠了。
測試一下在Linux平台和Windows平台下通過ctypes模塊導入C庫中函數的小例子:
Windows 下:
from ctypes import *
msvcrt = cdll.msvcrt
msg = “Hello world!\n”
msvcrt.printf(“Testing: %s”, msg)
Linux下:
from ctypes import *
libc = CDLL(“libc.so.6”)
msg = “Hello, world!\n”
libc.printf(“Testing: %s”, msg)
可以看到動態鏈接庫中的printf被直接導入到python中來調用了。
那麼,在python中怎麼表示C的類型?不用擔心,下面這張表就能搞定。
有了這個映射關係,多複雜的C類型也能在python中表達出來。
在C中定義一個聯合:
union
{
long barley_long;
int barley_int;
char barley_char[8];
}barley_amount;
而在python中同等的定義為:注意一下python中定義數組的方式。
class barley_amount(Union):
_fields_ = [
(“barley_long”, c_long),
(“barley_int”, c_int),
(“barley_char”, c_char * 8),
]
測試一下這個例子,在python中定義一個聯合體,為其賦值,再分別訪問其成員。
from ctypes import *
class barley_amount(Union):
_fields_ = [
(“barley_long”, c_long),
(“barley_int”, c_int),
(“barley_char”, c_char * 8),
]
value = raw_input(“Enter the amount of barley to put into the beer vat:”)
my_barley = barley_amount(int(value))
print “Barley amount as a long: %ld” % my_barley.barley_long
print “Barley amount as an int: %d” % my_barley.barley_int
print “Barley amount as a char: %s” % my_barley.barley_char
請教python調用dll動態庫的傳參問題
第一步,我先從簡單的調用出發,定義了一個簡單的函數,該函數僅僅實現一個整數加法求和:
LIBEXPORT_API int mySum(int a,int b){ return a+b;}
C# 導入定義:
public class RefComm
{
[DllImport(“LibEncrypt.dll”,
EntryPoint=” mySum “,
CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
public static extern int mySum (int a,int b);
}
在C#中調用測試:
int iSum = RefComm.mySum(,);
運行查看結果iSum為5,調用正確。第一步試驗完成,說明在C#中能夠調用自定義的動態鏈接庫函數。
第二步,我定義了字元串操作的函數(簡單起見,還是採用前面的函數名),返回結果為字元串:
LIBEXPORT_API char *mySum(char *a,char *b){sprintf(b,”%s”,a); return a;}
C# 導入定義:
public class RefComm
{
[DllImport(“LibEncrypt.dll”,
EntryPoint=” mySum “,
CharSet=CharSet.Auto,
CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, string b);
}
在C#中調用測試:
string strDest=””;
string strTmp= RefComm.mySum(“45”, strDest);
運行查看結果 strTmp 為”45″,但是strDest為空。我修改動態鏈接庫實現,返回結果為串b:
LIBEXPORT_API char *mySum(char *a,char *b){sprintf(b,”%s”,a) return b;}
修改 C# 導入定義,將串b修改為ref方式:
public class RefComm
{
[DllImport(“LibEncrypt.dll”,
EntryPoint=” mySum “,
CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, ref string b);
}
在C#中再調用測試:
string strDest=””;
string strTmp= RefComm.mySum(“45”, ref strDest);
運行查看結果 strTmp 和 strDest 均不對,含不可見字元。再修改 C# 導入定義,將CharSet從Auto修改為Ansi:
public class RefComm
{
[DllImport(“LibEncrypt.dll”,
EntryPoint=” mySum “,
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, string b);
}
在C#中再調用測試:
string strDest=””;
string strTmp= RefComm. mySum(“45”, ref strDest);
運行查看結果 strTmp 為”45″,但是串 strDest 沒有賦值。第二步實現函數返回串,但是在函數出口參數中沒能進行輸出。再次修改 C# 導入定義,將串b修改為引用(ref):
public class RefComm
{
[DllImport(“LibEncrypt.dll”,
EntryPoint=” mySum “,
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, ref string b);
}
運行時調用失敗,不能繼續執行。
第三步,修改動態鏈接庫實現,將b修改為雙重指針:
LIBEXPORT_API char *mySum(char *a,char **b){sprintf((*b),”%s”,a); return *b;}
C#導入定義:
public class RefComm
{
[DllImport(“LibEncrypt.dll”,
EntryPoint=” mySum “,
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern string mySum (string a, ref string b);
}
在C#中調用測試:
string strDest=””;
string strTmp= RefComm. mySum(“45”, ref strDest);
運行查看結果 strTmp 和 strDest 均為”45″,調用正確。第三步實現了函數出口參數正確輸出結果。
第四步,修改動態鏈接庫實現,實現整數參數的輸出:
LIBEXPORT_API int mySum(int a,int b,int *c){ *c=a+b; return *c;}
C#導入的定義:
public class RefComm
{
[DllImport(“LibEncrypt.dll”,
EntryPoint=” mySum “,
CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]
public static extern int mySum (int a, int b,ref int c);
}
在C#中調用測試:
int c=0;
int iSum= RefComm. mySum(,, ref c);
運行查看結果iSum 和c均為5,調用正確。
經過以上幾個步驟的試驗,基本掌握了如何定義動態庫函數以及如何在 C# 定義導入,有此基礎,很快我實現了變長加密函數在 C# 中的調用,至此目標實現。
三、結論
在 C# 中調用 C++ 編寫的動態鏈接庫函數,如果需要出口參數輸出,則需要使用指針,對於字元串,則需要使用雙重指針,對於 C# 的導入定義,則需要使用引用(ref)定義。
對於函數返回值,C# 導入定義和 C++ 動態庫函數聲明定義需要保持一致,否則會出現函數調用失敗。定義導入時,一定注意 CharSet 和 CallingConvention 參數,否則導致調用失敗或結果異常。運行時,動態鏈接庫放在 C# 程序的目錄下即可,我這裡是一個 C# 的動態鏈接庫,兩個動態鏈接庫就在同一個目錄下運行。
python怎麼調用c的動態鏈接庫
Python調用C/C++動態鏈接庫的需求
在自動化測試過程中,難免會遇到語言混合使用的情況,這不,我們也遇到了。初步決定採用Robot Framework作為自動化測試框架後,其支持Java和Python,而Python作為主流的語言,怎麼能放棄使用它的機會^_^。 然而產品採用是古老90年代開發的C/S結構,因為古老,當時也沒有考慮到對產品的測試進行自動化,Client端並沒有預留CLI(Command Line interface)形式的介面,真是雪上加霜啊。
那怎麼自動化?採用AutoIT來對客戶端界面進行自動化測試?可惜AutoIT對當初開發採用的控制項識別不是很好,如果採用控制項所在位置來進行控制的方式,又會導致自動化測試並不是很穩定。那麼!!!只有自己開發介面了,目前在Client端開發出CLI形式的介面,將其封裝為DLL,然後在Robot FrameWork框架中採用Python對DLL進行調用。任務艱巨哪!
Python調用DLL例子
示例一
首先,在創建一個DLL工程(本人是在VS 2005中創建),頭文件:
[cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片//hello.h
#ifdef EXPORT_HELLO_DLL
#define HELLO_API __declspec(dllexport)
#else
#define HELLO_API __declspec(dllimport)
#endif
extern “C”
{
HELLO_API int IntAdd(int , int);
}
CPP文件:
[cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片//hello.cpp
#define EXPORT_HELLO_DLL
#include “hello.h”
HELLO_API int IntAdd(int a, int b)
{
return a + b;
}
這裡有兩個注意點:
(1)弄清楚編譯的時候函數的調用約定採用的__cdecl還是__stdcall,因為根據DLL中函數調用約定方式,Python將使用相應的函數載入DLL。
(2)如果採用C++的工程,那麼導出的介面需要extern “C”,這樣python中才能識別導出的函數。
我的工程中採用__cdecl函數調用約定方式進行編譯鏈接產生hello.dll,然後Python中採用ctypes庫對hello.dll進行載入和函數調用:
[python] view plain copy 在CODE上查看代碼片派生到我的代碼片from ctypes import *
dll = cdll.LoadLibrary(‘hello.dll’);
ret = dll.IntAdd(2, 4);
print ret;
OK,一個小例子已經完成了,如果你感興趣,但還沒試過,那就嘗試一下吧。
示例二
示例一隻是一個”hello world”級別的程序,實際運用中更多的需要傳遞數據結構、字元串等,才能滿足我們的需求。那麼這個示例將展示,如何傳遞數據結構參數,以及如何通過數據結構獲取返回值。
首先編寫DLL工程中的頭文件:
[cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片//hello.h
#ifdef EXPORT_HELLO_DLL
#define HELLO_API __declspec(dllexport)
#else
#define HELLO_API __declspec(dllimport)
#endif
#define ARRAY_NUMBER 20
#define STR_LEN 20
struct StructTest
{
int number;
char* pChar;
char str[STR_LEN];
int iArray[ARRAY_NUMBER];
};
extern “C”
{
//HELLO_API int IntAdd(int , int);
HELLO_API char* GetStructInfo(struct StructTest* pStruct);}
CPP文件如下:
[cpp] view plain copy 在CODE上查看代碼片派生到我的代碼片//hello.cpp
#include string.h
#define EXPORT_HELLO_DLL
#include “hello.h”
HELLO_API char* GetStructInfo(struct StructTest* pStruct){
for (int i = 0; i ARRAY_NUMBER; i++)
pStruct-iArray[i] = i;
pStruct-pChar = “hello python!”;
strcpy (pStruct-str, “hello world!”);
pStruct-number = 100;
return “just OK”;
}
GetStructInfo這個函數通過傳遞一個StructTest類型的指針,然後對對象中的屬性進行賦值,最後返回”just OK”.
編寫Python調用代碼如下,首先在Python中繼承Structure構造一個和C DLL中一致的數據結構StructTest,然後設置函數GetStructInfo的參數類型和返回值類型,最後創建一個StructTest對象,並將其轉化為指針作為參數,調用函數GetStrcutInfo,最後通過輸出數據結構的值來檢查是否調用成功:
[python] view plain copy 在CODE上查看代碼片派生到我的代碼片from ctypes import *
ARRAY_NUMBER = 20;
STR_LEN = 20;
#define type
INTARRAY20 = c_int * ARRAY_NUMBER;
CHARARRAY20 = c_char * STR_LEN;
#define struct
class StructTest(Structure):
_fields_ = [
(“number”, c_int),
(“pChar”, c_char_p),
(“str”, CHARARRAY20),
(“iArray”, INTARRAY20)
]
#load dll and get the function object
dll = cdll.LoadLibrary(‘hello.dll’);
GetStructInfo = dll.GetStructInfo;
#set the return type
GetStructInfo.restype = c_char_p;
#set the argtypes
GetStructInfo.argtypes = [POINTER(StructTest)];objectStruct = StructTest();
#invoke api GetStructInfo
retStr = GetStructInfo(byref(objectStruct));#check result
print “number: “, objectStruct.number;
print “pChar: “, objectStruct.pChar;
print “str: “, objectStruct.str;
for i,val in enumerate(objectStruct.iArray):
print ‘Array[i]: ‘, val;
print retStr;
總結
1. 用64位的Python去載入32位的DLL會出錯
2. 以上只是些測試程序,在編寫Python過程中儘可能的使用”try Except”來處理異常3. 注意在Python與C DLL交互的時候位元組對齊問題4. ctypes庫的功能還有待繼續探索
原創文章,作者:簡單一點,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/130220.html