سؤال

عند إنشاء مكتبة فئة في C++، يمكنك الاختيار بين الديناميكي (.dll, .so) وثابت (.lib, .a) المكتبات.ما الفرق بينهما ومتى يكون من المناسب استخدام أي منهما؟

هل كانت مفيدة؟

المحلول

تعمل المكتبات الثابتة على زيادة حجم الكود في ملفك الثنائي.يتم تحميلها دائمًا وأي إصدار من الكود الذي قمت بتجميعه هو إصدار الكود الذي سيتم تشغيله.

يتم تخزين المكتبات الديناميكية وإصدارها بشكل منفصل.من الممكن أن يتم تحميل إصدار من المكتبة الديناميكية ليس هو الإصدار الأصلي الذي تم شحنه مع الكود الخاص بك لو يعتبر التحديث ثنائيًا متوافقًا مع الإصدار الأصلي.

بالإضافة إلى ذلك، لا يتم بالضرورة تحميل المكتبات الديناميكية - يتم تحميلها عادةً عند استدعائها لأول مرة - ويمكن مشاركتها بين المكونات التي تستخدم نفس المكتبة (تحميل بيانات متعددة، وتحميل كود واحد).

تم اعتبار المكتبات الديناميكية هي النهج الأفضل في معظم الأوقات، ولكن في الأصل كان بها عيب كبير (google DLL hell)، والذي تم التخلص منه تقريبًا بواسطة أنظمة تشغيل Windows الأحدث (Windows XP على وجه الخصوص).

نصائح أخرى

لقد شرح آخرون بشكل كاف ما هي المكتبة الثابتة، ولكن أود أن أشير إلى بعض التحذيرات من استخدام المكتبات الثابتة، على الأقل في نظام التشغيل Windows:

  • المفردات: إذا كان هناك شيء ما يجب أن يكون عالميًا/ثابتًا وفريدًا، فكن حذرًا للغاية بشأن وضعه في مكتبة ثابتة.إذا تم ربط عدة ملفات DLL بهذه المكتبة الثابتة، فسيحصل كل منها على نسخته الخاصة من المفردة.ومع ذلك، إذا كان التطبيق الخاص بك عبارة عن ملف EXE واحد بدون ملفات DLL مخصصة، فقد لا يمثل ذلك مشكلة.

  • إزالة التعليمات البرمجية غير المرجعية: عند الارتباط بمكتبة ثابتة، سيتم ربط أجزاء المكتبة الثابتة التي تمت الإشارة إليها بواسطة DLL/EXE فقط بـ DLL/EXE الخاص بك.

    على سبيل المثال، إذا mylib.lib يتضمن a.obj و b.obj ويشير DLL/EXE الخاص بك فقط إلى الوظائف أو المتغيرات من a.obj, ، مجملها b.obj سيتم التخلص منها بواسطة الرابط.لو b.obj يحتوي على كائنات عامة/ثابتة، ولن يتم تنفيذ منشئاتها ومدمراتها.إذا كان لهؤلاء المنشئين/المدمرين آثار جانبية، فقد تشعر بخيبة أمل بسبب غيابهم.

    وبالمثل، إذا كانت المكتبة الثابتة تحتوي على نقاط دخول خاصة، فقد تحتاج إلى التأكد من تضمينها بالفعل.مثال على ذلك في البرمجة المضمنة (حسنًا، وليس Windows) هو معالج المقاطعة الذي تم وضع علامة عليه على أنه موجود في عنوان محدد.تحتاج أيضًا إلى وضع علامة على معالج المقاطعة كنقطة دخول للتأكد من عدم التخلص منه.

    والنتيجة الأخرى لذلك هي أن المكتبة الثابتة قد تحتوي على ملفات كائنات غير قابلة للاستخدام تمامًا بسبب مراجع لم يتم حلها، ولكنها لن تسبب خطأ في الرابط حتى تقوم بالإشارة إلى دالة أو متغير من ملفات الكائنات تلك.قد يحدث هذا بعد فترة طويلة من كتابة المكتبة.

  • رموز التصحيح: قد تحتاج إلى PDB منفصل لكل مكتبة ثابتة، أو قد ترغب في وضع رموز تصحيح الأخطاء في ملفات الكائنات بحيث يتم إدخالها في PDB لـ DLL/EXE.تشرح وثائق Visual C++ الخيارات الضرورية.

  • رتي: قد ينتهي بك الأمر مع متعددة type_info كائنات لنفس الفئة إذا قمت بربط مكتبة ثابتة واحدة بمكتبات DLL متعددة.إذا كان برنامجك يفترض ذلك type_info هي البيانات والاستخدامات "المفردة". &typeid() أو type_info::before(), ، فقد تحصلين على نتائج غير مرغوب فيها ومفاجئة.

lib عبارة عن وحدة من التعليمات البرمجية المجمعة داخل تطبيقك القابل للتنفيذ.

ملف dll هو وحدة مستقلة من التعليمات البرمجية القابلة للتنفيذ.يتم تحميله في العملية فقط عند إجراء اتصال بهذا الرمز.يمكن استخدام ملف dll بواسطة تطبيقات متعددة وتحميله في عمليات متعددة، مع الاحتفاظ بنسخة واحدة فقط من التعليمات البرمجية على القرص الصلب.

إيجابيات دلل:يمكن استخدامه لإعادة استخدام/مشاركة التعليمات البرمجية بين العديد من المنتجات؛تحميل في ذاكرة العملية عند الطلب ويمكن تفريغها عند عدم الحاجة إليها؛يمكن ترقيتها بشكل مستقل عن بقية البرنامج.

سلبيات دلل:تأثير أداء تحميل dll وإعادة تأسيس التعليمات البرمجية؛مشاكل الإصدار ("dll الجحيم")

إيجابيات ليب:لا يوجد أي تأثير على الأداء حيث يتم دائمًا تحميل التعليمات البرمجية في العملية ولا يتم إعادة تأسيسها؛لا توجد مشاكل الإصدار.

سلبيات ليب:"bloat" القابل للتنفيذ/العملية - كل التعليمات البرمجية موجودة في ملفك القابل للتنفيذ ويتم تحميلها عند بدء العملية؛لا يجوز إعادة الاستخدام/المشاركة - كل منتج له نسخته الخاصة من الكود.

إلى جانب الآثار التقنية للمكتبات الثابتة مقابل المكتبات الديناميكية (تجمع الملفات الثابتة كل شيء في مكتبات ثنائية كبيرة مقابل مكتبات ديناميكية تسمح بمشاركة التعليمات البرمجية بين العديد من الملفات التنفيذية المختلفة)، هناك الآثار القانونية.

على سبيل المثال، إذا كنت تستخدم تعليمات برمجية مرخصة من LGPL وقمت بالارتباط بشكل ثابت مع مكتبة LGPL (وبالتالي إنشاء ملف ثنائي كبير واحد)، فإن التعليمات البرمجية الخاصة بك تصبح مفتوحة المصدر تلقائيًا (حرا كما في الحرية) رمز LGPL.إذا قمت بالارتباط بكائنات مشتركة، فأنت تحتاج فقط إلى LGPL للتحسينات / إصلاحات الأخطاء التي تجريها على مكتبة LGPL نفسها.

تصبح هذه مشكلة أكثر أهمية إذا كنت تقرر كيفية تجميع تطبيقات الهاتف المحمول الخاصة بك على سبيل المثال (في Android لديك خيار ثابت مقابل ديناميكي، وفي iOS لا يمكنك ذلك - فهو دائمًا ثابت).


إنشاء مكتبة ثابتة

$$:~/static [32]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/static [33]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H

void foo();

#endif
$$:~/static [34]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/static [35]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H

void foo2();

#endif
$$:~/static [36]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/static [37]> cat makefile
hello: hello.o libtest.a
        cc -o hello hello.o -L. -ltest
hello.o: hello.c
        cc -c hello.c -I`pwd`
libtest.a:foo.o foo2.o
        ar cr libtest.a foo.o foo2.o
foo.o:foo.c
        cc -c foo.c
foo2.o:foo.c
        cc -c foo2.c
clean:
        rm -f foo.o foo2.o libtest.a hello.o

$$:~/static [38]>

إنشاء مكتبة ديناميكية

$$:~/dynamic [44]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/dynamic [45]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H

void foo();

#endif
$$:~/dynamic [46]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/dynamic [47]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H

void foo2();

#endif
$$:~/dynamic [48]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/dynamic [49]> cat makefile
hello:hello.o libtest.sl
        cc -o hello hello.o -L`pwd` -ltest
hello.o:
        cc -c -b hello.c -I`pwd`
libtest.sl:foo.o foo2.o
        cc -G -b -o libtest.sl foo.o foo2.o
foo.o:foo.c
        cc -c -b foo.c
foo2.o:foo.c
        cc -c -b foo2.c
clean:
        rm -f libtest.sl foo.o foo

2.o hello.o
$$:~/dynamic [50]>

يتم بناء برامج C++ على مرحلتين

  1. التجميع - ينتج رمز الكائن (.obj)
  2. الارتباط - ينتج تعليمات برمجية قابلة للتنفيذ (.exe أو .dll)

المكتبة الثابتة (.lib) هي مجرد حزمة من ملفات .obj وبالتالي فهي ليست برنامجًا كاملاً.لم يمر بالمرحلة الثانية (الربط) من بناء البرنامج.من ناحية أخرى، تشبه ملفات Dll ملفات exe، وبالتالي فهي برامج كاملة.

إذا قمت بإنشاء مكتبة ثابتة، فلن يتم ربطها بعد وبالتالي سيتعين على مستهلكي مكتبتك الثابتة استخدام نفس المترجم الذي استخدمته (إذا كنت تستخدم g++، فسيتعين عليهم استخدام g++).

إذا قمت بدلاً من ذلك بإنشاء ملف dll (وقمت بإنشائه بشكل صحيح)، لقد قمت ببناء برنامج كامل يمكن لجميع المستهلكين استخدامه، بغض النظر عن المترجم الذي يستخدمونه.هناك العديد من القيود على التصدير من ملف dll، إذا كان التوافق بين المترجمات مطلوبًا.

يجب أن تفكر مليًا في التغييرات التي تطرأ بمرور الوقت، والإصدارات، والاستقرار، والتوافق، وما إلى ذلك.

إذا كان هناك تطبيقان يستخدمان الرمز المشترك، فهل تريد إجبار هذين التطبيقين على التغيير معًا، في حالة الحاجة إلى التوافق مع بعضهما البعض؟ثم استخدم ملف dllستستخدم جميع ملفات exe نفس الرمز.

أم تريد عزلهما عن بعضهما البعض، حتى تتمكن من تغيير أحدهما وتكون واثقًا أنك لم تكسر الآخر.ثم استخدم lib الثابت.

DLL hell هو الوقت الذي ربما كان يجب عليك فيه استخدام lib ثابت، ولكنك استخدمت dll بدلاً من ذلك، وليس كل الملفات exes متوافقة معه.

يتم تجميع مكتبة ثابتة في العميل.يتم استخدام .lib في وقت الترجمة وتصبح محتويات المكتبة جزءًا من الملف القابل للتنفيذ.

يتم تحميل المكتبة الديناميكية في وقت التشغيل ولا يتم تجميعها في العميل القابل للتنفيذ.تعد المكتبات الديناميكية أكثر مرونة حيث يمكن للملفات التنفيذية المتعددة للعملاء تحميل ملف DLL والاستفادة من وظائفه.يؤدي هذا أيضًا إلى تقليل الحجم الإجمالي وقابلية الصيانة لرمز العميل الخاص بك إلى الحد الأدنى.

يجب ربط المكتبة الثابتة بالملف التنفيذي النهائي؛يصبح جزءًا من الملف القابل للتنفيذ ويتبعه أينما ذهب.يتم تحميل مكتبة ديناميكية في كل مرة يتم فيها تنفيذ الملف القابل للتنفيذ وتظل منفصلة عن الملف القابل للتنفيذ كملف DLL.

يمكنك استخدام ملف DLL عندما تريد أن تكون قادرًا على تغيير الوظيفة التي توفرها المكتبة دون الحاجة إلى إعادة ربط الملف القابل للتنفيذ (فقط استبدل ملف DLL، دون الحاجة إلى استبدال الملف القابل للتنفيذ).

يمكنك استخدام مكتبة ثابتة عندما لا يكون لديك سبب لاستخدام مكتبة ديناميكية.

ورقة أولريش دريبر حول "كيفية كتابة المكتبات المشتركة" يعد أيضًا موردًا جيدًا يوضح بالتفصيل أفضل السبل للاستفادة من المكتبات المشتركة، أو ما يشير إليه بـ "الكائنات الديناميكية المشتركة" (DSOs).ويركز أكثر على المكتبات المشتركة في قزم تنسيق ثنائي، ولكن بعض المناقشات مناسبة لمكتبات DLL الخاصة بنظام التشغيل Windows أيضًا.

لمناقشة ممتازة لهذا الموضوع لديك قراءة هذا المقال من الشمس.

إنه يدخل في جميع الفوائد بما في ذلك القدرة على إدراج مكتبات متداخلة.يمكن العثور على مزيد من التفاصيل حول التدخل في هذه المقالة هنا.

حقًا إن المقايضة التي تقوم بها (في مشروع كبير) تكون في وقت التحميل الأولي، وسيتم ربط المكتبات في وقت أو آخر، والقرار الذي يجب اتخاذه هو هل سيستغرق الارتباط وقتًا كافيًا يحتاجه المترجم لدغة الرصاصة والقيام بذلك مقدمًا، أو يمكن للرابط الديناميكي القيام بذلك في وقت التحميل.

إذا كانت مكتبتك ستتم مشاركتها بين العديد من الملفات التنفيذية، فمن المنطقي غالبًا أن تجعلها ديناميكية لتقليل حجم الملفات التنفيذية.خلاف ذلك، تأكد من جعلها ثابتة.

هناك العديد من عيوب استخدام DLL.هناك حمل إضافي للتحميل والتفريغ.هناك أيضًا تبعية إضافية.إذا قمت بتغيير ملف dll لجعله غير متوافق مع ملفاتك التنفيذية، فسوف تتوقف عن العمل.من ناحية أخرى، إذا قمت بتغيير مكتبة ثابتة، فلن تتأثر الملفات التنفيذية المجمعة باستخدام الإصدار القديم.

إذا كانت المكتبة ثابتة، فسيتم ربط الكود في وقت الارتباط بالملف القابل للتنفيذ.وهذا يجعل الملف القابل للتنفيذ أكبر (مما لو اتبعت المسار الديناميكي).

إذا كانت المكتبة ديناميكية، فسيتم تضمين المراجع للطرق المطلوبة في الملف القابل للتنفيذ في وقت الارتباط.هذا يعني أنه يتعين عليك شحن الملف القابل للتنفيذ والمكتبة الديناميكية.يجب عليك أيضًا أن تفكر فيما إذا كان الوصول المشترك إلى الكود الموجود في المكتبة آمنًا، وعنوان التحميل المفضل من بين أشياء أخرى.

إذا كنت تستطيع العيش مع المكتبة الثابتة، فانتقل إلى المكتبة الثابتة.

المكتبات الثابتة هي أرشيفات تحتوي على التعليمات البرمجية الكائنية للمكتبة، عند ربطها بتطبيق ما، يتم تجميع التعليمات البرمجية في الملف القابل للتنفيذ.تختلف المكتبات المشتركة من حيث أنها لا يتم تجميعها في الملف القابل للتنفيذ.وبدلاً من ذلك، يبحث الرابط الديناميكي في بعض الدلائل بحثًا عن المكتبة (المكتبات) التي يحتاجها، ثم يقوم بتحميلها في الذاكرة.يمكن لأكثر من ملف تنفيذي واحد استخدام نفس المكتبة المشتركة في نفس الوقت، مما يقلل من استخدام الذاكرة وحجم الملف القابل للتنفيذ.ومع ذلك، هناك المزيد من الملفات لتوزيعها مع الملف القابل للتنفيذ.تحتاج إلى التأكد من تثبيت المكتبة على نظام الاستخدامات في مكان ما حيث يمكن للرابط العثور عليها، والربط الثابت يزيل هذه المشكلة ولكنه يؤدي إلى ملف قابل للتنفيذ أكبر.

إذا كان عملك على المشاريع المضمنة أو المكتبات الثابتة للمنصات المتخصصة هو الطريقة الوحيدة للذهاب، ففي كثير من الأحيان يكون تجميعها في تطبيقك أقل صعوبة.كما أن وجود مشاريع وملفات تعريفية تتضمن كل شيء يجعل الحياة أكثر سعادة.

نحن نستخدم الكثير من ملفات DLL (> 100) في مشروعنا.تحتوي ملفات DLL هذه على تبعيات على بعضها البعض، ولذلك اخترنا إعداد الارتباط الديناميكي.ومع ذلك فهو لديه العيوب التالية:

  • بدء التشغيل البطيء (> 10 ثوانٍ)
  • يجب أن يتم إصدار ملفات DLL، نظرًا لأن Windows يقوم بتحميل الوحدات النمطية بناءً على تفرد الأسماء.وإلا فإن المكونات المكتوبة الخاصة بها ستحصل على الإصدار الخاطئ من DLL (أي.المجموعة التي تم تحميلها بالفعل بدلاً من المجموعة الموزعة الخاصة بها)
  • يمكن للمحسن التحسين فقط ضمن حدود DLL.على سبيل المثال، يحاول المحسن وضع البيانات والتعليمات البرمجية المستخدمة بشكل متكرر بجوار بعضها البعض، ولكن هذا لن يعمل عبر حدود DLL

ربما كان من الأفضل القيام بإعداد أفضل كل شئ مكتبة ثابتة (وبالتالي لديك مكتبة واحدة فقط قابلة للتنفيذ).يعمل هذا فقط في حالة عدم حدوث تكرار للتعليمات البرمجية.يبدو أن الاختبار يدعم هذا الافتراض، لكن لم أتمكن من العثور على عرض أسعار MSDN رسمي.على سبيل المثال، قم بإنشاء ملف exe واحد باستخدام:

  • يستخدم إكس Shared_lib1، Shared_lib2
  • Shared_lib1 استخدم Shared_lib2
  • shared_lib2

يجب أن تكون التعليمات البرمجية والمتغيرات الخاصة بـShared_lib2 موجودة في الملف القابل للتنفيذ المدمج النهائي مرة واحدة فقط.يمكن لأي شخص أن يدعم هذا السؤال؟

سأعطي قاعدة عامة مفادها أنه إذا كان لديك قاعدة تعليمات برمجية كبيرة، وكلها مبنية على مكتبات ذات مستوى أدنى (على سبيل المثال Utils أو إطار عمل Gui)، والتي تريد تقسيمها إلى مكتبات أكثر قابلية للإدارة، فاجعلها مكتبات ثابتة.المكتبات الديناميكية لا تشتري لك أي شيء حقًا، وهناك عدد أقل من المفاجآت - لن يكون هناك سوى مثيل واحد للمكتبات المفردة على سبيل المثال.

إذا كانت لديك مكتبة منفصلة تمامًا عن بقية قاعدة التعليمات البرمجية (على سبيل المثال، مكتبة تابعة لجهة خارجية)، ففكر في جعلها ملف dll.إذا كانت المكتبة LGPL فقد تحتاج إلى استخدام ملف dll على أي حال بسبب شروط الترخيص.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top