Dll'den gelen işlevler. DLL ile çalışırken arayüzleri kullanma. DLL'lerin dinamik yüklenmesi ve boşaltılması

  • 31.10.2019

kullanım DLL(dinamik bağlantı kitaplığı), Windows programlamada yaygın olarak kullanılmaktadır. DLL aslında uzantılı yürütülebilir dosyanın kodunun bir parçası DLL. Herhangi bir program arayabilir DLL.

Avantaj DLLŞöyleki:

  • Kodun yeniden kullanımı.
  • Uygulamalar arasında kod paylaşımı.
  • Kod bölme.
  • Windows'ta kaynak tüketimini iyileştirin.

DLL oluşturma

Menüde dosya Yeni -> Diğer'i seçin. Sekmedeki iletişim kutusunda Yeni Seçme DLL Sihirbazı. Otomatik olarak bir modül oluşturulacak - gelecek için boş bir şablon DLL.

DLL Sözdizimi

inşa etmek için DLL, Seçme Proje -> İnşa Et proje Adı .

İşlev ve Prosedürlerin Görünürlüğü

Fonksiyonlar ve prosedürler yerel olabilir ve şuradan dışa aktarılabilir: DLL.

Yerel

Yerel işlevler ve prosedürler dahili olarak kullanılabilir DLL. Yalnızca kitaplığın içinde görünürler ve hiçbir program onları dışarıdan çağıramaz.

ihraç

Dışa aktarılan işlevler ve prosedürler dışarıda kullanılabilir DLL. Diğer programlar bu tür işlevleri ve prosedürleri çağırabilir.

Yukarıdaki kaynak kodu, dışa aktarılan bir işlevi kullanır. İşlev adı ayrılmış sözcüğü takip eder ihracat.

DLL yükleme

Delphi'de iki tür indirme vardır DLL:

Statik yükleme

Uygulamayı başlattığınızda, otomatik olarak yüklenir. Program süresince hafızada kalır. Kullanımı çok kolay. sadece bir kelime ekle harici bir işlev veya prosedür bildiriminden sonra.

Fonksiyon ToplamıToplam(faktör: tamsayı): tamsayı; Kayıt ol; harici "Örnek.dll";

Eğer DLL bulunamadı, program çalışmaya devam edecek.

gerektiği gibi belleğe yüklenir. Uygulaması daha karmaşıktır, çünkü onu bellekten kendiniz yüklemeniz ve boşaltmanız gerekir. Bellek daha ekonomik kullanılır, bu nedenle uygulama daha hızlı çalışır. Programcının kendisi her şeyin doğru çalışmasını sağlamalıdır. Bunun için ihtiyacınız olan:

  • Tanımlanan işlevin veya prosedürün türünü bildirin.
  • Kütüphaneyi belleğe yükleyin.
  • Bellekteki bir işlevin veya prosedürün adresini alın.
  • Bir işlevi veya prosedürü çağırın.
  • Kitaplığı bellekten kaldırın.

Bir işlevi açıklayan tür bildirimi

type TSumaTotal = function(faktör: tamsayı): tamsayı;

Kitaplığı yükleme

dll_instance:= LoadLibrary("Example_dll.dll");

Bir işleve işaretçi alma

@SummaTotal:= GetProcAddress(dll_instance, "SummaTotal");

işlev çağrısı

Label1.Caption:= IntToStr(SummaTotal(5));

Bir kütüphaneyi bellekten boşaltma

FreeLibrary(dll_instance);

dinamik arama DLL daha fazla çalışma gerektirir, ancak bellek içi kaynakları yönetmek daha kolaydır. kullanman gerekiyorsa DLL program içinde, daha sonra statik yükleme tercih edilir. Blok kullanmayı unutmayın dene… hariç Ve dene… sonunda Program yürütme sırasında hatalardan kaçınmak için.

Satır dışa aktarma

oluşturuldu DLL Delphi kullanılarak diğer programlama dillerinde yazılmış programlarda da kullanılabilir. Bu nedenle herhangi bir veri tipi kullanamıyoruz. Delphi'de bulunan türler diğer dillerde bulunmayabilir. Linux veya Windows'tan yerel veri türlerinin kullanılması tavsiye edilir. Bizim DLL farklı bir programlama dili kullanan başka bir programcı tarafından kullanılabilir.

Kullanılabilir çizgiler Ve dinamik diziler içinde DLL Delphi'de yazılmıştır, ancak bunun için modülü eklemeniz gerekir PaylaşMem bölüme kullanır içinde DLL ve onu kullanacak program. Ayrıca, bu beyan, bölümdeki ilk beyan olmalıdır. kullanır Her proje dosyası.

türleri sicim, bildiğimiz gibi, C, C++ ve diğer diller mevcut değil, bu yüzden bunun yerine kullanılması tavsiye edilir. PChar.

Örnek DLL

kitaplık Example_dll; SysUtils, Classes'ı kullanır; var ( Değişkenleri bildir ) k1: tamsayı; k2: tam sayı; ( Bir fonksiyon bildir ) function SummaTotal(faktör: tamsayı): tamsayı; Kayıt ol; başlangıç ​​faktörü:= faktör * k1 + faktör; faktör:= faktör * k2 + faktör; sonuç:= faktör; son; (Program tarafından daha fazla kullanım için işlevi dışa aktarıyoruz) SummaTotal'ı dışa aktarır; ( Değişkenlerin başlatılması ) start k1:= 2; k2:= 5; son.

DLL'den bir işlev çağırma örneği

birim Birim1; arayüz, Windows, Mesajlar, SysUtils, Varyantlar, Sınıflar, Grafikler, Kontroller, Formlar, Diyaloglar, StdCtrls'yi kullanır; type TForm1 = class(TForm) Button1: TButton; prosedür Button1Click(Gönderen: TObject); özel ( Özel bildirimler ) genel ( Genel bildirimler ) sonu; type TSummaTotal = function(faktör: tamsayı): tamsayı; var Form1: TForm1; uygulama ($R *.dfm) prosedürü TForm1.Button1Click(Gönderen: TObject); var dll_instance: Handle; Toplam Toplam: TSummaToplam; dll_instance'ı başlat:= LoadLibrary("Example_dll.dll"); @SummaTotal:= GetProcAddress(dll_instance, "SummaTotal"); Label1.Caption:= IntToStr(SummaTotal(5)); FreeLibrary(dll_instance); son; son.

Muhtemelen bildiğiniz gibi, dinamik bağlantı kitaplıkları (DLL'ler) dışa aktarılan nesneleri bildirirken C dili kurallarını kullanır, oysa C++ derlendiğinde biraz farklı bir adlandırma sistemi kullanır, bu nedenle yalnızca işlevleri - C++ sınıf yöntemlerini dışa aktaramazsınız. istemci uygulama kodu (bundan böyle istemci, DLL'yi kullanan uygulama anlamına gelir). Ancak bu, hem DLL hem de istemci uygulaması için kullanılabilen arabirimler kullanılarak yapılabilir. Bu yöntem aynı zamanda çok güçlü ve zariftir. istemci yalnızca soyut arabirimi görür ve tüm işlevleri uygulayan gerçek sınıf herhangi bir şey olabilir. Microsoft'un COM (Bileşen Nesne Modeli) teknolojisi, benzer bir fikir (artı ek işlevsellik, tabii ki) üzerine kurulmuştur.Geç (program çalışırken) bağlama.

Daha önce bir DLL ile çalıştıysanız, DLL'nin özel bir DllMain() işlevi olduğunu zaten biliyorsunuzdur. Bu işlev, DLL'ye bir tür giriş noktası olması bakımından WinMain veya main() işlevine benzer. DLL yüklendiğinde ve kaldırıldığında işletim sistemi bu işlevi otomatik olarak çağırır. Genellikle bu işlev başka bir şey için kullanılmaz.

Bir DLL'yi bir projeye bağlamanın iki yöntemi vardır - erken (program derlemesi sırasında) ve geç (programın yürütülmesi sırasında) bağlama. Yöntemler, DLL'nin nasıl yüklendiğine ve DLL'den uygulanan ve dışa aktarılan işlevlerin nasıl çağrıldığına göre farklılık gösterir.

Erken bağlama (program derlemesi sırasında)

Bu bağlama yöntemiyle, işletim sistemi program başlatma sırasında DLL'yi otomatik olarak yükler. Ancak bu DLL'ye karşılık gelen .lib dosyasının (kütüphane dosyası) geliştirme projesine dahil edilmesi gerekir. Bu dosya, dışa aktarılan tüm DLL nesnelerini tanımlar. Bildirimler, sıradan C işlevleri veya sınıfları içerebilir. İstemcinin tek yapması gereken bu .lib dosyasını kullanmak ve DLL başlık dosyasını dahil etmektir - ve işletim sistemi bu DLL dosyasını otomatik olarak yükler. Gördüğünüz gibi, bu yöntemin kullanımı çok kolay görünüyor. her şey şeffaf. Ancak, DLL kodu değiştiğinde ve buna göre yeni bir .lib dosyası oluşturulduğunda istemci kodunun yeniden derlenmesi gerektiğini fark etmiş olmalısınız. Uygulamanız için uygun olup olmadığı size kalmış. Bir DLL, dışa aktarmak istediği işlevleri iki şekilde bildirebilir. Standart yöntem .def dosyalarını kullanmaktır. Böyle bir .def dosyası, DLL'den dışa aktarılan işlevlerin bir listesidir.

//============================================== =========== // .def dosyası KÜTÜPHANE myfirstdll.dll AÇIKLAMA "İlk DLL'im" İHRACAT MyFunction //=================== = ===================================== // İstemci kodu bool'a dahil edilecek DLL başlığı MyFunction(int parms); //============================================== =========== // işlevin DLL'de uygulanması bool MyFunction(int parms) ( // ne gerekiyorsa yapın........ )

Bu örnekte MyFunction'ın yalnızca bir işlevinin dışa aktarıldığını söylemenin güvenli olduğunu düşünüyorum. Dışa aktarılabilir nesneleri bildirmenin ikinci yöntemi özeldir, ancak çok daha güçlüdür: yalnızca işlevleri değil, aynı zamanda sınıfları ve değişkenleri de dışa aktarabilirsiniz. VisualC++ AppWizard DLL'i oluştururken oluşturulan kod parçacığına bir göz atalım.Listede yer alan yorumlar nasıl çalıştığını anlamak için yeterlidir.

//============================================== ========== // İstemci koduna dahil edilecek DLL başlığı /* Aşağıdaki ifdef bloğu, DLL'den dışa aktarmayı kolaylaştıran standart bir makro oluşturma yöntemidir. Bu DLL'nin tüm dosyaları, belirli bir MYFIRSTDLL_EXPORTS anahtarıyla derlenir. Bu anahtar, bu DLL'yi kullanan hiçbir proje için tanımlı değil. Bu nedenle, bu dosyayı içeren herhangi bir proje, MYFIRSTDLL_API işlevlerini DLL'den içe aktarılmış olarak görürken, DLL'nin kendisi dışa aktarılan işlevlerin aynısını görür. */ #ifdef MYFIRSTDLL_EXPORTS #define MYFIRSTDLL_API __declspec(dllexport) #else #define MYFIRSTDLL_API __declspec(dllimport) #endif // Sınıf test2.dll sınıfından dışa aktarılır MYFIRSTDLL_API CMyFirstFill burada; kendi yöntemlerinizi ekleyebilirsiniz. ); harici MYFIRSTDLL_API int nMyFirstDll; MYFIRSTDLL_API int fnMyFunction(void);

MYFIRSTDLL_EXPORTS anahtarı DLL derleme zamanında tanımlanır, bu nedenle __declspec(dllexport) anahtar sözcüğü, dışa aktarılan nesnelerin bildirimlerinden önce değiştirilir. Ve istemci kodu derlendiğinde, bu anahtar tanımsızdır ve istemcinin DLL'den hangi nesnelerin içe aktarıldığını bilmesi için nesnelerin önüne __declspec(dllimport) eklenir.

Her iki durumda da, istemcinin tek yapması gereken projeye myfirstdll.lib dosyasını eklemek ve DLL'den içe aktarılacak nesneleri bildiren bir başlık dosyası eklemek ve ardından bu nesneleri (fonksiyonlar, sınıflar ve değişkenler) tam olarak kullanmaktır. sanki projede yerel olarak tanımlanmış ve uygulanmış gibi. Şimdi, genellikle daha kullanışlı ve daha güçlü olan bir DLL kullanma yöntemine bakalım.

Geç bağlama (program çalışırken)

Geç bağlama kullanıldığında, DLL, program başladığında otomatik olarak değil, doğrudan gerektiği yerde kodda yüklenir. Hiçbir .lib dosyasının kullanılması gerekmez, bu nedenle DLL değiştirilirken istemci uygulamasının yeniden derlenmesi gerekmez. Bu bağlantı, tam olarak güçlüdür, çünkü hangi DLL dosyasının ne zaman yükleneceğine SİZ karar verirsiniz. Örneğin, DirectX ve OpenGL kullanan bir oyun yazıyorsunuz. Yürütülebilir dosyaya gerekli tüm kodu dahil edebilirsiniz, ancak o zaman herhangi bir şeyi anlamak imkansız olacaktır. Veya DirectX kodunu bir DLL dosyasına ve OpenGL kodunu bir başkasına koyabilir ve bunları statik olarak projeye bağlayabilirsiniz. Ancak şimdi tüm kodlar birbirine bağımlıdır, bu nedenle DirectX kodunu içeren yeni bir DLL yazarsanız, yürütülebilir dosyayı da yeniden derlemeniz gerekir. Tek kolaylık, yükleme konusunda endişelenmenize gerek olmamasıdır (her iki DLL'yi de yüklerseniz, gerçekten yalnızca birine ihtiyacınız olduğunda bellekte yer kaplarsanız, bunun bir kolaylık olup olmadığı bilinmese de). Son olarak, bence en iyi fikir, yürütülebilir dosyanın başlangıçta hangi DLL'nin yükleneceğine karar vermesine izin vermektir. Örneğin, program sistemin OpenGL hızlandırmayı desteklemediğini belirlediyse, DirectX koduyla bir DLL yüklemek, aksi takdirde OpenGL'yi yüklemek daha iyidir. Bu nedenle, geç bağlama bellekten tasarruf sağlar ve DLL ile yürütülebilir dosya arasındaki bağımlılığı azaltır. Ancak bu durumda, dışa aktarılan nesnelere bir kısıtlama uygulanır - yalnızca C tarzı işlevler dışa aktarılabilir. Program geç bağlama kullanıyorsa, sınıflar ve değişkenler yüklenemez. Arayüzleri kullanarak bu sınırlamayı nasıl aşacağımızı görelim.

Geç bağlama için tasarlanmış bir DLL, genellikle hangi nesneleri dışa aktarmak istediğini tanımlamak için bir .def dosyası kullanır. Bir .def dosyası kullanmak istemiyorsanız, dışa aktarılan işlevlerden önce __declspec(dllexport) önekini kullanabilirsiniz. Her iki yöntem de aynı şeyi yapar. İstemci, DLL'nin dosya adını Win32 LoadLibrary() işlevine geçirerek DLL'yi yükler.Bu işlev, DLL ile çalışmak için kullanılan ve artık gerekmediğinde DLL'yi bellekten kaldırmak için gerekli olan bir HINSTANCE tanıtıcısı döndürür. DLL'yi yükledikten sonra istemci, gerekli işlevin adını parametre olarak kullanarak GetProcAddress() işlevini kullanarak herhangi bir işlev için bir işaretçi alabilir.

//============================================== =========== // .def dosyası KÜTÜPHANE myfirstdll.dll AÇIKLAMA "İlk DLL'im" İHRACAT MyFunction //=================== = ====================================== /* DLL'de fonksiyon uygulaması */ bool MyFunction( int parms) ( //bir şeyler yap............ ) //====================== ==== ========================================== // İstemci kodu /* İşlev bildirimi gerçekten yalnızca aşağıdakiler için gereklidir: parametreleri tanımlayın. İşlev bildirimleri genellikle bir DLL ile birlikte gelen bir başlık dosyasında bulunur. Bir işlev bildirimindeki extern C anahtar sözcüğü, derleyiciye C değişken adlandırma kurallarını kullanmasını söyler */ extern "C" bool MyFunction(int parms); typedef bool (*MYFUNCTION)(int parms); MYFUNCTION pfnMyFunc=0; //MyFunction HINSTANCE işaretçisi hMyDll = ::LoadLibrary("myfirstdll.dll"); if(hMyDll != NULL) ( //pfnMyFunc= (MYFUNCTION)::GetProcAddress(hMyDll, "MyFunction"); //Eğer başarısız olursa, DLL'yi kaldırın if(pfnMyFunc== 0) ( :: FreeLibrary(hMyDll) ; return; ) //fonksiyonu çağırın bool result = pfnMyFunc(parms); //Artık ihtiyacımız yoksa DLL'yi kaldırın::FreeLibrary(hMyDll); )

Gördüğünüz gibi, kod oldukça basit. Ve şimdi "sınıflar" ile çalışmanın nasıl uygulanabileceğini görelim. Daha önce belirtildiği gibi, geç bağlama kullanılıyorsa, DLL'den sınıfları içe aktarmanın doğrudan bir yolu yoktur, bu nedenle sınıfın "işlevselliğini", yapıcı ve yıkıcı hariç tüm genel işlevleri içeren bir arabirimle uygulamamız gerekir. Arayüz, yalnızca sanal soyut üye işlevleri içeren normal bir C/C++ yapısı olacaktır. DLL'deki asıl sınıf bu yapıdan miras alacak ve arabirimde tanımlanan tüm işlevleri uygulayacaktır. Şimdi, istemci uygulamasından bu sınıfa erişmek için tek yapmamız gereken, sınıf örneğine karşılık gelen C tarzı işlevleri dışa aktarıp, istemcinin kullanabilmesi için tanımladığımız arayüze bağlamak. Bu yöntemi uygulamak için, biri arayüzü oluşturacak ve ikincisi, onunla çalışmayı bitirdikten sonra arayüzü silecek olan iki işleve daha ihtiyaç vardır. Bu fikrin örnek bir uygulaması aşağıda gösterilmiştir.

//============================================== =========== // .def dosyası KÜTÜPHANE myinterface.dll AÇIKLAMA "I_MyInterface I_MyInterface İHRACAT GetMyInterface FreeMyInterface'i uygular //================== == ===================================== // Dll ve istemcide kullanılan başlık dosyası, // // I_MyInterface.h struct I_MyInterface ( virtual bool Init(int parms)=0; virtual bool Release()=0; virtual void DoStuff() =0; ); /* Dışa aktarılan işlevlerin bildirimleri Dll ve işlev işaretçisi kolay yükleme ve işlevlerle çalışma için tür tanımları Derleyiciye C tarzı işlevlerin kullanıldığını söyleyen harici "C" önekine dikkat edin */ extern "C" ( HRESULT GetMyInterface(I_MyInterface ** pInterface); typedef HRESULT (*GETINTERFACE) )(I_MyInterface ** pInterface); HRESULT FreeMyInterface(I_MyInterface ** pInterface); typedef HRESULT (*FREEINTERFACE)(I_MyInterface ** pInterface); ) //=========== ===== ============================ ============== //Arayüzün Dll'de uygulanması // MyInterface.h sınıfı CMyClass: public I_MyInterface ( public: bool Init(int parms); boolRelease(); geçersiz DoStuff(); CMyClass(); ~CMyClass(); //sınıfın diğer üyeleri...... özel: //sınıfın herhangi bir üyesi............. ); //============================================== =========== // Bir arabirim oluşturan ve yok eden dışa aktarılan işlevler // Dllmain.h HRESULT GetMyInterface(I_MyInterface ** pInterface) ( if(!*pInterface) ( *pInterface= new CMyClass; return S_OK ; ) E_FAIL döndürür; ) HRESULT FreeMyInterface(I_MyInterface ** pInterface) ( if(!*pInterface) E_FAIL döndürür; *pInterface; *pInterface= 0; S_OK döndürür; ) //========= = =============================================== // İstemci kodu //Arayüz bildirimleri ve işlev çağrıları GETINTERFACE pfnInterface=0;//GetMyInterface işlevine işaretçi I_MyInterface * pInterface =0;//MyInterface yapısına işaretçi HINSTANCE hMyDll = ::LoadLibrary("myinterface.dll"); if(hMyDll != NULL) ( //pfnInterface= (GETINTERFACE)::GetProcAddress(hMyDll, "GetMyInterface" işlevinin adresini belirleyin); //Önceki işlem başarısız olursa DLL'yi kaldırın if(pfnInterface == 0) ( ::FreeLibrary (hMyDll); return; ) //HRESULT hr = pfnInterface(&pInterface); //Eğer başarısız olursa kaldır if(FAILED(hr)) ( ::FreeLibrary(hMyDll); return; ) //Arayüz yüklendiğinde, pInterface->Init(1); pInterface->DoStuff(); pInterface->Release(); //Release interface FREEINTERFACE pfnFree = (FREEINTERFACE)::GetProcAddress(hMyDll,"FreeMyInterface"); fonksiyonlarını çağırabilirsiniz. (pfnÜcretsiz! = 0) pfnFree(&hMyDll); // DLL'yi kaldır::FreeLibrary(hMyDll); )

Bu bilgiler, arayüzleri kullanmanın tüm rahatlığını hissetmeniz için yeterlidir. Mutlu programlama!

DLL yüklemenin üç yolu vardır:

a) örtük;

c) gecikmeli.

Örtük DLL yüklemesini düşünün. Örtük DLL yüklemesi için tasarlanmış bir uygulama oluşturmak için şunlara sahip olmanız gerekir:

Bir kitaplık, DLL'den kullanılan nesnelerin açıklamalarını içeren bir dosya içerir (işlev prototipleri, sınıf ve tür bildirimleri). Bu dosya derleyici tarafından kullanılır.

İçe aktarılan tanımlayıcıların listesini içeren LIB dosyası. Bu dosya proje ayarlarına eklenmelidir (bağlayıcı tarafından kullanılan kitaplıklar listesine).

Proje olağan şekilde derlenir. Bağlayıcı (bağlayıcı, bağlayıcı) nesne modüllerini ve LIB dosyasını kullanmanın yanı sıra içe aktarılan tanımlayıcılara olan bağlantıları da hesaba katarak önyüklenebilir bir EXE modülü oluşturur. Bu modül içinde, bağlayıcı ayrıca gerekli tüm DLL modüllerinin adlarını listeleyen bir içe aktarma bölümü yerleştirir. Her DLL için içe aktarma bölümü, yürütülebilir dosyanın kodunda hangi işlev ve değişken adlarına başvurulduğunu gösterir. Bu bilgiler işletim sistemi yükleyicisi tarafından kullanılacaktır.

İstemci uygulamasının yürütülmesi aşamasında ne olur? EXE modülünü başlattıktan sonra işletim sistemi yükleyicisi aşağıdaki işlemleri gerçekleştirir:

1. Yeni bir süreç için sanal bir adres alanı oluşturur ve bunun üzerine yürütülebilir bir modül yansıtır;

2. Gerekli DLL'leri belirleyerek ve ayrıca bunları işlemin adres alanına yansıtarak içe aktarma bölümünü ayrıştırır.

Bir DLL, başka bir DLL'den işlevleri ve değişkenleri alabilir. Bu, aynı adımları tekrarlamanız gereken kendi içe aktarma bölümüne sahip olabileceği anlamına gelir. Sonuç olarak, işlemi başlatmak oldukça uzun zaman alabilir.

EXE modülü ve tüm DLL modülleri işlemin adres alanına eşlendikten sonra, birincil iş parçacığı çalışmaya hazırdır ve uygulama çalışmaya başlar.

Örtük yüklemenin dezavantajları, işlevleri çağrılmasa bile kitaplığın zorunlu olarak yüklenmesi ve buna bağlı olarak, bağlantı sırasında kitaplığın bulunması için zorunlu gerekliliktir.

Açık DLL yüklemesi, kodu biraz daha karmaşık hale getirerek yukarıda belirtilen dezavantajları ortadan kaldırır. Programcının kendisi DLL'yi yüklemek ve dışa aktarılan işlevleri bağlamakla ilgilenmelidir. Ancak açık yükleme, DLL'yi gerektiği gibi yüklemenize ve programın DLL eksik olduğunda ortaya çıkan durumları ele almasına olanak tanır.

Açık yükleme durumunda, bir DLL ile çalışma süreci üç aşamada gerçekleşir:

1. İşlevi kullanarak DLL'yi indirin Yük Kitaplığı(veya genişletilmiş karşılığı LoadLibraryEx). Başarılı yükleme durumunda, işlev, bu DLL'ye daha fazla erişime izin veren HMODULE türünde bir hLib tanıtıcısı döndürür.

2. İşlev çağrıları GetProcAdresi gerekli işlevlere veya diğer nesnelere işaretçiler elde etmek için. İlk parametre olarak, fonksiyon GetProcAdresi hLib tanımlayıcısını ikinci parametre olarak alır - içe aktarılan işlevin adıyla dizenin adresi. Ortaya çıkan işaretçi daha sonra istemci tarafından kullanılır. Örneğin, bir işleve işaretçiyse, istenen işlev çağrılır.

3. Yüklenen dinamik kitaplığa artık ihtiyaç duyulmadığında, işlevi çağırarak kitaplığın serbest bırakılması önerilir. ücretsiz kitaplık. Bir kitaplığı boşaltmak, işletim sisteminin onu hemen bellekten kaldıracağı anlamına gelmez. Boşaltma gecikmesi, aynı DLL'ye bir süre sonra bazı işlemlerle tekrar ihtiyaç duyulduğunda sağlanır. Ancak RAM ile ilgili sorunlar varsa, Windows önce serbest bırakılan kitaplıkları bellekten kaldırır.

Bir DLL dosyasının tembel yüklenmesini düşünün. Gecikmeli yükleme DLL'si, kod kendisinden dışa aktarılan bir tanımlayıcıya erişene kadar yüklenmeyen, dolaylı olarak bağlantılı bir DLL'dir. Bu tür DLL'ler aşağıdaki durumlarda faydalı olabilir:

Bir uygulama birden çok DLL kullanıyorsa, yükleyicinin tüm DLL'leri işlemin adres alanıyla eşleştirmesi gerektiğinden, başlatılması uzun zaman alabilir. Geç yüklenen DLL'ler, uygulamanın yürütülmesi sırasında DLL'nin yüklenmesini dağıtarak bu sorunu çözer.

Uygulama, işletim sisteminin farklı sürümlerinde çalışacak şekilde tasarlanmışsa, bazı işlevler yalnızca işletim sisteminin sonraki sürümlerinde görünebilir ve mevcut sürümde kullanılmayabilir. Ancak program belirli bir işlevi çağırmazsa, bunun için DLL'ye gerek yoktur ve güvenle çalışmaya devam edebilir. Olmayan bir şeyden bahsederken

işlevi, kullanıcıya uygun bir uyarı verilmesini sağlayabilirsiniz.

Gecikmeli yükleme yöntemini uygulamak için, yalnızca gerekli içe aktarma kitaplığı MyLib.lib'i değil, aynı zamanda sistem içe aktarma kitaplığı delayimp.lib'i de bağlayıcı kitaplıklar listesine eklemek gerekir. Ek olarak, linker seçeneklerinde / delayload:MyLib.dll bayrağını eklemeniz gerekir.

Listelenen ayarlar, bağlayıcının aşağıdaki işlemleri gerçekleştirmesine neden olur:

EXE modülünde özel bir işlev uygulayın
gecikmeLoadHelper;

İşletim sistemi yükleyicisinin uygulama yükleme aşaması sırasında bu kitaplığı dolaylı olarak yüklemeye çalışmaması için yürütülebilir dosyanın içe aktarma bölümünden MyLib.dll'yi kaldırın;

MyLib.dll'den içe aktarılan işlevlerin bir listesiyle EXE dosyasına yeni bir ertelenmiş içe aktarma bölümü ekleyin;

İşlev çağrılarını DLL'den çağrılara dönüştürün
gecikmeLoadhelper.

Uygulama yürütme aşamasında, delayLoadHelper çağrılarak DLL'den bir işlev çağrısı uygulanır. Bu işlev, tembel içe aktarma bölümündeki bilgileri kullanarak önce LoadLibrary'yi ve ardından GetprocAddress'i çağırır. DLL işlevinin adresini alan delayLoadHelper, bu DLL işlevinin gelecekte doğrudan çağrılmasını sağlar. DLL'deki her işlevin, ilk çağrıldığında ayrı ayrı yapılandırıldığını unutmayın.

Bir DLL dosyasını yüklemenin iki yolu vardır: açık bağlantı ve örtük bağlantı.

Örtülü bağlama, yüklenen DLL'nin işlevlerini çağıran dosyanın içe aktarma bölümüne ekler. Böyle bir dosya başlatıldığında, işletim sistemi yükleyicisi içe aktarma bölümünü analiz eder ve belirtilen tüm kitaplıkları içerir. Basitliği nedeniyle bu yöntem çok popülerdir; ancak basitlik basitliktir ve örtük bağlantının bazı sakıncaları ve sınırlamaları vardır:

program tüm oturum boyunca hiçbirini çağırmasa bile, bağlı tüm DLL'ler her zaman yüklenir;

gerekli DLL'lerden en az biri eksikse (veya DLL en az bir gerekli işlevi dışa aktarmıyorsa) - yürütülebilir dosyanın yüklenmesi "Dinamik bağlantı kitaplığı bulunamadı" mesajıyla (veya buna benzer bir şey) durdurulur - olsa bile Bu DLL'nin olmaması, programı yürütmek için kritik değildir. Örneğin, bir metin düzenleyici, tabloları, grafikleri, formülleri ve diğer küçük bileşenleri görüntüleyen bir yazdırma modülü olmadan minimal bir yapılandırmada iyi çalışabilir, ancak bu DLL'ler kapalı bir bağlantı tarafından yüklenirse - beğenin veya beğenmeyin, yapmanız gerekir. onları "çek".

DLL'ler şu sırayla aranır: çağıran dosyayı içeren dizinde; işlemin geçerli dizininde; %Windows%System% sistem dizininde; %Windows% ana dizininde; PATH değişkeninde belirtilen dizinlerde. Farklı bir arama yolu ayarlamak imkansızdır (veya daha doğrusu mümkündür, ancak bu sistem kayıt defterinde değişiklik yapılmasını gerektirir ve bu değişiklikler sistemde çalışan tüm işlemleri etkiler - bu iyi değildir).

Açık bağlantı, tüm bu eksiklikleri ortadan kaldırır - bazı kod karmaşıklığı pahasına. Programcının kendisi DLL'yi yüklemek ve dışa aktarılan işlevleri bağlamakla ilgilenmek zorunda kalacak (hata kontrolünü unutmadan, aksi takdirde bir noktada her şey sistemin donmasıyla sona erecek). Ancak açık bağlantı, DLL'yi gerektiği gibi yüklemenize izin verir ve programcıya, DLL'nin olmadığı durumları bağımsız olarak ele alma fırsatı verir. Daha ileri gidebilirsiniz - programdaki DLL adını açıkça ayarlamayın, ancak böyle bir dizini dinamik kitaplıkların varlığı için tarayın ve bulunanların tümünü uygulamaya bağlayın. Eklenti destek mekanizması, popüler FAR dosya yöneticisinde (sadece içinde değil) bu şekilde çalışır.

Doğduğundan beri (veya biraz sonra), Windows işletim sistemi, en sık kullanılan işlevlerin uygulamalarını içeren DLL'leri (Dinamik Bağlantı Kitaplığı) kullandı. Windows'un ardılları - NT ve Windows 95 ve OS/2 - ayrıca işlevlerinin çoğu için DLL'lere bağlıdır.

DLL oluşturmanın ve kullanmanın birkaç yönünü düşünün:

  • DLL'lerin statik olarak nasıl bağlanacağı;
  • DLL'lerin dinamik olarak nasıl yükleneceği;
  • DLL'ler nasıl oluşturulur;
  • MFC DLL'lerine uzantılar nasıl oluşturulur.

DLL kullanımı

DLL kullanmayan bir Windows uygulaması oluşturmak neredeyse imkansızdır. DLL, Win32 API'sinin tüm işlevlerini ve Win32 işletim sistemlerinin sayısız diğer işlevlerini içerir.

Genel olarak konuşursak, DLL'ler yalnızca kitaplıklarda toplanan işlev koleksiyonlarıdır. Ancak, statik kuzenlerinden (.lib dosyaları) farklı olarak, DLL'ler bir bağlayıcı kullanarak doğrudan yürütülebilir dosyalara bağlanmaz. Yürütülebilir dosya yalnızca konumlarıyla ilgili bilgileri içerir. Program yürütülürken tüm kitaplık yüklenir. Bu, farklı işlemlerin aynı bellek içi kitaplıkları paylaşmasına izin verir. Bu yaklaşım, birçok paylaşılan kitaplık kullanan birden çok uygulama için gereken bellek miktarını azaltmanıza ve EXE dosyalarının boyutunu kontrol etmenize olanak tanır.

Ancak, kitaplık yalnızca bir uygulama tarafından kullanılıyorsa, onu düzenli, statik hale getirmek daha iyidir. Tabii ki, içerdiği işlevler yalnızca bir programda kullanılacaksa, uygun kaynak dosyayı içine kolayca yapıştırabilirsiniz.

Çoğu zaman, bir proje bağlantı zamanında bir DLL'ye statik veya dolaylı olarak bağlanır. Program yürütme sırasında bir DLL dosyasının yüklenmesi işletim sistemi tarafından kontrol edilir. Ancak, uygulama çalışırken bir DLL açık veya dinamik olarak yüklenebilir.

Kitaplıkları İçe Aktar

Bir DLL'yi statik olarak bağlarken, .lib dosyasının adı, komut satırındaki veya Developer Studio'nun Proje Ayarları iletişim kutusunun Bağlantı sekmesindeki diğer bağlayıcı seçenekleri arasında tanımlanır. Ancak, kullanılan .lib dosyası bir DLL dosyasını dolaylı olarak bağlarken, normal bir statik kitaplık değildir. Bu tür .lib dosyaları denir kitaplıkları içe aktar(kütüphaneleri içe aktarın). Kitaplık kodunun kendisini içermezler, ancak yalnızca her şeyin depolandığı DLL dosyasından dışa aktarılan tüm işlevlere bağlanırlar. Sonuç olarak, içe aktarma kitaplıkları DLL dosyalarından daha küçük olma eğilimindedir. Yaratılış yöntemlerine daha sonra döneceğiz. Şimdi de dinamik kitaplıkların örtük olarak bağlanmasıyla ilgili diğer konulara bakalım.

Arayüz anlaşması

Yerel kütüphaneleri veya üçüncü şahıslara ait kütüphaneleri kullanırken, fonksiyon çağrısını prototipiyle eşleştirmeye dikkat etmeniz gerekecektir.

Dünya mükemmel olsaydı, programcıların kütüphaneleri eklerken fonksiyon arayüzlerini eşleştirme konusunda endişelenmeleri gerekmezdi - hepsi aynı olurdu. Bununla birlikte, dünya mükemmel olmaktan uzaktır ve birçok büyük program, C++ olmadan çeşitli kütüphaneler kullanılarak yazılmaktadır.

Varsayılan olarak, Visual C++'da işlev arabirimleri C++ kurallarına göre uyumludur. Bu, parametrelerin sağdan sola yığına itildiği ve işlevden çıkıp adını genişlettiğinde çağıranın bunları yığından çıkarmaktan sorumlu olduğu anlamına gelir. Ad yönetimi, bağlayıcının aşırı yüklenmiş işlevler arasında ayrım yapmasına olanak tanır, ör. aynı ada sahip ancak farklı argüman listelerine sahip işlevler. Ancak, eski C kitaplığında genişletilmiş adlara sahip işlevler yoktur.

Diğer tüm C işlevi çağırma kuralları, C++ işlevi çağırma kurallarıyla aynı olsa da, C kitaplıkları işlev adlarını genişletmez. Yalnızca bir alt çizgi (_) ile başlarlar.

Bir C kitaplığını bir C++ uygulamasına bağlamanız gerekiyorsa, o kitaplıktan tüm işlevleri C biçiminde harici olarak bildirmeniz gerekir:

Extern "C" int MyOldCFunction(int myParam);

Çoğu C kitaplığı başlığının C++ projelerinde kullanılması amaçlanmamasına rağmen, kitaplık işlev bildirimleri genellikle bu kitaplığın başlık dosyasına yerleştirilir. Bu durumda, başlık dosyasının bir kopyasını oluşturmalı ve kullanılan tüm kitaplık işlevlerinin bildirimine extern "C" değiştiricisini eklemelisiniz. Extern "C" değiştiricisi, eski C başlık dosyasının #tinclude yönergesi kullanılarak bağlandığı tüm bloğa da uygulanabilir.Böylece, her işlevi ayrı ayrı değiştirmek yerine, sadece üç satırla yapabilirsiniz:

Dış "C" ( #include "MyCLib.h")

Windows'un eski sürümlerine yönelik programlar, Windows API işlevleri için PASCAL işlevi çağırma kurallarını da kullandı. Daha yeni programlar, _stdcall'a dönüştüren winapi değiştiricisini kullanmalıdır. Standart bir C veya C++ işlev arabirimi olmasa da, Windows API işlevlerini çağırmak için kullanılan arabirimdir. Ancak, genellikle tüm bunlar standart Windows başlıklarında zaten dikkate alınmıştır.

Örtülü olarak bağlantılı bir DLL yükleme

Uygulama başladığında, uygulamaya dolaylı olarak bağlı olan tüm DLL'leri bulmaya ve bunları RAM'in bu işlemin kapladığı alana yerleştirmeye çalışır. İşletim sistemi DLL dosyalarını aşağıdaki sırayla arar.

  • EXE dosyasının bulunduğu dizin.
  • İşlemin geçerli dizini.
  • Windows sistem dizini.

DLL bulunamazsa, uygulama DLL'nin eksik olduğunu ve arandığını belirten bir iletişim kutusu görüntüler. Ardından işlem sonlandırılır.

Gerekli kütüphane bulunursa, işlemin RAM'ine yerleştirilir ve işlemin sonuna kadar burada kalır. Uygulama artık DLL'de bulunan işlevlere erişebilir.

DLL'lerin dinamik yüklenmesi ve boşaltılması

Bir uygulama RAM'e ilk yüklendiğinde Windows'un bir DLL'ye dinamik olarak bağlanması yerine, bir programı çalışma zamanında bir kitaplık modülüne bağlayabilirsiniz (bu şekilde, uygulama oluşturma sırasında bir içe aktarma kitaplığı kullanmanız gerekmez). Özellikle, kullanıcı için hangi DLL'lerin mevcut olduğunu belirleyebilir veya kullanıcının hangi DLL'lerin yükleneceğini seçmesine izin verebilirsiniz. Böylece, farklı eylemler gerçekleştiren aynı işlevleri uygulayan farklı DLL'ler kullanabilirsiniz. Örneğin, bağımsız iletişim için tasarlanmış bir uygulama, çalışma zamanında TCP/IP için bir DLL veya başka bir protokol yüklenip yüklenmeyeceğine karar verebilir.

Normal bir DLL yükleme

Bir DLL dosyasını dinamik olarak yüklerken yapılacak ilk şey, kütüphane modülünü işlem belleğine yerleştirmektir. Bu işlem, işlev kullanılarak gerçekleştirilir. ::Kütüphaneyi Yükle tek bir argümanı olan - yüklenecek modülün adı. İlgili program parçası şöyle görünmelidir:

HINSTANCE hMyDll; :: if((hMyDll=:: LoadLibrary("MyDLL"))==NULL) ( /* DLL yüklenemedi */ ) else ( /* Uygulamanın hMyDll */ aracılığıyla DLL işlevlerini kullanma izni var)

Farklı bir uzantı belirtmediğiniz sürece, Windows için varsayılan kitaplık dosya uzantısı .dll'dir. Dosya adında bir yol da belirtilmişse, dosyayı aramak için yalnızca bu yol kullanılacaktır. Aksi takdirde, Windows, exe dosyasının yüklendiği dizinden başlayarak ve PATH değerine göre devam ederek, dolaylı olarak bağlantılı DLL'lerle aynı şekilde dosyayı arayacaktır.

Windows dosyayı bulduğunda, tam yolu, işlem tarafından önceden yüklenmiş DLL'lerin yolu ile karşılaştırılacaktır. Bir eşleşme bulunursa, uygulamanın bir kopyasını yüklemek yerine, zaten bağlantılı kitaplığa bir tanıtıcı döndürülür.

Dosya bulunursa ve kitaplık başarıyla yüklendiyse, işlev ::Kütüphaneyi Yükle kitaplık işlevlerine erişmek için kullanılan tanıtıcısını döndürür.

Kütüphane fonksiyonlarını kullanmadan önce adreslerini almanız gerekir. Bunu yapmak için önce yönergeyi kullanın typedef bir işlev işaretçisinin türünü tanımlamak ve bu yeni türden bir değişkeni aşağıdaki gibi tanımlamak için:

// PFN_MyFunction yazın // bir işaretçiyi karakter arabelleğine alan ve int typedef int (WINAPI *PFN_MyFunction)(char *) türünde bir değer döndüren bir işleve işaretçi bildirir. :: PFN_MyFunction pfnMyFunction;

Ardından, işlevlerin adreslerini, örneğin MyFunction adlı bir işlevin adresini belirleyebileceğiniz bir kitaplık tanımlayıcısı almalısınız:

HMyDll=::LoadLibrary("MyDLL"); pfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll,"MyFunction"); :: int iCode=(*pfnMyFunction)("Merhaba");

İşlev adresi, işlev kullanılarak belirlenir ::GetProcAdresi, kütüphanenin adı ve işlevin adı iletilmelidir. İkincisi, DLL'den dışa aktarıldığı biçimde aktarılmalıdır.

Bir işleve, dışa aktarıldığı seri numarasıyla da başvurabilirsiniz (bu durumda, kitaplığı oluşturmak için bir def dosyası kullanılmalıdır, bu daha sonra tartışılacaktır):

PfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll, MAKEINTRESOURCE(1));

Dinamik bağlantı kitaplığıyla çalışmayı bitirdikten sonra, işlev kullanılarak süreç belleğinden kaldırılabilir: ücretsiz kitaplık:

::FreeLibrary(hMyDll);

MFC Dinamik Kitaplık Uzantılarını Yükleme

İşlevler yerine DLL'ler için MFC uzantıları yüklerken (daha sonra ayrıntılı olarak anlatılacaktır) Yük Kitaplığı Ve ücretsiz kitaplık işlevler kullanılır AfxLoadLibrary Ve afxFreeKütüphane. İkincisi, Win32 API işlevleriyle neredeyse aynıdır. Yalnızca ek olarak DLL uzantısı tarafından başlatılan MFC yapılarının diğer iş parçacıkları tarafından bozulmadığını garanti ederler.

DLL kaynakları

Dinamik yükleme, standart uygulama kaynaklarını yüklemek için MFC tarafından kullanılan DLL kaynakları için de geçerlidir. Bunu yapmak için önce işlevi çağırmanız gerekir. Yük Kitaplığı ve DLL'yi belleğe yerleştirin. Daha sonra işlevi kullanarak AfxSetResourceTutucu yeni yüklenen kitaplıktan kaynakları almak için program penceresini hazırlamanız gerekir. Aksi takdirde, kaynaklar işlemin yürütülebilir dosyasına eklenen dosyalardan yüklenecektir. Bu yaklaşım, örneğin farklı diller için farklı kaynak kümeleri kullanmanız gerekiyorsa yararlıdır.

Yorum. işlevi kullanma Yük Kitaplığı ayrıca yürütülebilir dosyaları belleğe yükleyebilirsiniz (bunları çalıştırmayın!). Yürütülebilir modülün tanıtıcısı, işlevler çağrılırken kullanılabilir. Kaynak Bul Ve YükKaynak uygulama kaynaklarını bulmak ve indirmek için. İşlevi kullanarak modülleri bellekten çıkarın ücretsiz kitaplık.

Normal bir DLL örneği ve yükleme yolları

Burada, yalnızca bir ileti görüntüleyen MyFunction adlı tek bir işlev içeren MyDLL adlı dinamik olarak bağlantılı bir kitaplığın kaynak kodu yer almaktadır.

İlk olarak, makro sabiti başlık dosyasında tanımlanır İHRACAT. Bir dinamik bağlantı kitaplığında bir işlev tanımlarken bu anahtar sözcüğün kullanılması, bağlayıcıya işlevin diğer programlar tarafından kullanılabilir olduğunu söylemenize ve böylece içeri aktarma kitaplığında listelenmesine neden olur. Ek olarak, böyle bir fonksiyon, tıpkı bir pencere prosedürü gibi, sabit kullanılarak tanımlanmalıdır. GERİ ÇAĞIRMAK:

MyDLL.h#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction(char *str);

Kitaplık dosyası da normal Windows C dosyalarından biraz farklıdır. İçinde, bir işlev yerine kazanmak bir fonksiyon var DllAna. Bu işlev, daha sonra tartışılacak olan başlatma işlemini gerçekleştirmek için kullanılır. Bir kütüphanenin yüklendikten sonra fonksiyonlarının çağrılabilmesi için bellekte kalması için, dönüş değerinin TRUE olması gerekir:

MyDLL.c #include #include "MyDLL.h" int WINAPI DllMain(HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) ( return TRUE; ) EXPORT int CALLBACK MyFunction(char *str) ( MessageBox(NULL,str,"DLL'den İşlev",MB_OK); dönüş 1 ; )

Bu dosyaları çevirdikten ve bağladıktan sonra iki dosya görünür - MyDLL.dll (dinamik bağlantı kitaplığının kendisi) ve MyDLL.lib (içe aktarma kitaplığı).

Bir uygulama tarafından bir DLL'nin örtük bağlantısına bir örnek

MyDLL.dll kitaplığındaki MyFunction işlevini kullanan basit bir uygulamanın kaynak kodu:

#Dahil etmek #include "MyDLL.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) ( int iCode=MyFunction("Hello"); return 0; )

Bu program normal bir Windows programına benziyor, ki bu da öyle. Ancak, DLL kitaplığından MyFunction işlevini çağırmaya ek olarak, kaynak metninin MyDLL.h kitaplığının başlık dosyasını da içerdiğine dikkat edilmelidir. Uygulamayı oluşturma aşamasında ona bağlanmak da gereklidir. kitaplığı içe aktar MyDLL.lib (bir DLL dosyasını bir yürütülebilir dosyaya dolaylı olarak bağlama işlemi).

MyFunction kodunun kendisinin MyApp.exe dosyasında bulunmadığını anlamak son derece önemlidir. Bunun yerine, yalnızca MyDLL.dll dosyasına bir başvuru ve bu dosyada bulunan MyFunction işlevine bir başvuru vardır. MyApp.exe dosyasının çalışması için MyDLL.dll dosyasının olması gerekir.

MyDLL.h başlık dosyası, tıpkı windows.h dosyasının dahil edildiği gibi MyApp.c kaynak dosyasına dahil edilir. Bağlantı için MyDLL.lib içe aktarma kitaplığının dahil edilmesi, tüm Windows içe aktarma kitaplıklarının dahil edilmesiyle aynıdır. MyApp.exe çalışırken, tüm standart Windows dinamik bağlantı kitaplıkları gibi MyDLL.dll'ye bağlanır.

Bir uygulama tarafından bir DLL dosyasının dinamik olarak yüklenmesine bir örnek

Dinamik kitaplık yüklemesini kullanarak MyDLL.dll kitaplığındaki MyFunction işlevini kullanan basit bir uygulamanın tam kaynak kodunu burada bulabilirsiniz:

#Dahil etmek typedef int (WINAPI *PFN_MyFunction)(char *); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) ( HINSTANCE hMyDll; if((hMyDll=LoadLibrary("MyDLL"))==NULL)dönüş 1; MyFunction"); int iCode=(*pfnMyFunction)("Merhaba"); FreeLibrary(hMyDll); 0 döndür; )

DLL oluşturma

Şimdi, DLL'lerin uygulamalarda nasıl çalıştığının ilkelerine aşina olduktan sonra, onları nasıl oluşturacağımıza bakalım. Bir uygulama geliştirirken, birden çok işlem tarafından erişilen işlevler tercihen bir DLL dosyasına yerleştirilmelidir. Bu, Windows'ta belleğin daha verimli kullanılmasını sağlar.

Yeni bir DLL projesi oluşturmanın en kolay yolu, birçok işi sizin için otomatik olarak yapan AppWizard'dır. Bu bölümde açıklananlar gibi basit DLL'ler için Win32 Dynamic-Link Library proje türünü seçmelisiniz. Yeni projeye DLL'yi oluşturmak için gerekli tüm parametreler verilecektir. Kaynak dosyaların projeye manuel olarak eklenmesi gerekecektir.

Ancak, belgeler ve görünümler gibi MFC işlevlerinden tam olarak yararlanmayı planlıyorsanız veya bir OLE otomasyon sunucusu oluşturmayı planlıyorsanız, MFC AppWizard (dll) proje türü daha iyi bir seçimdir. Bu durumda, dinamik kitaplıkları bağlamak için projeye parametre atamaya ek olarak, sihirbaz bazı ek işler yapacaktır. MFC kitaplıklarına ve aşağıdakilerden türetilen uygulama sınıfı nesnesinin DLL'sindeki açıklama ve uygulamayı içeren kaynak dosyalara gerekli referanslar CWinApp.

Bazen test uygulaması olarak önce bir MFC AppWizard (dll) projesi oluşturmak ve ardından bunun bir parçası olarak bir DLL oluşturmak uygun olabilir. Sonuç olarak, gerekirse DLL otomatik olarak oluşturulacaktır.

DLL ana işlevi

Çoğu DLL, uygulamalara aktarılan ve uygulamalar tarafından kullanılan, büyük ölçüde bağımsız işlevlerin koleksiyonlarıdır. Dışa aktarma amaçlı işlevlere ek olarak, her DLL'nin bir işlevi vardır. DllAna. Bu işlev, bir DLL dosyasını başlatmak ve temizlemek için tasarlanmıştır. Fonksiyonları değiştirdi LibMain Ve WEP Windows'un önceki sürümlerinde kullanılır. En basit fonksiyonun yapısı DllAnaörneğin şöyle görünebilir:

BOOL WINAPI DllMain (HANDLE hInst,DWORD dwReason, LPVOID IpReserved) ( BOOL bAllWentWell=TRUE; anahtar (dwReason) ( case DLL_PROCESS_ATTACH: // İşlem başlatma. break; case DLL_THREAD_ATTACH: // Temiz DLL_THREAD_ATTHAD // Thread başlatma. break; thread yapıları break; case DLL_PROCESS_DETACH: // proses yapılarını temizle; ) if(bAllWentWell) TRUE döndürür; yoksa FALSE döndürür; )

İşlev DllAna birkaç kez çağrıldı. Çağrılma nedeni, aşağıdaki değerlerden birini alabilen dwReason parametresi tarafından belirlenir.

Bir DLL bir işlem tarafından ilk kez yüklendiğinde, bir işlev çağrılır. DllAna DLL_PROCESS_ATTACH'a eşit dwReason ile. Bir işlem yeni bir iş parçacığı oluşturduğunda, DllMainO, DLL_THREAD_ATTACH'a eşit dwReason ile çağrılır (bu durumda dwReason, DLL_PROCESS_ATTACH'a eşit olduğundan, ilk iş parçacığı dışında).

DLL ile işlemin sonunda, işlev DllAna DLL_PROCESS_DETACH'a eşit dwReason parametresiyle çağrılır. Bir iş parçacığı yok edildiğinde (birincisi hariç), dwReason DLL_THREAD_DETACH'a eşit olacaktır.

DLL'nin ihtiyaç duyduğu işlemler ve iş parçacıkları için tüm başlatma ve temizleme işlemleri, önceki örnekte gösterildiği gibi dwReason değerine göre gerçekleştirilmelidir. İşlem başlatma, genellikle, paylaşılan dosyaların yüklenmesi ve kitaplıkların başlatılması gibi, iş parçacıkları tarafından paylaşılan kaynakları tahsis etmekle sınırlıdır. İş parçacığı başlatma, yerel bellek başlatma gibi iş parçacığına özgü modları ayarlamak için kullanılır.

Bir DLL, kitaplığı çağıran uygulamaya ait olmayan kaynakları içerebilir. DLL işlevleri DLL kaynakları üzerinde çalışıyorsa, bir hInst tanıtıcısını özel bir yerde depolamak ve DLL'den kaynakları yüklerken kullanmak kesinlikle yararlı olacaktır. IpReserved işaretçisi, Windows dahili kullanımı için ayrılmıştır. Bu nedenle, uygulama bunu talep etmemelidir. Sadece değerini kontrol edebilirsiniz. DLL dinamik olarak yüklendiyse, NULL olacaktır. Statik yükleme için bu işaretçi boş olmayacaktır.

Başarılı bir şekilde tamamlandığında, işlev DllAna DOĞRU döndürmelidir. Bir hata oluşursa, YANLIŞ döndürülür ve sonraki işlemler sonlandırılır.

Yorum. Kendi DllMain() işlevinizi yazmazsanız, derleyici yalnızca TRUE değerini döndüren standart bir sürüm içerecektir.

İşlevleri bir DLL'den dışa aktarma

Bir uygulamanın DLL işlevlerine erişmesi için, her işlevin DLL'nin dışa aktarılan işlevler tablosunda bir satırı işgal etmesi gerekir. Derleme zamanında bu tabloya bir fonksiyon koymanın iki yolu vardır.

Yöntem __declspec(dllexport)

__declspec(dllexport) değiştiricisiyle önek ekleyerek DLL'den bir işlevi dışa aktarabilirsiniz. Ayrıca MFC, AFX_CLASS_EXPORT, AFX_DATA_EXPORT ve AFX_API_EXPORT dahil olmak üzere __declspec(dllexport) tanımlayan birkaç makro içerir.

__declspec yöntemi, modül tanımlama (.def) dosyalarıyla çalışan ve dışa aktarma işlemi üzerinde size daha fazla kontrol sağlayan ikinci yöntemden daha az kullanılır.

Modül tanım dosyaları

Visual C++'daki .def dosyalarının sözdizimi oldukça basittir, çünkü Windows'un önceki sürümlerinde kullanılan karmaşık seçenekler artık Win32'de geçerli değildir. Aşağıdaki basit örnekten anlaşılacağı gibi, .def dosyası kitaplığın adını ve açıklamasını ve ayrıca dışa aktarılan işlevlerin bir listesini içerir:

MyDLL.def KÜTÜPHANE "MyDLL" AÇIKLAMA "MyDLL örnek bir DLL'dir" İHRACAT MyFunction @1

Bir fonksiyonun dışa aktarma satırında, önüne @ sembolü koyarak sıra numarasını belirtebilirsiniz. Bu numara daha sonra iletişim kurarken kullanılacaktır. GetProcAdresi(). Aslında, derleyici, dışa aktarılan tüm nesnelere sıra sayıları atar. Ancak, bu numaralar açıkça atanmadıkça, bunu yapma şekli biraz tahmin edilemez.

NONAME parametresini dışa aktarma dizesinde kullanabilirsiniz. Derleyicinin işlev adını DLL'nin dışa aktarma tablosuna dahil etmesini engeller:

MyFunction @1 NONAME

Bu bazen DLL dosyasında çok fazla alan kazandırabilir. Bir DLL'yi örtük olarak bağlamak için içe aktarma kitaplığını kullanan uygulamalar, örtülü bağlama sıra numaralarını otomatik olarak kullandığından farkı "fark etmez". DLL'leri dinamik olarak yükleyen uygulamaların geçmesi gerekir GetProcAdresi işlev adı değil sıra numarası.

Yukarıdakileri kullanırken, DLL'nin dışa aktarılan işlevlerinin açıklamasının def dosyası, örneğin şöyle olmayabilir:

#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction(char *str); buna benzer: extern "C" int CALLBACK MyFunction(char *str);

Sınıfları dışa aktarma

Dinamik bir kitaplıktan basit sınıfları bile dışa aktarmak için bir .def dosyası oluşturmak oldukça zor olabilir. Harici bir uygulama tarafından kullanılabilecek her işlevi açıkça dışa aktarmanız gerekecektir.

Sınıfta uygulanan bellek ayırma dosyasına bakarsanız, içinde çok sıra dışı özellikler görebilirsiniz. Örtük yapıcılar ve yıkıcılar, MFC makrolarında bildirilen işlevler, özellikle _DECLARE_MESSAGE_MAP ve programcı tarafından yazılan işlevler olduğu ortaya çıktı.

Bu işlevlerin her birini ayrı ayrı dışa aktarmak mümkün olsa da, daha kolay bir yol var. Sınıf bildiriminde AFX_CLASS_EXPORT makro değiştiricisini kullanırsanız, derleyici, uygulamanın DLL'de bulunan sınıfı kullanmasına izin veren gerekli işlevleri dışa aktarmaya özen gösterir.

Bellek DLL'si

Esasen uygulama kodunun bir parçası haline gelen statik kitaplıklardan farklı olarak, Windows'un 16 bit sürümlerindeki dinamik bağlantı kitaplıkları, bellekle biraz farklı bir şekilde ilgileniyordu. Win 16 altında, DLL'nin belleği, görevin adres alanının dışına yerleştirildi. Dinamik kütüphaneleri global belleğe yerleştirmek, onları farklı görevler arasında paylaşma imkanı sağladı.

Win32'de bir DLL, onu yükleyen işlemin belleğinde yaşar. Her işlem, yeni bir işlem onu ​​her yüklediğinde yeniden başlatılan "genel" DLL belleğinin ayrı bir kopyasıyla sağlanır. Bu, dinamik kitaplığın Winl6'da olduğu gibi paylaşılan bellekte paylaşılamayacağı anlamına gelir.

Yine de DLL veri segmenti üzerinde bir dizi karmaşık manipülasyon gerçekleştirerek, bu kütüphaneyi kullanan tüm işlemler için ortak bir bellek alanı oluşturmak mümkündür.

Diyelim ki bu DLL'yi yükleyen tüm işlemler tarafından kullanılması gereken bir tamsayı dizimiz var. Bu şu şekilde programlanabilir:

#pragma data_seg(".myseg") int paylaşılanInts ; // diğer paylaşılan değişkenler #pragma data_seg() #pragma comment(lib, "msvcrt" "-SECTION:.myseg,rws");

#pragma data_seg() yönergeleri arasında bildirilen tüm değişkenler .myseg segmentine yerleştirilir. #pragma comment() yönergesi normal bir yorum değildir. Yeni bölümü okunabilir, yazılabilir ve paylaşılabilir olarak işaretlemesi için C çalışma zamanı kitaplığına talimat verir.

Tam DLL Derlemesi

AppWizard ile dinamik kitaplık projesi oluşturulmuşsa ve .def dosyası buna göre değiştirilmişse bu yeterlidir. Proje dosyaları AppWizard'ın yardımı olmadan manuel olarak veya başka yollarla oluşturulursa, /DLL seçeneği bağlayıcının komut satırına eklenmelidir. Bu, bağımsız bir yürütülebilir dosya yerine bir DLL oluşturacaktır.

.def dosyasında bir LIBRART satırı varsa, linker komut satırında /DLL seçeneğini açıkça belirtmeniz gerekmez.

MFC, dinamik bir kitaplığın MFC kitaplıklarını nasıl kullandığına ilişkin bir dizi özel moda sahiptir. Bir sonraki bölüm bu konuya ayrılmıştır.

DLL ve MFC

Programcının dinamik kitaplıklar oluştururken MFC kullanması gerekmez. Bununla birlikte, MFC'yi kullanmak bir dizi çok önemli olasılığın önünü açar.

Bir DLL'de MFC yapısının iki kullanım düzeyi vardır. Birincisi, düzenli bir MFC tabanlı dinamik kitaplıktır, MFC DLL'si(normal MFC DLL). MFC kullanabilir, ancak DLL'ler ve uygulamalar arasında MFC nesnelerine işaretçiler iletemez. İkinci seviye şurada uygulanmaktadır: dinamik MFC uzantıları(MFC uzantıları DLL). Bu tür bir dinamik kitaplığın kullanılması, bazı ek yapılandırma çalışmaları gerektirir, ancak DLL ve uygulama arasında MFC nesnelerine işaretçileri serbestçe değiştirmenize olanak tanır.

Normal MFC DLL'leri

Normal MFC DLL'leri, MFC'yi dinamik kitaplıklarda kullanmanıza olanak tanır. Aynı zamanda, bu tür kitaplıklara erişen uygulamaların MFC temelinde oluşturulması gerekmez. MFC'yi normal DLL'lerde, MFC sınıflarına dayalı olarak DLL'de yeni sınıflar oluşturma ve bunları uygulamalara aktarma dahil olmak üzere herhangi bir şekilde kullanabilirsiniz.

Ancak, normal DLL'ler, uygulamalarla MFC'den türetilen sınıflara işaretçi alışverişi yapamaz.

Bir uygulamanın DLL ile MFC sınıflarının nesnelerine veya türevlerine işaretçi alışverişi yapması gerekiyorsa, sonraki bölümde açıklanan DLL uzantısı kullanılmalıdır.

Normal DLL mimarisi, Visual Basic ve PowerBuilder gibi diğer programlama ortamları tarafından kullanılmak üzere tasarlanmıştır.

AppWizard ile normal bir MFC DLL oluştururken, yeni bir proje türü seçilir MFC Uygulama Sihirbazı (dll). Uygulama sihirbazının ilk iletişim kutusunda, normal dinamik kitaplıklar için modlardan birini seçmelisiniz: "MFC istatistiksel olarak bağlantılı normal DLL" veya "Paylaşılan MFC DLL kullanarak normal DLL". Birincisi statik, ikincisi MFC kitaplıklarının dinamik bağlanmasını sağlar. Daha sonra Proje ayarları iletişim kutusunun Genel sekmesindeki birleşik giriş kutusunu kullanarak MFC'nin bağlantı modunu DLL olarak değiştirebilirsiniz.

MFC Durum Bilgilerini Yönetme

Her MFC işlem modülü, durumu hakkında bilgi içerir. Bu nedenle, bir DLL'nin durum bilgisi, onu çağıran uygulamanın durum bilgisinden farklıdır. Bu nedenle, doğrudan uygulamalardan çağrılan kitaplıktan dışa aktarılan işlevler, MFC'ye hangi durum bilgilerinin kullanılacağını söylemelidir. MFC DLL'lerini kullanan normal bir MFC DLL'sinde, herhangi bir MFC alt yordamını çağırmadan önce, dışa aktarılan işlevin başına aşağıdaki satır yerleştirilmelidir:

AFX_MANAGE_STATE(AfxGetStaticModuleState()) ;

Bu ifade, bu altyordamı çağıran işlevin yürütülmesi sırasında uygun durum bilgisinin kullanımını belirler.

MFC Dinamik Uzantıları

MFC, uygulamalar tarafından bir dizi ayrı işlev olarak değil, MFC'nin uzantıları olarak algılanan DLL'ler oluşturmanıza olanak tanır. Bu tür DLL ile MFC sınıflarından türetilen yeni sınıflar oluşturabilir ve bunları uygulamalarınızda kullanabilirsiniz.

Bir uygulama ile DLL arasında MFC nesnelerine ücretsiz işaretçi değişimini etkinleştirmek için dinamik bir MFC uzantısı oluşturmanız gerekir. Bu türdeki DLL'ler, MFC dinamik uzantısı kullanan herhangi bir uygulamada olduğu gibi MFC dinamik kitaplıklarına bağlanır.

Yeni bir dinamik MFC uzantısı oluşturmak için en kolay yol, proje türünü atamak için uygulama sihirbazını kullanmaktır. MFC Uygulama Sihirbazı (dll) ve 1. adımda "MFC Extension DLL" modunu etkinleştirin. Bu, yeni projeye gerekli tüm MFC dinamik uzantı özniteliklerini verecektir. Ayrıca, bir işlev oluşturulacak DllAna DLL uzantısını başlatmak üzere bir dizi belirli işlemi gerçekleştiren bir DLL için. Bu türdeki dinamik kitaplıkların, aşağıdakilerden türetilen nesneleri içermediği ve içermemesi gerektiği belirtilmelidir. CWinApp.

Dinamik Uzantıları Başlatma

MFC çerçevesine "uyum sağlamak" için dinamik MFC uzantıları ek ilk kurulum gerektirir. İlgili işlemler fonksiyon tarafından gerçekleştirilir. DllAna. AppWizard tarafından oluşturulan bu fonksiyonun bir örneğine bakalım.

Statik AFX_EXTENSION_MODULE MyExtDLL = ( NULL, NULL ) ; extern "C" int APIENTRY DllMain(HINSTANCE örneği, DWORD dwReason, LPVOID IpReserved) ( if (dwReason == DLL_PROCESS_ATTACH) ( TRACED("MYEXT.DLL Başlatılıyor!\n") ; // Extension DLL tek seferlik başlatma , örnek) ; // Bu DLL dosyasını kaynak zincirine ekleyin new CDynLinkLibrary(MyExtDLL); ) else if (dwReason == DLL_PROCESS_DETACH) ( TRACED("MYEXT.DLL Sonlandırma!\n") ; ) 1 döndür; // tamam )

Bu fonksiyonun en önemli kısmı çağrıdır. AfxInitExtensionModule. Bu, dinamik kitaplığın başlatılmasıdır ve MFC yapısının bir parçası olarak doğru çalışmasına izin verir. Bu işlevin bağımsız değişkenleri, DllMain'e iletilen DLL kitaplık tanımlayıcısı ve MFC'ye bağlı dinamik kitaplık hakkında bilgi içeren AFX_EXTENSION_MODULE yapısıdır.

AFX_EXTENSION_MODULE yapısını açıkça başlatmaya gerek yoktur. Ancak beyan edilmesi gerekir. Yapıcı başlatma işlemini yapacaktır. CDynLinkKütüphanesi. DLL'nin bir sınıf oluşturması gerekiyor CDynLinkKütüphanesi. Oluşturucusu yalnızca AFX_EXTENSION_MODULE yapısını başlatmakla kalmayacak, aynı zamanda yeni kitaplığı MFC'nin çalışabileceği DLL listesine ekleyecektir.

Dinamik MFC Uzantılarını Yükleme

4.0 sürümünden itibaren MFC, uzantılar da dahil olmak üzere DLL'leri dinamik olarak yüklemenize ve kaldırmanıza olanak tanır. Oluşturulan DLL üzerinde bu işlemleri doğru bir şekilde gerçekleştirmek için işlevinde DllAna süreçten bağlantının kesildiği anda, bir çağrı eklemeniz gerekir AfxTermExtensionModule. Yukarıda kullanılan AFX_EXTENSION_MODULE yapısı, son fonksiyona parametre olarak geçirilir. Bunu yapmak için metinde DllAna aşağıdaki satırları eklemeniz gerekir.

If(dwReason == DLL_PROCESS_DETACH) ( AfxTermExtensionModule(MyExtDLL); )

Ayrıca, yeni DLL'nin dinamik bir uzantı olduğunu ve işlevler kullanılarak dinamik olarak yüklenmesi ve kaldırılması gerektiğini unutmayın. AfxLoadLibrary Ve afxFreeKütüphane,Ama değil Yük Kitaplığı Ve ücretsiz kitaplık.

Dinamik Uzantılardan İşlevleri Dışa Aktarma

Şimdi fonksiyonların ve sınıfların dinamik bir uzantıdan uygulamaya nasıl aktarıldığını ele alalım. Tüm genişletilmiş adları DEF dosyasına manuel olarak eklemek mümkün olsa da, dışa aktarılan sınıfların ve işlevlerin bildirimleri için AFX_EXT_CLASS ve AFX_EXT_API gibi değiştiricileri kullanmak daha iyidir, örneğin:

Class AFX_EXT_CLASS CMyClass: public CObject (// Sınıf bildiriminiz) void AFX_EXT_API MyFunc();