DLL’ler ve Visual C++ çalışma zamanı kitaplığı davranışı

Visual Studio kullanarak dinamik bağlantı kitaplığı (DLL) oluşturduğunuzda, bağlayıcı varsayılan olarak Visual C++ çalışma zamanı kitaplığını (VCRuntime) içerir. VCRuntime, C/C++ yürütülebilir dosyasını başlatmak ve sonlandırmak için gereken kodu içerir. Bir DLL'ye bağlanıldığında, VCRuntime kodu bir işleme veya iş parçacığına eklemek veya bu iş parçacığından ayırmak için DLL'ye Windows işletim sistemi iletilerini işleyen adlı _DllMainCRTStartup bir iç DLL giriş noktası işlevi sağlar. İşlev, _DllMainCRTStartup yığın arabelleği güvenlik kurulumu, C çalışma zamanı kitaplığı (CRT) başlatma ve sonlandırma gibi temel görevleri gerçekleştirir ve statik ve genel nesneler için oluşturuculara ve yıkıcılara çağrı yapar. _DllMainCRTStartup ayrıca Kendi başlatma ve sonlandırma işlemlerini gerçekleştirmek için WinRT, MFC ve ATL gibi diğer kitaplıklar için kanca işlevlerini çağırır. Bu başlatma olmadan, CRT ve diğer kitaplıkların yanı sıra statik değişkenleriniz başlatılmamış durumda bırakılır. DLL'nizin statik olarak bağlı bir CRT veya dinamik olarak bağlı bir CRT DLL kullanması farketmeksizin aynı VCRuntime iç başlatma ve sonlandırma yordamları çağrılır.

Varsayılan DLL giriş noktası _DllMainCRTStartup

Windows'da, tüm DLL'ler hem başlatma hem de sonlandırma için çağrılan, genellikle adlı DllMainisteğe bağlı bir giriş noktası işlevi içerebilir. Bu size gerektiğinde ek kaynaklar ayırma veya serbest bırakma fırsatı verir. Windows giriş noktası işlevini dört durumda çağırır: işlem ekleme, işlem ayırma, iş parçacığı ekleme ve iş parçacığı ayırma. Dll bir işlem adres alanına yüklendiğinde, onu kullanan bir uygulama yüklendiğinde veya uygulama çalışma zamanında DLL'yi istediğinde, işletim sistemi DLL verilerinin ayrı bir kopyasını oluşturur. Buna işlem ekleme adı verilir. DLL'nin yüklendiği işlem yeni bir iş parçacığı oluşturduğunda iş parçacığı ekleme gerçekleşir. İş parçacığı ayırma , iş parçacığı sonlandırıldığında gerçekleşir ve işlem ayırma , DLL'nin artık gerekli olmadığı ve bir uygulama tarafından serbest bırakıldığı durumlardır. İşletim sistemi, bu olayların her biri için DLL giriş noktasına ayrı bir çağrı yapar ve her olay türü için bir neden bağımsız değişkeni geçirir. Örneğin, işletim sistemi sinyal işlemi eklemeye neden bağımsız değişkeni olarak gönderirDLL_PROCESS_ATTACH.

VCRuntime kitaplığı, varsayılan başlatma ve sonlandırma işlemlerini işlemek için adlı _DllMainCRTStartup bir giriş noktası işlevi sağlar. İşlem ekleme sırasında _DllMainCRTStartup işlev arabellek güvenlik denetimlerini ayarlar, CRT ve diğer kitaplıkları başlatır, çalışma zamanı türü bilgilerini başlatır, statik ve yerel olmayan veriler için oluşturucuları başlatır ve çağırır, iş parçacığı yerel depolamayı başlatır, her ekleme için bir iç statik sayacı artırır ve sonra kullanıcı veya kitaplık tarafından sağlanan DllMainbir öğesini çağırır. İşlem ayırma işleminde işlev bu adımları tersten ilerler. çağrısında DllMainbulunur, iç sayacı yok eder, yok edicileri çağırır, CRT sonlandırma işlevlerini ve kayıtlı atexit işlevleri çağırır ve diğer sonlandırma kitaplıklarına bildirir. Ek sayacı sıfıra gittiğinde, işlev Windows'a DLL'nin kaldırılabildiğini göstermek için geri döner FALSE . İşlev, _DllMainCRTStartup iş parçacığı ekleme ve iş parçacığı ayırma sırasında da çağrılır. Böyle durumlarda, VCRuntime kodu kendi başına ek başlatma veya sonlandırma gerçekleştirmez ve yalnızca iletiyi iletmek için çağırır DllMain . İşlem ekleme, sinyal hatasından döndürülüyorsa DllMain FALSE, _DllMainCRTStartup yeniden çağrılar DllMain DLL_PROCESS_DETACH ve neden bağımsız değişkeni olarak geçerse sonlandırma işleminin geri kalanından geçer.

Visual Studio'da DLL'ler oluşturulurken, VCRuntime tarafından sağlanan varsayılan giriş noktası _DllMainCRTStartup otomatik olarak bağlanır. /ENTRY (Giriş noktası simgesi) bağlayıcı seçeneğini kullanarak DLL'niz için bir giriş noktası işlevi belirtmeniz gerekmez.

Not

/ENTRY: bağlayıcı seçeneğini kullanarak DLL için başka bir giriş noktası işlevi belirtmek mümkün olsa da, giriş noktası işlevinizin aynı sırada olan _DllMainCRTStartup her şeyi yinelemesi gerekeceğinden bunu önermeyiz. VCRuntime, davranışını yinelemenize olanak sağlayan işlevler sağlar. Örneğin, /GS (Arabellek güvenlik denetimi) arabellek denetimi seçeneğini desteklemek için işlem ekleme sırasında __security_init_cookie hemen çağırabilirsiniz. DLL başlatma veya sonlandırma işlevlerinin _CRT_INIT geri kalanını gerçekleştirmek için giriş noktası işleviyle aynı parametreleri geçirerek işlevini çağırabilirsiniz.

DLL başlatma

DLL'niz, DLL'niz yüklendiğinde yürütülmesi gereken başlatma koduna sahip olabilir. Kendi DLL başlatma ve sonlandırma işlevlerinizi gerçekleştirebilmeniz için, _DllMainCRTStartup sağlayabileceğiniz adlı DllMain bir işlevi çağırır. Dll giriş noktası için gerekli imzaya DllMain sahip olmanız gerekir. Varsayılan giriş noktası işlevi _DllMainCRTStartup , Windows tarafından geçirilen parametreleri kullanarak çağırır DllMain . Varsayılan olarak, bir işlev sağlamazsanız, Visual Studio sizin için bir DllMain işlev sağlar ve her zaman çağıracak bir şey olması _DllMainCRTStartup için bunu bağlar. Bu, DLL'nizi başlatmanız gerekmiyorsa DLL'nizi oluştururken yapmanız gereken özel bir şey olmadığı anlamına gelir.

Bu, için DllMainkullanılan imzadır:

#include <windows.h>

extern "C" BOOL WINAPI DllMain (
    HINSTANCE const instance,  // handle to DLL module
    DWORD     const reason,    // reason for calling function
    LPVOID    const reserved); // reserved

Bazı kitaplıklar işlevi sizin için sarmalar DllMain . Örneğin, normal bir MFC DLL'sinde, DLL'nizin gerektirdiği başlatma ve sonlandırma işlemlerini gerçekleştirmek için nesnenin InitInstance ve ExitInstance üye işlevlerini uygulayınCWinApp. Daha fazla ayrıntı için Normal MFC DLL'lerini başlatma bölümüne bakın.

Uyarı

DLL giriş noktasında güvenle yapabileceklerinizde önemli sınırlar vardır. içinde aranması güvenli olmayan belirli Windows API'leri DllMainhakkında daha fazla bilgi için bkz . Genel En İyi Yöntemler. En basit başlatma dışında herhangi bir şeye ihtiyacınız varsa bunu DLL için bir başlatma işlevinde yapın. Uygulamaların başlatıldıktan sonra DllMain ve DLL'deki diğer işlevleri çağırmadan önce başlatma işlevini çağırmasını gerektirebilirsiniz.

Normal (MFC olmayan) DLL'leri başlatma

VCRuntime tarafından sağlanan _DllMainCRTStartup giriş noktasını kullanan normal (MFC olmayan) DLL'lerde kendi başlatmanızı gerçekleştirmek için, DLL kaynak kodunuz adlı DllMainbir işlev içermelidir. Aşağıdaki kod, tanımının DllMain nasıl görünebileceğini gösteren temel bir iskelet sunar:

#include <windows.h>

extern "C" BOOL WINAPI DllMain (
    HINSTANCE const instance,  // handle to DLL module
    DWORD     const reason,    // reason for calling function
    LPVOID    const reserved)  // reserved
{
    // Perform actions based on the reason for calling.
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        // Initialize once for each new process.
        // Return FALSE to fail DLL load.
        break;

    case DLL_THREAD_ATTACH:
        // Do thread-specific initialization.
        break;

    case DLL_THREAD_DETACH:
        // Do thread-specific cleanup.
        break;

    case DLL_PROCESS_DETACH:
        // Perform any necessary cleanup.
        break;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

Not

Eski Windows SDK belgelerinde, DLL giriş noktası işlevinin gerçek adının bağlayıcı komut satırında /ENTRY seçeneğiyle belirtilmesi gerektiği belirtilir. Visual Studio ile, giriş noktası işlevinizin DllMainadı ise /ENTRY seçeneğini kullanmanız gerekmez. Aslında, /ENTRY seçeneğini kullanır ve giriş noktası işlevinizi dışında DllMainbir adla adlandırırsanız, giriş noktası işleviniz aynı başlatma çağrılarını _DllMainCRTStartup yapmadığı sürece CRT düzgün başlatılmaz.

Normal MFC DLL'lerini başlatma

Normal MFC DLL'lerinin bir CWinApp nesnesi olduğundan, başlatma ve sonlandırma görevlerini bir MFC uygulamasıyla aynı konumda gerçekleştirmeleri gerekir: DLL'nin CWinApptüretilmiş sınıfının ve ExitInstance üye işlevlerindeInitInstance. MFC ve için tarafından _DllMainCRTStartup çağrılan bir DllMain işlev sağladığındanDLL_PROCESS_ATTACH, kendi DllMain işlevinizi yazmamalısınız.DLL_PROCESS_DETACH MFC tarafından sağlanan DllMain işlev, DLL'niz yüklendiğinde ve InitInstance DLL kaldırilmeden önce çağırdığında çağırır ExitInstance .

Normal bir MFC DLL işlevinde TlsAlloc ve TlsGetValue çağrısı yaparak birden çok iş parçacığını InitInstance izleyebilir. Bu işlevler DLL'nin iş parçacığına özgü verileri izlemesine olanak sağlar.

MFC'ye dinamik olarak bağlanan normal MFC DLL'nizde, sırasıyla herhangi bir MFC OLE, MFC Veritabanı (veya DAO) veya MFC Yuva desteği kullanıyorsanız, hata ayıklama MFC uzantısı DLL'leri MFCOsürümüD.dll, MFCDsürümüD.dll ve MFCNsürümD.dll (sürüm numarasıdır) otomatik olarak bağlanır. Normal MFC DLL'nizde CWinApp::InitInstancekullandığınız bu DLL'lerin her biri için aşağıdaki önceden tanımlanmış başlatma işlevlerinden birini çağırmanız gerekir.

MFC desteğinin türü Çağrılacak başlatma işlevi
MFC OLE (MFCOsürümD.dll) AfxOleInitModule
MFC Veritabanı (MFCDsürümüD.dll) AfxDbInitModule
MFC Yuvaları (MFCNsürümüD.dll) AfxNetInitModule

MFC uzantısı DLL'lerini başlatma

MFC uzantısı DLL'lerinin türetilmiş bir CWinAppnesnesi olmadığından (normal MFC DLL'lerinde olduğu gibi), başlatma ve sonlandırma kodunuzu MFC DLL Sihirbazı'nın DllMain oluşturduğu işleve eklemeniz gerekir.

Sihirbaz MFC uzantısı DLL'leri için aşağıdaki kodu sağlar. Kodda, PROJNAME projenizin adı için bir yer tutucudur.

#include "pch.h" // For Visual Studio 2017 and earlier, use "stdafx.h"
#include <afxdllx.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
static AFX_EXTENSION_MODULE PROJNAMEDLL;

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
   if (dwReason == DLL_PROCESS_ATTACH)
   {
      TRACE0("PROJNAME.DLL Initializing!\n");

      // MFC extension DLL one-time initialization
      AfxInitExtensionModule(PROJNAMEDLL,
                                 hInstance);

      // Insert this DLL into the resource chain
      new CDynLinkLibrary(Dll3DLL);
   }
   else if (dwReason == DLL_PROCESS_DETACH)
   {
      TRACE0("PROJNAME.DLL Terminating!\n");
   }
   return 1;   // ok
}

Başlatma sırasında yeni CDynLinkLibrary bir nesne oluşturmak, MFC uzantısı DLL'sinin nesneleri veya kaynakları istemci uygulamasına dışarı aktarmasına CRuntimeClass olanak tanır.

Bir veya daha fazla normal MFC DLL'sinden MFC uzantı DLL'nizi kullanacaksanız, nesne oluşturan bir CDynLinkLibrary başlatma işlevini dışarı aktarmanız gerekir. Bu işlev, MFC uzantısı DLL'sini kullanan normal MFC DLL'lerinin her birinden çağrılmalıdır. Bu başlatma işlevini çağırmak için uygun bir yer, MFC uzantı DLL'sinin InitInstance dışarı aktarılan sınıflarından veya işlevlerinden herhangi birini kullanmadan önce normal MFC DLL'sinin CWinApptüretilmiş nesnesinin üye işlevindedir.

MFC DLL Sihirbazı'nın DllMain oluşturduğu içinde, modülün çalışma zamanı sınıflarını (CRuntimeClassyapıları) ve nesne oluşturulduğunda kullanılacak CDynLinkLibrary nesne fabrikalarını (COleObjectFactorynesneleri) yakalama çağrısıAfxInitExtensionModule. değerinin dönüş değerini AfxInitExtensionModuledenetlemeniz gerekir; değerinden AfxInitExtensionModulesıfır değer döndürülürse işlevinizden DllMain sıfır döndürür.

MFC uzantı DLL'niz açıkça bir yürütülebilir dosyaya bağlanacaksa (dll'ye bağlanmak için yürütülebilir çağrılar AfxLoadLibrary anlamına gelir), üzerinde DLL_PROCESS_DETACHöğesine AfxTermExtensionModule bir çağrı eklemeniz gerekir. Bu işlev, her işlem MFC uzantı DLL'sinden ayrıldığında MFC uzantısı DLL'sini temizlemesine olanak tanır (işlem çıktığında veya bir AfxFreeLibrary çağrı sonucunda DLL kaldırıldığında gerçekleşir). MFC uzantı DLL'niz uygulamaya örtük olarak bağlanacaksa çağrısı AfxTermExtensionModule gerekli değildir.

MFC uzantısı DLL'lerine açıkça bağlanan uygulamaların DLL'yi serbest bırakırken çağrısı AfxTermExtensionModule yapması gerekir. Ayrıca, uygulama birden çok iş parçacığı kullanıyorsa ve AfxFreeLibrary (Win32 işlevleri LoadLibrary yerine ve FreeLibrary) kullanmalıdırAfxLoadLibrary. ve AfxLoadLibrary AfxFreeLibrary kullanılması, MFC uzantısı DLL yüklendiğinde ve kaldırıldığında yürütülen başlatma ve kapatma kodunun genel MFC durumunu bozmamasını sağlar.

MFCx0.dll çağrılınca tamamen başlatıldığından DllMain , içinde bellek ayırabilir ve MFC işlevlerini DllMain çağırabilirsiniz (MFC'nin 16 bit sürümünden farklı olarak).

Uzantı DLL'leri işlevindeki ve DLL_THREAD_DETACH durumlarını işleyerek DLL_THREAD_ATTACH çoklu iş parçacığı kullanımıyla DllMain ilgilenebilir. İş parçacıkları DLL'ye DllMain eklendiğinde ve dll'den ayrıldığında bu durumlar geçirilir. Dll eklenirken TlsAlloc çağrısı, DLL'nin DLL'ye bağlı her iş parçacığı için iş parçacığı yerel depolama (TLS) dizinlerini korumasını sağlar.

Afxdllx.h üst bilgi dosyasının MFC uzantısı DLL'lerinde kullanılan yapılar için ve CDynLinkLibrarytanımı gibi özel tanımlar AFX_EXTENSION_MODULE içerdiğini unutmayın. Bu üst bilgi dosyasını MFC uzantı DLL'nize eklemelisiniz.

Not

pch.h dosyasındaki _AFX_NO_XXX (Visual Studio 2017 ve önceki sürümlerde stdafx.h) makroları tanımlamanız veya tanımlamamanız önemlidir. Bu makrolar yalnızca belirli bir hedef platformun bu özelliği destekleyip desteklemediğini denetlemek amacıyla bulunur. Bu makroları denetlemek için programınızı yazabilirsiniz (örneğin, #ifndef _AFX_NO_OLE_SUPPORT), ancak programınız bu makroları hiçbir zaman tanımlamamalı veya tanımlarını kaldırmamalıdır.

Çoklu iş parçacıklarını işleyen bir örnek başlatma işlevi, Windows SDK'sında Dinamik Bağlantı Kitaplığında İş Parçacığı Yerel Depolama Kullanma bölümüne dahildir. Örnekte adlı LibMainbir giriş noktası işlevi bulunduğunu, ancak MFC ve C çalışma zamanı kitaplıklarıyla çalışması için bu işlevi DllMain adlandırmanız gerektiğini unutmayın.

Ayrıca bkz.

Visual Studio'da C/C++ DLL'leri oluşturma
DllMain giriş noktası
Dinamik Bağlantı Kitaplığı En İyi Yöntemleri