ctypes學習歷程

ctypes筆記

概述

之前要調用海康的sdk,嘗試過使用ctypes,但是自己沒有潛心學習,以爲這東西不好用。等最近看arcface的pythondemo的時候,發現都是用ctypes做的接口,感動的淚流滿面。。。。

數據基礎

先講數據類型的問題。大多數不知道怎麼處理的問題其實都在這裏。
具體的數據類型在linux和windows上可能有不同,比如c_int在不同的系統上可能長度都不同。
如果遇到不知道是啥的數據類型,先百度,比如DWORD類型,百度一下就知道其實是unsigned int32,所以我們使用c_uint32就ok。

基礎數據類型

ctypes類型 C類型 Python類型 備註
c_bool _Bool bool -
c_char char 1個字符的字節對象 -
c_wchar wchar_t 1個字符的字符串 -
c_byte char int -
c_ubyte unsigned char int -
c_short short int -
c_ushort unsigned short int -
c_int int int -
c_uint unsigned int int -
c_long long int -
c_ulong unsigned long int -
c_longlong __int64 或者 long long int -
c_ulonglong unsigned __int64 或者 unsigned long long int -
c_size_t size_t int -
c_ssize_t ssize_t 要麼 Py_ssize_t int -
c_float float float -
c_double double float -
c_longdouble long double float -
c_char_p char * ((NUL terminated)) bytes對象或 None -
c_wchar_p wchar_t * ((NUL terminated)) 字符串或 None -
c_void_p void * int或 None 通用指針,不指定數據類型,使用指針內容的時候需要強制轉換。

日常中,除了以上類型,還有一些值得注意的地方:一般情況下數據傳遞通過結構體。而結構體很多時候都使用的是指針進行傳遞數據。數據傳遞主要分爲兩種:文本數據和二進制數據。
文本數據很簡單,一般不會有指針, 即使有,使用的方式也是POINTER(c_int)大概這樣的結構。如果是結構體指針,先定義結構體,再使用指針。
二進制數據:包括從內存讀取數據和通過指針傳遞二進制數據。

  1. 知道文件地址,從內存讀取數據。
    假設,我們知道某個指針指向一段內存,也知道這段內存是什麼數據結構,可以通過如下方式將數據結構化:
#從二進制獲取數據
import ctypes
#x_id爲某個指定的地址,該數據爲結構體BASE_STRUCT
base=BASE_STRUCT()
ctypes.memmove(base,x_id,len(base))#從內存讀取結構體數據

通過如上方法就可從內存地址加載該結構體了.這裏要注意一個很重要的問題,結構體的創建者。結構體在c++或python裏面創建的是不一樣的。python對結構體進行了翻譯,但並不是創建了c++的結構體。所以:該方法僅適用於python創建的結構體。(c++創建的結構體字段長度比python的更多,因爲python創建的結構體只有數據,沒有基礎的struct包含的其他東西,當然不知道c語言裏面是不是也是這樣。。)

#include <iostream>
#define DLLEXPORT extern "C" __declspec(dllexport)
	typedef struct Sa {
		char x;
		int y;
		char *z[10];
	}TEST_STRUCT;
	DLLEXPORT void *w(TEST_STRUCT *tt){
		
		tt->y = -101;
		std::cout<<"c:"<< sizeof(tt)<<"\n";
		//std::cout << "cs:" << sizeof(test_struct) << "\n";
		return (void *)tt;
	}
	

對應的python代碼

from ctypes import *
dll = CDLL('Project1.dll')
dllc=cdll.msvcrt#c語言自帶的一些方法


class TEST_STRUCT(Structure):
    _fields_=[
        ("x",c_char),
        ("y",c_int),
        ("z",c_char*10)
    ]

www=TEST_STRUCT()
id=dll.w
id.restype=c_void_p
id.argtypes=(POINTER(TEST_STRUCT),)
w2=TEST_STRUCT()
w2.y=-101
print("w2-size:",sizeof(w2))
res=id(byref(www))
print(res)
print("p:",sizeof(w2))
memmove(addressof(w2),res,sizeof(w2))
print("w2:",w2.y)
print(www.y)

數據回收問題

在ctypes裏面,不要對ctypes的數據進行地址比對判斷是否爲同一對象,要比較也要比較指針的值。因爲ctypes每次調用返回的對象的地址都是不一樣的。

函數調用

函數調用根據垃圾回收機制主要分爲4種調用方式:CDLL,OleDLL,WinDLL,PyDLL。
CDLL:這些庫中的函數使用標準C調用約定,並假定返回 int。(即垃圾回收由c++端進行)
OleDLL:僅限Windows。此類的實例表示已加載的共享庫,這些庫中的函數使用stdcall調用約定,並假定返回特定於Windows的HRESULT代碼。 HRESULT values包含指定函數調用是否失敗或成功的信息,以及其他錯誤代碼。如果返回值表示失敗,OSError則自動引發a。

對c++的要求

由於種種原因,很多東西還是不能直接使用的,ctypes只支持c語言的結構,不支持c++,所以編譯之前,需要對要使用的函數使用如下的前綴,才能調用成功:

//必不可少的東西
#define DLLEXPORT extern "C" __declspec(dllexport)

	typedef void (*lpFunc)(char, int, TEST_STRUCT*);
	DLLEXPORT int a() {
		
		return 1;
	}
	//所有要導出的方法均需要加這個,
	DLLEXPORT void *w(TEST_STRUCT *tt){
		
		tt->y = -101;
		std::cout<<"c:"<< sizeof(tt)<<"\n";
		//std::cout << "cs:" << sizeof(test_struct) << "\n";
		return (void *)tt;
	}

一般情況下,給的sdk均爲一個頭文件(.h)和一堆的dll文件,這裏使用的時候,注意可能需要重新編譯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章