5. ساخت ماژول‌های توسعه‌ای C و ++C در ویندوز

این فصل به‌طور خلاصه توضیح می‌دهد که چگونه می‌توان با استفاده از Microsoft Visual C++ یک ماژول توسعه‌ای ویندوزی برای پایتون ایجاد کرد، و سپس با اطلاعات پس‌زمینه‌ی دقیق‌تری درباره‌ی نحوه‌ی کارکرد آن ادامه می‌یابد. این مطالب توضیحی هم برای برنامه‌نویس ویندوزی که در حال یادگیری ساخت توسعه‌های پایتون است و هم برای برنامه‌نویس یونیکسی که علاقه‌مند به تولید نرم‌افزاری است که می‌توان آن را با موفقیت هم روی یونیکس و هم روی ویندوز ساخت، مفید است.

به نویسندگان ماژول‌ها توصیه می‌شود که برای ساخت ماژول‌های توسعه‌ای، به‌جای روش توصیف‌شده در این بخش، از رویکرد distutils استفاده کنند. شما همچنان به همان کامپایلر C که برای ساخت پایتون استفاده شده است نیاز خواهید داشت؛ که معمولاً Microsoft Visual C++ است.

توجه

این فصل به تعدادی از نام‌های پرونده اشاره می‌کند که شامل شماره‌ی نسخه‌ی کدگذاری‌شده‌ی پایتون هستند. این نام‌های پرونده با شماره‌ی نسخه‌ای که به‌صورت XY نمایش داده شده بازنمایی می‌شوند؛ در عمل، 'X' شماره‌ی نسخه‌ی اصلی و 'Y' شماره‌ی نسخه‌ی فرعی انتشار پایتونی است که با آن کار می‌کنید. برای مثال، اگر از پایتون 2.2.1 استفاده می‌کنید، XY در واقع 22 خواهد بود.

5.1. رویکرد کتاب آشپزی

همان‌طور که در یونیکس، در ویندوز نیز دو رویکرد برای ساخت ماژول‌های توسعه‌ای وجود دارد: استفاده از بسته‌ی setuptools برای کنترل فرایند ساخت، یا انجام کارها به‌صورت دستی. رویکرد setuptools برای بیشتر ماژول‌های توسعه‌ای به‌خوبی کار می‌کند؛ مستندات مربوط به استفاده از setuptools برای ساخت و بسته‌بندی ماژول‌های توسعه‌ای در ساخت توسعه‌های C و C++ با setuptools در دسترس است. اگر دریافتید که واقعاً باید کارها را به‌صورت دستی انجام دهید، مطالعه‌ی پرونده‌ی پروژه‌ی ماژول winsound از کتابخانه استاندارد می‌تواند آموزنده باشد.

5.2. تفاوت‌های بین Unix و Windows

یونیکس و ویندوز از پارادایم‌های کاملاً متفاوتی برای بارگذاری کد در زمان اجرا استفاده می‌کنند. پیش از آنکه تلاش کنید ماژولی بسازید که بتوان آن را به‌صورت پویا بارگذاری کرد، از چگونگی کارکرد سیستم خود آگاه باشید.

در یونیکس، یک پرونده‌ی شیء اشتراکی (shared object) (.so) شامل کدی است که قرار است توسط برنامه استفاده شود، و همچنین نام توابع و داده‌هایی که انتظار دارد آن‌ها را در برنامه بیابد. هنگامی که پرونده به برنامه الحاق می‌شود، تمام ارجاع‌ها به آن توابع و داده‌ها در کدِ پرونده تغییر می‌کنند تا به مکان‌های واقعی در برنامه — جایی که توابع و داده‌ها در حافظه قرار گرفته‌اند — اشاره کنند. این اساساً یک عملیات پیوند است.

در ویندوز، پرونده کتابخانه پیوند پویا (.dll) هیچ ارجاع معلقی ندارد. در عوض، دسترسی به توابع یا داده‌ها از طریق یک جدول جستجو انجام می‌شود. بنابراین، کد DLL لازم نیست در ران‌تایم برای ارجاع به حافظه برنامه اصلاح شود؛ در عوض، کد از پیش از جدول جستجوی DLL استفاده می‌کند و جدول جستجو در ران‌تایم تغییر داده می‌شود تا به توابع و داده‌ها اشاره کند.

در یونیکس، تنها یک نوع پرونده کتابخانه (.a) وجود دارد که حاوی کد چندین پرونده شیء (.o) است. در طول مرحله پیوند برای ایجاد یک پرونده شیء اشتراکی (.so)، ممکن است پیونددهنده دریابد که نمی‌داند یک شناسه در کجا تعریف شده است. پیونددهنده آن را در پرونده‌های شیء موجود در کتابخانه‌ها جست‌وجو می‌کند؛ اگر آن را پیدا کند، تمام کد آن پرونده شیء را در بر می‌گیرد.

در ویندوز، دو نوع کتابخانه وجود دارد: کتابخانه‌ی ایستا و کتابخانه‌ی ایمپورت (هر دو .lib نامیده می‌شوند). کتابخانه‌ی ایستا مانند پرونده‌ی .a در یونیکس است؛ حاوی کدی است که در صورت نیاز گنجانده می‌شود. کتابخانه‌ی ایمپورت اساساً فقط برای اطمینان‌دادن به پیونددهنده که شناسه‌ی مشخصی معتبر است و هنگام بارگذاری DLL در برنامه موجود خواهد بود، استفاده می‌شود. بنابراین پیونددهنده از اطلاعات کتابخانه‌ی ایمپورت برای ساخت جدول جستجوی شناسه‌هایی که در DLL گنجانده نشده‌اند، استفاده می‌کند. هنگامی که یک برنامه یا DLL پیوند داده می‌شود، ممکن است یک کتابخانه‌ی ایمپورت تولید شود که باید برای تمام DLL‌های آینده‌ای که به نمادهای موجود در آن برنامه یا DLL وابسته‌اند، استفاده شود.

فرض کنید در حال ساخت دو ماژول بارگذاری پویا به نام B و C هستید که باید بلوک کد دیگری به نام A را به اشتراک بگذارند. در یونیکس، شما A.a را برای B.so و C.so به پیونددهنده نمی‌دهید؛ این کار باعث می‌شود که A دو بار گنجانده شود، به‌طوری که B و C هر یک نسخه‌ی خود را داشته باشند. در ویندوز، با ساخت A.dll، A.lib نیز ساخته می‌شود. شما A.lib را برای B و C به پیونددهنده می‌دهید. A.lib حاوی کد نیست؛ فقط حاوی اطلاعاتی است که در زمان اجرا برای دسترسی به کد A استفاده خواهند شد.

در ویندوز، استفاده از کتابخانه ایمپورت (import library) تا حدی شبیه استفاده از import spam است؛ به شما دسترسی به نام‌های spam می‌دهد، اما نسخه جداگانه‌ای ایجاد نمی‌کند. در یونیکس، پیوند دادن با یک کتابخانه بیشتر شبیه from spam import * است؛ و نسخه جداگانه‌ای ایجاد می‌کند.

پیوند ضمنی و مبتنی بر #pragma با کتابخانه‌ی پایتون را که درون پرونده‌های سرآیند سی‌پایتون انجام می‌شود، غیرفعال کنید.

اضافه شده در نسخه‌ی 3.14.

5.3. استفاده از DLL‌ها در عمل

نسخه‌ی ویندوزی پایتون با Microsoft Visual C++ ساخته می‌شود؛ استفاده از کامپایلرهای دیگر ممکن است کار کند یا نکند. بقیه‌ی این بخش ویژه‌ی MSVC++ است.

هنگام ساخت DLL‌ها در ویندوز، می‌توانید از کتابخانه سی‌پایتون به دو روش استفاده کنید:

  1. به‌طور پیش‌فرض، گنجاندن PC/pyconfig.h به‌صورت مستقیم یا از طریق Python.h موجب برقراری یک پیوند ضمنی و آگاه از پیکربندی با کتابخانه می‌شود. پرونده‌ی سرآیند برای Debug، pythonXY_d.lib؛ برای Release، pythonXY.lib؛ و برای Release با فعال بودن API محدود، pythonX.lib را انتخاب می‌کند.

    برای ساخت دو DLL با نام‌های spam و ni (که از توابع C موجود در spam استفاده می‌کند)، می‌توانید از این دستورها استفاده کنید:

    cl /LD /I/python/include spam.c
    cl /LD /I/python/include ni.c spam.lib
    

    فرمان نخست سه پرونده ایجاد کرد: spam.obj، spam.dll و spam.lib. Spam.dll هیچ تابع پایتونی (مانند PyArg_ParseTuple()) ندارد، اما به لطف پیوند ضمنی pythonXY.lib می‌داند که کد پایتون را چگونه پیدا کند.

    دستور دوم ni.dll را ایجاد کرد (به همراه .obj و .lib)، که می‌داند چگونه توابع لازم را از spam و همچنین از پرونده اجرایی پایتون پیدا کند.

  2. به‌صورت دستی، با تعریف ماکروی Py_NO_LINK_LIB پیش از شامل کردن Python.h. باید pythonXY.lib را به پیونددهنده بدهید.

    برای ساخت دو DLL با نام‌های spam و ni (که از توابع C موجود در spam استفاده می‌کند)، می‌توانید از این دستورها استفاده کنید:

    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
    

    دستور اول سه پرونده ایجاد کرد: spam.obj، spam.dll و spam.lib. Spam.dll هیچ تابع پایتونی (مانند PyArg_ParseTuple()) در بر نمی‌گیرد، اما به لطف pythonXY.lib می‌داند چگونه کد پایتون را پیدا کند.

    دستور دوم ni.dll را ایجاد کرد (به همراه .obj و .lib)، که می‌داند چگونه توابع لازم را از spam و همچنین از پرونده اجرایی پایتون پیدا کند.

هر شناسه‌ای به جدول جستجو اکسپورت نمی‌شود. اگر می‌خواهید ماژول‌های دیگر (از جمله پایتون) بتوانند شناسه‌های شما را ببینند، باید _declspec(dllexport) را بنویسید، مانند void _declspec(dllexport) initspam(void) یا PyObject _declspec(dllexport) *NiGetSpamData(void).

Developer Studio تعداد زیادی کتابخانه‌ی ایمپورت که واقعاً به آن‌ها نیاز ندارید را اضافه می‌کند و حدود ۱۰۰ کیلوبایت به پرونده‌ی اجرایی شما می‌افزاید. برای خلاص شدن از آن‌ها، از کادر گفت‌وگوی Project Settings، زبانه‌ی Link، برای مشخص کردن ignore default libraries استفاده کنید. کتابخانه‌ی مناسب msvcrtxx.lib را به فهرست کتابخانه‌ها اضافه کنید.