最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python調(diào)用DLL動態(tài)庫的入門指南

 更新時(shí)間:2026年04月21日 09:02:27   作者:yuanpan  
本文介紹了在Windows環(huán)境下使用Python調(diào)用C/C++和C#動態(tài)鏈接庫(DLL)的方法,主要包括如何使用ctypes庫調(diào)用C/C++的DLL,如何使用pythonnet庫調(diào)用C#的普通類庫,以及如何將C#方法導(dǎo)出為原生函數(shù)以便ctypes調(diào)用,需要的朋友可以參考下

很多 Windows 項(xiàng)目里都會遇到這樣的需求:底層算法已經(jīng)用 C/C++ 寫好了,或者公司內(nèi)部有一個(gè) C# 組件,現(xiàn)在希望在 Python 里直接調(diào)用,而不是重寫一遍。

這篇文章面向新手,重點(diǎn)講清楚三件事:

  1. Python 如何調(diào)用 C/C++ 編譯出來的 DLL。
  2. Python 調(diào)用 C# 動態(tài)庫時(shí)有哪些可行方案。
  3. DLL 里的常見數(shù)據(jù)類型如何對應(yīng)到 Python 里的類型。

示例環(huán)境:

  • Windows 10/11
  • Python 3.10+,64 位
  • Visual Studio 2022 Build Tools 或 Visual Studio 2022
  • .NET 8 SDK

重要前提:Python、DLL、依賴庫必須是同一位數(shù)。64 位 Python 調(diào) 64 位 DLL,32 位 Python 調(diào) 32 位 DLL。位數(shù)不一致會加載失敗。

一、先理解 Python 調(diào) DLL 的基本思路

Python 標(biāo)準(zhǔn)庫里自帶 ctypes,它可以加載 Windows 下的 .dll 文件,并調(diào)用 DLL 中導(dǎo)出的 C 風(fēng)格函數(shù)。

核心步驟通常是:

from ctypes import CDLL

dll = CDLL(r".\native_demo.dll")
result = dll.AddInt(1, 2)
print(result)

但真實(shí)項(xiàng)目中不能只寫這幾行,因?yàn)?Python 不知道 DLL 函數(shù)的參數(shù)和返回值類型。如果不聲明類型,遇到字符串、浮點(diǎn)數(shù)、結(jié)構(gòu)體、指針時(shí)很容易出錯(cuò)。

更規(guī)范的寫法是:

import ctypes as C

dll = C.CDLL(r".\native_demo.dll")

dll.AddInt.argtypes = [C.c_int, C.c_int]
dll.AddInt.restype = C.c_int

print(dll.AddInt(10, 20))

argtypes 表示參數(shù)類型,restype 表示返回值類型。

二、準(zhǔn)備一個(gè) C++ DLL 示例

先寫一個(gè)最簡單但覆蓋多種類型的 C++ 動態(tài)庫:整數(shù)、浮點(diǎn)數(shù)、字符串、結(jié)構(gòu)體、指針都包含。

新建文件 native_demo.cpp

#include <cmath>
#include <cwchar>
#define DLL_EXPORT extern "C" __declspec(dllexport)
struct Point
{
    int x;
    int y;
};
struct Student
{
    int id;
    double score;
    wchar_t name[32];
};
DLL_EXPORT int AddInt(int a, int b)
{
    return a + b;
}
DLL_EXPORT double CircleArea(double radius)
{
    return 3.141592653589793 * radius * radius;
}
DLL_EXPORT int IsEven(int value)
{
    return value % 2 == 0 ? 1 : 0;
}
DLL_EXPORT void MovePoint(Point* point, int dx, int dy)
{
    if (point == nullptr)
    {
        return;
    }
    point->x += dx;
    point->y += dy;
}
DLL_EXPORT int BuildStudent(int id, const wchar_t* name, double score, Student* output)
{
    if (name == nullptr || output == nullptr)
    {
        return 0;
    }
    output->id = id;
    output->score = score;
    wcsncpy_s(output->name, name, _TRUNCATE);
    return 1;
}
DLL_EXPORT void WriteMessage(wchar_t* buffer, int bufferLength)
{
    if (buffer == nullptr || bufferLength <= 0)
    {
        return;
    }
    wcsncpy_s(buffer, bufferLength, L"Hello from C++ DLL", _TRUNCATE);
}

用 Visual Studio 的 “x64 Native Tools Command Prompt for VS 2022” 進(jìn)入該文件所在目錄,執(zhí)行:

cl /LD /EHsc native_demo.cpp /Fe:native_demo.dll

編譯成功后會得到:

native_demo.dll
native_demo.lib
native_demo.obj

Python 只需要加載 native_demo.dll。

三、Python 調(diào)用 C++ DLL

新建 call_cpp_dll.py

import ctypes as C
from pathlib import Path


dll_path = Path(__file__).with_name("native_demo.dll")
dll = C.CDLL(str(dll_path))


class Point(C.Structure):
    _fields_ = [
        ("x", C.c_int),
        ("y", C.c_int),
    ]


class Student(C.Structure):
    _fields_ = [
        ("id", C.c_int),
        ("score", C.c_double),
        ("name", C.c_wchar * 32),
    ]


dll.AddInt.argtypes = [C.c_int, C.c_int]
dll.AddInt.restype = C.c_int

dll.CircleArea.argtypes = [C.c_double]
dll.CircleArea.restype = C.c_double

dll.IsEven.argtypes = [C.c_int]
dll.IsEven.restype = C.c_int

dll.MovePoint.argtypes = [C.POINTER(Point), C.c_int, C.c_int]
dll.MovePoint.restype = None

dll.BuildStudent.argtypes = [C.c_int, C.c_wchar_p, C.c_double, C.POINTER(Student)]
dll.BuildStudent.restype = C.c_int

dll.WriteMessage.argtypes = [C.c_wchar_p, C.c_int]
dll.WriteMessage.restype = None


print("AddInt:", dll.AddInt(10, 20))
print("CircleArea:", dll.CircleArea(3.0))
print("IsEven:", bool(dll.IsEven(8)))

point = Point(3, 5)
dll.MovePoint(C.byref(point), 10, -2)
print("Point:", point.x, point.y)

student = Student()
ok = dll.BuildStudent(1001, "Alice", 95.5, C.byref(student))
if ok:
    print("Student:", student.id, student.name, student.score)

buffer = C.create_unicode_buffer(128)
dll.WriteMessage(buffer, len(buffer))
print("Message:", buffer.value)

運(yùn)行:

python call_cpp_dll.py

可能輸出:

AddInt: 30
CircleArea: 28.274333882308137
IsEven: True
Point: 13 3
Student: 1001 Alice 95.5
Message: Hello from C++ DLL

四、為什么 C++ DLL 要寫extern "C"

C++ 支持函數(shù)重載,所以編譯器會對函數(shù)名做 name mangling,也就是把函數(shù)名改造成帶參數(shù)信息的內(nèi)部符號。

例如你寫的是:

int AddInt(int a, int b);

編譯后的導(dǎo)出名可能不是簡單的 AddInt。Python 用 ctypes 找函數(shù)時(shí)會按導(dǎo)出名查找,如果導(dǎo)出名變了,就會找不到。

所以給 DLL 導(dǎo)出的函數(shù)建議寫成:

extern "C" __declspec(dllexport)

它的含義是:

  • extern "C":按 C 語言方式導(dǎo)出函數(shù)名,減少 C++ 名字改寫問題。
  • __declspec(dllexport):告訴 MSVC 把函數(shù)導(dǎo)出到 DLL。

如果要在 32 位環(huán)境穩(wěn)定導(dǎo)出函數(shù)名,建議再配合 .def 文件控制導(dǎo)出名。新手學(xué)習(xí)階段優(yōu)先使用 64 位 Python 和 64 位 DLL,會少很多麻煩。

五、DLL 類型與 Pythonctypes類型對應(yīng)表

下面是 Windows DLL 開發(fā)中最常見的類型映射。

C/C++ 類型Windows 常見類型Python ctypes 類型說明
charCHARc_char單個(gè)字節(jié)字符
unsigned charBYTEc_ubyte無符號 8 位整數(shù)
shortSHORTc_short16 位整數(shù)
unsigned shortWORDc_ushort無符號 16 位整數(shù)
intINTc_int通常是 32 位整數(shù)
unsigned intUINTc_uint無符號 32 位整數(shù)
longLONGc_longWindows 下通常是 32 位
unsigned longDWORDc_ulongWindows 下常用 32 位無符號整數(shù)
long longLONGLONGc_longlong64 位整數(shù)
floatFLOATc_float單精度浮點(diǎn)數(shù)
doubleDOUBLEc_double雙精度浮點(diǎn)數(shù)
boolBOOLc_boolc_intWin32 BOOL 一般用 c_int 更穩(wěn)
char*LPSTRc_char_pANSI 字符串指針
wchar_t*LPWSTRc_wchar_pUnicode 寬字符字符串指針
const char*LPCSTRc_char_p輸入用 ANSI 字符串
const wchar_t*LPCWSTRc_wchar_p輸入用 Unicode 字符串
void*LPVOIDc_void_p通用指針
int*int*POINTER(c_int)指向整數(shù)的指針
structSTRUCTctypes.Structure字段順序必須一致

1. 整數(shù)和浮點(diǎn)數(shù)

C++:

DLL_EXPORT int AddInt(int a, int b);
DLL_EXPORT double CircleArea(double radius);

Python:

dll.AddInt.argtypes = [C.c_int, C.c_int]
dll.AddInt.restype = C.c_int
dll.CircleArea.argtypes = [C.c_double]
dll.CircleArea.restype = C.c_double

2. 布爾值

Windows API 里很多函數(shù)的布爾值不是 C++ 的 bool,而是 BOOL,本質(zhì)上是 32 位整數(shù)。

C++ 示例里這樣寫:

DLL_EXPORT int IsEven(int value)
{
    return value % 2 == 0 ? 1 : 0;
}

Python:

dll.IsEven.argtypes = [C.c_int]
dll.IsEven.restype = C.c_int
is_even = bool(dll.IsEven(8))

新手建議:跨語言 DLL 接口里盡量用 int 表示成功失敗,少直接暴露 C++ bool。

3. 字符串輸入

C++:

DLL_EXPORT int BuildStudent(int id, const wchar_t* name, double score, Student* output);

Python:

dll.BuildStudent.argtypes = [C.c_int, C.c_wchar_p, C.c_double, C.POINTER(Student)]
dll.BuildStudent.restype = C.c_int

調(diào)用時(shí)直接傳 Python 字符串:

dll.BuildStudent(1001, "Alice", 95.5, C.byref(student))

這里用的是 wchar_t*,對應(yīng) Python 的 c_wchar_p。在 Windows 上建議優(yōu)先用寬字符,中文路徑和中文內(nèi)容會更省事。

4. 字符串輸出

DLL 如果要返回字符串,不建議直接返回內(nèi)部臨時(shí)指針。更穩(wěn)的方式是:Python 創(chuàng)建緩沖區(qū),把緩沖區(qū)指針傳給 DLL,DLL 往緩沖區(qū)里寫。

C++:

DLL_EXPORT void WriteMessage(wchar_t* buffer, int bufferLength);

Python:

buffer = C.create_unicode_buffer(128)
dll.WriteMessage(buffer, len(buffer))
print(buffer.value)

這種方式的優(yōu)點(diǎn)是內(nèi)存由 Python 分配和釋放,不容易出現(xiàn)“誰申請、誰釋放”的問題。

5. 結(jié)構(gòu)體

C++:

struct Point
{
    int x;
    int y;
};

Python 必須按相同字段順序定義:

class Point(C.Structure):
    _fields_ = [
        ("x", C.c_int),
        ("y", C.c_int),
    ]

傳給 DLL 時(shí):

point = Point(3, 5)
dll.MovePoint(C.byref(point), 10, -2)

C.byref(point) 表示把結(jié)構(gòu)體地址傳過去,C++ 側(cè)收到的是 Point*

6. 數(shù)組

C/C++ 里的數(shù)組可以用 ctypes 的數(shù)組類型表示。

IntArray10 = C.c_int * 10
arr = IntArray10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

如果 DLL 函數(shù)參數(shù)是:

void SumArray(const int* values, int count);

Python 可以聲明為:

dll.SumArray.argtypes = [C.POINTER(C.c_int), C.c_int]

調(diào)用時(shí)把數(shù)組對象傳進(jìn)去即可。

六、調(diào)用約定:CDLL和WinDLL怎么選

Windows DLL 里常見兩種調(diào)用約定:

  • cdecl
  • stdcall

Python 里對應(yīng):

C.CDLL("xxx.dll")     # cdecl
C.WinDLL("xxx.dll")  # stdcall

如果 C++ 函數(shù)沒有特別聲明,MSVC 默認(rèn)通常是 cdecl,所以使用 CDLL

如果 DLL 函數(shù)寫成:

extern "C" __declspec(dllexport) int __stdcall AddInt(int a, int b);

Python 側(cè)就應(yīng)該用:

dll = C.WinDLL(r".\xxx.dll")

調(diào)用約定不匹配可能導(dǎo)致參數(shù)讀取錯(cuò)誤、程序崩潰,或者函數(shù)調(diào)用結(jié)束后棧異常。

七、Python 調(diào)用 C# DLL 的兩種常見方式

C# 編譯出來的普通 .dll 是 .NET 程序集,不是傳統(tǒng) Win32 DLL。它里面的 C# 方法不能直接像 C 函數(shù)那樣被 ctypes.CDLL 調(diào)用。

常見方案有兩個(gè):

  1. 使用 pythonnet,在 Python 中加載 .NET 程序集并調(diào)用 C# 類。
  2. 把 C# 方法導(dǎo)出成原生函數(shù),再用 ctypes 調(diào)用。

新手優(yōu)先推薦第一種,簡單、直觀、適合調(diào)用普通 C# 類庫。第二種更接近“Python 調(diào) DLL”的傳統(tǒng)方式,但構(gòu)建復(fù)雜一些。

八、方案一:用pythonnet調(diào)用普通 C# 類庫

先創(chuàng)建一個(gè) C# 類庫:

dotnet new classlib -n CsLibraryDemo

修改 CsLibraryDemo/Class1.cs

namespace CsLibraryDemo;
public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
    public double CircleArea(double radius)
    {
        return Math.PI * radius * radius;
    }
    public string Hello(string name)
    {
        return $"Hello, {name}";
    }
}
public class Student
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public double Score { get; set; }
}

編譯:

dotnet build -c Release

安裝 Python 包:

pip install pythonnet

Python 調(diào)用:

import clr
from pathlib import Path


dll_path = Path(r".\CsLibraryDemo\bin\Release\net8.0\CsLibraryDemo.dll").resolve()
clr.AddReference(str(dll_path))

from CsLibraryDemo import Calculator, Student


calc = Calculator()

print(calc.Add(10, 20))
print(calc.CircleArea(3.0))
print(calc.Hello("Python"))

student = Student()
student.Id = 1001
student.Name = "Alice"
student.Score = 95.5

print(student.Id, student.Name, student.Score)

這種方式的類型映射由 pythonnet 幫你處理,調(diào)用體驗(yàn)更像“在 Python 中使用 C# 類”。

常見映射大致如下:

C# 類型Python 側(cè)表現(xiàn)
intint
longint
floatfloat
doublefloat
boolbool
stringstr
class.NET 對象
List<T>.NET 集合對象,可遍歷
byte[].NET 字節(jié)數(shù)組,必要時(shí)轉(zhuǎn) bytes

九、方案二:把 C# 方法導(dǎo)出成原生函數(shù)給ctypes調(diào)

如果你希望 Python 像調(diào)用 C++ DLL 一樣調(diào)用 C#,可以使用 .NET NativeAOT,把 C# 方法導(dǎo)出為原生函數(shù)。

這種方式適合:

  • Python 端必須使用 ctypes
  • 希望 C# DLL 暴露 C 風(fēng)格函數(shù)。
  • 不想在 Python 進(jìn)程里直接操作 C# 類。

創(chuàng)建項(xiàng)目:

dotnet new classlib -n CsNativeExportDemo

修改 CsNativeExportDemo.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <PublishAot>true</PublishAot>
    <NativeLib>Shared</NativeLib>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
</Project>

修改 Class1.cs

using System.Runtime.InteropServices;
public static class NativeExports
{
    [UnmanagedCallersOnly(EntryPoint = "AddInt")]
    public static int AddInt(int a, int b)
    {
        return a + b;
    }
    [UnmanagedCallersOnly(EntryPoint = "CircleArea")]
    public static double CircleArea(double radius)
    {
        return Math.PI * radius * radius;
    }
}

發(fā)布:

dotnet publish -c Release -r win-x64

發(fā)布目錄中會生成原生 DLL。Python 可以像調(diào)用 C++ DLL 一樣調(diào)用它:

import ctypes as C
from pathlib import Path
dll_path = Path(r".\CsNativeExportDemo\bin\Release\net8.0\win-x64\publish\CsNativeExportDemo.dll")
dll = C.CDLL(str(dll_path))
dll.AddInt.argtypes = [C.c_int, C.c_int]
dll.AddInt.restype = C.c_int
dll.CircleArea.argtypes = [C.c_double]
dll.CircleArea.restype = C.c_double
print(dll.AddInt(10, 20))
print(dll.CircleArea(3.0))

注意:UnmanagedCallersOnly 導(dǎo)出的函數(shù)更適合使用簡單類型,例如 int、double。字符串、數(shù)組、復(fù)雜對象需要額外處理內(nèi)存和編碼,新手不建議一開始就這樣做。

十、實(shí)戰(zhàn)中最容易踩的坑

1. 32 位和 64 位不一致

報(bào)錯(cuò)類似:

OSError: [WinError 193] %1 不是有效的 Win32 應(yīng)用程序

通常是 Python 和 DLL 位數(shù)不一致。

檢查 Python 位數(shù):

import platform
print(platform.architecture())

2. DLL 依賴項(xiàng)找不到

報(bào)錯(cuò)類似:

OSError: [WinError 126] 找不到指定的模塊

不一定是目標(biāo) DLL 不存在,也可能是目標(biāo) DLL 依賴的其他 DLL 找不到。

解決思路:

  • 把依賴 DLL 放到同一目錄。
  • 把依賴目錄加入 PATH。
  • Python 3.8+ 可以使用 os.add_dll_directory()。

示例:

import os

os.add_dll_directory(r"C:\path\to\dll_folder")

3. 沒寫argtypes和restype

ctypes 默認(rèn)返回 int。如果 DLL 實(shí)際返回 double、指針、結(jié)構(gòu)體,結(jié)果就會錯(cuò)。

建議每個(gè)函數(shù)都顯式聲明:

dll.SomeFunction.argtypes = [...]
dll.SomeFunction.restype = ...

4. 字符串編碼混亂

Windows 下建議優(yōu)先使用 Unicode 寬字符接口:

  • C/C++:wchar_t*
  • Python:c_wchar_p
  • 緩沖區(qū):create_unicode_buffer

如果使用 char*,就需要明確編碼,比如 UTF-8 或 GBK。

5. 內(nèi)存釋放責(zé)任不清楚

跨語言調(diào)用時(shí)一定要明確:內(nèi)存是誰申請的,就盡量由誰釋放。

新手推薦:

  • Python 傳入緩沖區(qū),DLL 寫入內(nèi)容。
  • DLL 不要返回臨時(shí)字符串指針。
  • 如果 DLL 必須分配內(nèi)存,就同時(shí)提供 FreeMemory 之類的釋放函數(shù)。

十一、推薦的 DLL 接口設(shè)計(jì)習(xí)慣

為了讓 Python 調(diào)用更穩(wěn)定,建議 DLL 對外接口盡量保持 C 風(fēng)格:

extern "C" __declspec(dllexport) int FunctionName(...);

接口設(shè)計(jì)上盡量:

  • int 返回成功或失敗。
  • 復(fù)雜數(shù)據(jù)通過結(jié)構(gòu)體指針輸出。
  • 字符串輸出采用“調(diào)用方傳緩沖區(qū)”的方式。
  • 避免直接暴露 C++ 類、模板、std::stringstd::vector。
  • 保證 Python 和 DLL 使用相同位數(shù)。
  • 每個(gè) Python 調(diào)用都寫清楚 argtypesrestype。

十二、完整文件清單

學(xué)習(xí) C++ DLL 調(diào)用時(shí),建議把下面文件放在同一目錄:

demo/
  native_demo.cpp
  native_demo.dll
  call_cpp_dll.py

學(xué)習(xí) C# 普通類庫調(diào)用時(shí):

demo/
  CsLibraryDemo/
  call_csharp_by_pythonnet.py

學(xué)習(xí) C# NativeAOT 導(dǎo)出時(shí):

demo/
  CsNativeExportDemo/
  call_csharp_native_export.py

總結(jié)

Python 在 Windows 下調(diào)用 DLL,最常用的是標(biāo)準(zhǔn)庫 ctypes。調(diào)用 C/C++ DLL 時(shí),關(guān)鍵點(diǎn)是導(dǎo)出 C 風(fēng)格函數(shù)、聲明參數(shù)類型、處理好字符串和結(jié)構(gòu)體。

調(diào)用 C# 動態(tài)庫要先區(qū)分 DLL 類型:普通 C# 類庫是 .NET 程序集,推薦用 pythonnet;如果要像 C++ DLL 一樣用 ctypes 調(diào)用,則需要把 C# 方法導(dǎo)出成原生函數(shù),例如使用 NativeAOT。

掌握這幾個(gè)規(guī)則后,Python 就可以很自然地接入已有的 C++ 算法庫、Windows SDK 封裝庫、工業(yè)設(shè)備 SDK,或者公司內(nèi)部的 C# 組件。

以上就是Python調(diào)用DLL動態(tài)庫的入門指南的詳細(xì)內(nèi)容,更多關(guān)于Python調(diào)用DLL動態(tài)庫的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

建湖县| 仁化县| 卓资县| 塔城市| 昌都县| 大埔区| 华池县| 华池县| 桃源县| 武乡县| 察雅县| 土默特右旗| 六安市| 张家港市| 兰溪市| 鹤岗市| 循化| 兴义市| 兰溪市| 潍坊市| 赤水市| 克东县| 康平县| 上杭县| 武鸣县| 防城港市| 龙井市| 义乌市| 扎赉特旗| 吉林市| 澄城县| 雅安市| 辉南县| 和平区| 资源县| 平度市| 公主岭市| 利辛县| 若羌县| 甘孜县| 松桃|