5. Xây dựng tiện ích mở rộng C và C++ trên Windows¶
Chương này giải thích ngắn gọn cách tạo mô-đun mở rộng Windows cho Python bằng Microsoft Visual C++ và tiếp theo là thông tin cơ bản chi tiết hơn về cách hoạt động của mô-đun này. Tài liệu giải thích này hữu ích cho cả lập trình viên Windows đang học cách xây dựng các phần mở rộng Python và lập trình viên Unix quan tâm đến việc sản xuất phần mềm có thể được xây dựng thành công trên cả Unix và Windows.
Các tác giả mô-đun được khuyến khích sử dụng phương pháp distutils để xây dựng các mô-đun mở rộng, thay vì phương pháp được mô tả trong phần này. Bạn vẫn sẽ cần trình biên dịch C đã được sử dụng để xây dựng Python; điển hình là Microsoft Visual C++.
Ghi chú
Chương này đề cập đến một số tên tệp bao gồm số phiên bản Python được mã hóa. Những tên tệp này được thể hiện bằng số phiên bản hiển thị là XY; trong thực tế, 'X' sẽ là số phiên bản chính và 'Y' sẽ là số phiên bản phụ của bản phát hành Python mà bạn đang làm việc. Ví dụ: nếu bạn đang sử dụng Python 2.2.1, XY thực tế sẽ là 22.
5.1. Phương pháp tiếp cận sách dạy nấu ăn¶
Có hai cách tiếp cận để xây dựng mô-đun mở rộng trên Windows, giống như trên Unix: sử dụng gói setuptools để kiểm soát quá trình xây dựng hoặc thực hiện mọi việc theo cách thủ công. Cách tiếp cận setuptools hoạt động tốt với hầu hết các tiện ích mở rộng; tài liệu về cách sử dụng setuptools để xây dựng và đóng gói các mô-đun mở rộng có sẵn trong Xây dựng tiện ích mở rộng C và C++ bằng setuptools. Nếu bạn thấy mình thực sự cần thực hiện mọi việc theo cách thủ công, bạn có thể nên nghiên cứu tệp dự án cho mô-đun thư viện chuẩn winsound.
5.2. Sự khác biệt giữa Unix và Windows¶
Unix và Windows sử dụng các mô hình hoàn toàn khác nhau để tải mã trong thời gian chạy. Trước khi bạn cố gắng xây dựng một mô-đun có thể được tải động, hãy lưu ý cách hệ thống của bạn hoạt động.
Trong Unix, tệp đối tượng dùng chung (.so) chứa mã sẽ được chương trình sử dụng cũng như tên của các hàm và dữ liệu mà nó mong muốn tìm thấy trong chương trình. Khi tệp được nối với chương trình, tất cả các tham chiếu đến các hàm và dữ liệu đó trong mã của tệp sẽ được thay đổi để trỏ đến các vị trí thực tế trong chương trình nơi các hàm và dữ liệu được đặt trong bộ nhớ. Về cơ bản đây là một hoạt động liên kết.
Trong Windows, tệp thư viện liên kết động (.dll) không có tham chiếu treo. Thay vào đó, quyền truy cập vào các chức năng hoặc dữ liệu sẽ phải thông qua bảng tra cứu. Vì vậy, mã DLL không cần phải sửa trong thời gian chạy để tham chiếu đến bộ nhớ của chương trình; thay vào đó, mã đã sử dụng bảng tra cứu của DLL và bảng tra cứu được sửa đổi trong thời gian chạy để trỏ đến các hàm và dữ liệu.
Trong Unix, chỉ có một loại tệp thư viện (.a) chứa mã từ một số tệp đối tượng (.o). Trong bước liên kết để tạo tệp đối tượng dùng chung (.so), trình liên kết có thể thấy rằng nó không biết mã định danh được xác định ở đâu. Trình liên kết sẽ tìm kiếm nó trong các tệp đối tượng trong thư viện; nếu tìm thấy nó, nó sẽ bao gồm tất cả mã từ tệp đối tượng đó.
Trong Windows, có hai loại thư viện, thư viện tĩnh và thư viện nhập (cả hai đều được gọi là .lib). Thư viện tĩnh giống như tệp Unix .a; nó chứa mã được đưa vào khi cần thiết. Thư viện nhập về cơ bản chỉ được sử dụng để trấn an trình liên kết rằng một số nhận dạng nhất định là hợp pháp và sẽ có trong chương trình khi DLL được tải. Vì vậy, trình liên kết sử dụng thông tin từ thư viện nhập để xây dựng bảng tra cứu sử dụng các mã định danh không có trong DLL. Khi một ứng dụng hoặc DLL được liên kết, một thư viện nhập có thể được tạo. Thư viện này sẽ cần được sử dụng cho tất cả các tệp DLL trong tương lai phụ thuộc vào các ký hiệu trong ứng dụng hoặc DLL.
Giả sử bạn đang xây dựng hai mô-đun tải động, B và C, sẽ chia sẻ một khối mã A khác. Trên Unix, bạn sẽ not chuyển A.a tới trình liên kết cho B.so và C.so; điều đó sẽ khiến nó được đưa vào hai lần, do đó B và C mỗi người sẽ có một bản sao riêng. Trong Windows, việc xây dựng A.dll cũng sẽ xây dựng A.lib. Bạn do chuyển A.lib tới trình liên kết cho B và C. A.lib không chứa mã; nó chỉ chứa thông tin sẽ được sử dụng trong thời gian chạy để truy cập mã của A.
Trong Windows, việc sử dụng thư viện nhập cũng giống như sử dụng import spam; nó cho phép bạn truy cập vào tên của thư rác nhưng không tạo một bản sao riêng. Trên Unix, liên kết với thư viện giống from spam import * hơn; nó tạo ra một bản sao riêng biệt.
-
Py_NO_LINK_LIB¶
Tắt liên kết ngầm, dựa trên
#pragmavới thư viện Python, được thực hiện bên trong các tệp tiêu đề CPython.Added in version 3.14.
5.3. Sử dụng DLL trong thực tế¶
Windows Python được xây dựng bằng Microsoft Visual C++; sử dụng các trình biên dịch khác có thể hoạt động hoặc không. Phần còn lại của phần này là dành riêng cho MSVC++.
Khi tạo DLL trong Windows, bạn có thể sử dụng thư viện CPython theo hai cách:
Theo mặc định, việc bao gồm
PC/pyconfig.htrực tiếp hoặc thông quaPython.hsẽ kích hoạt một liên kết ngầm, nhận biết cấu hình với thư viện. Tệp tiêu đề chọnpythonXY_d.libcho Gỡ lỗi,pythonXY.libcho Bản phát hành vàpythonX.libcho Bản phát hành khi bật Limited API.Để xây dựng hai tệp DLL, spam và ni (sử dụng các hàm C có trong thư rác), bạn có thể sử dụng các lệnh sau:
cl /LD /I/python/include spam.c cl /LD /I/python/include ni.c spam.lib
Lệnh đầu tiên tạo ba tệp:
spam.obj,spam.dllvàspam.lib.Spam.dllkhông chứa bất kỳ hàm Python nào (chẳng hạn nhưPyArg_ParseTuple()), nhưng nó biết cách tìm mã Python nhờpythonXY.libđược liên kết ngầm.Lệnh thứ hai đã tạo
ni.dll(và.objvà.lib), biết cách tìm ra các chức năng cần thiết từ thư rác cũng như từ tệp thực thi Python.Thủ công bằng cách xác định macro
Py_NO_LINK_LIBtrước khi bao gồmPython.h. Bạn phải chuyểnpythonXY.libcho trình liên kết.Để xây dựng hai tệp DLL, spam và ni (sử dụng các hàm C có trong thư rác), bạn có thể sử dụng các lệnh sau:
cl /LD /DPy_NO_LINK_LIB /I/python/include spam.c ../libs/pythonXY.lib cl /LD /DPy_NO_LINK_LIB /I/python/include ni.c spam.lib ../libs/pythonXY.lib
Lệnh đầu tiên tạo ba tệp:
spam.obj,spam.dllvàspam.lib.Spam.dllkhông chứa bất kỳ hàm Python nào (chẳng hạn nhưPyArg_ParseTuple()), nhưng nó biết cách tìm mã Python nhờpythonXY.lib.Lệnh thứ hai đã tạo
ni.dll(và.objvà.lib), biết cách tìm ra các chức năng cần thiết từ thư rác cũng như từ tệp thực thi Python.
Không phải mọi mã định danh đều được xuất sang bảng tra cứu. Nếu bạn muốn bất kỳ mô-đun nào khác (bao gồm cả Python) có thể nhìn thấy số nhận dạng của bạn, bạn phải nói _declspec(dllexport), như trong void _declspec(dllexport) initspam(void) hoặc PyObject _declspec(dllexport) *NiGetSpamData(void).
Developer Studio sẽ đưa vào rất nhiều thư viện nhập mà bạn không thực sự cần, thêm khoảng 100K vào tệp thực thi của bạn. Để loại bỏ chúng, hãy sử dụng hộp thoại Cài đặt dự án, tab Liên kết, để chỉ định ignore default libraries. Thêm msvcrtxx.lib chính xác vào danh sách thư viện.