Aracılığıyla paylaş


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 eklenmek veya bu iş parçacığından ayrılmak için DLL'ye Windows işletim sistemi iletilerini işleyen, _DllMainCRTStartup adında bir iç DLL giriş noktası işlevi sağlar. İşlev, _DllMainCRTStartup yığın arabelleği güvenlik kurulumu, C çalışma zamanı kitaplığının (CRT) başlatılması ve sonlandırılması gibi temel görevleri yerine getirir ve statik ve genel nesnelerin oluşturucularına ve yıkıcılarına çağrılarda bulunur. _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 fonksiyonlarını çağırır. Bu başlatma olmadan, CRT ve diğer kitaplıklar ve 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 başka 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 process attach işlemi 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 proses ekleme sinyaline gerekçe bağımsız değişkeni olarak DLL_PROCESS_ATTACH gönderir.

VCRuntime kitaplığı, varsayılan başlatma ve sonlandırma işlemlerini işlemek için adlı _DllMainCRTStartup bir giriş noktası işlevi sağlar. İşleme ekleme anında _DllMainCRTStartup işlevi arabellek güvenlik denetimlerini ayarlar, CRT'yi ve diğer kütüphaneleri 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 kütüphane tarafından sağlanan DllMain fonksiyonunu çağırır. İşlem ayırmada, işlev bu adımları ters sırayla izler. DllMain çağrısını yapar, iç sayacı azaltır, yok edicileri çağırır, CRT sonlandırma işlevlerini ve kayıtlı atexit işlevleri çağırır ve diğer kitaplıklara sonlandırmayı bildirir. Ek sayacı sıfır olduğunda, işlev Windows'a DLL'nin kaldırılabildiğini göstermek için bir değer FALSE döndürür. İşlev, _DllMainCRTStartup iş parçacığı bağlama ve iş parçacığı ayırma sırasında da çağrılır. Bu gibi durumlarda, VCRuntime kodu kendi başına başka bir başlatma veya sonlandırma gerçekleştirmez ve yalnızca iletiyi iletmek için çağırır DllMain . İşlem eklemeden hata sinyali olarak DllMainFALSE döndürülürse, _DllMainCRTStartup, DLL_PROCESS_DETACH'ü neden bağımsız değişkeni olarak geçerek DllMain'yi yeniden çağırır ve 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. (Giriş noktası simgesi) bağlayıcı seçeneğini kullanarak /ENTRY DLL'niz için bir giriş noktası işlevi belirtmeniz gerekmez.

Not

: bağlayıcı seçeneğini kullanarak /ENTRYDLL için başka bir giriş noktası işlevi belirtmek mümkün olsa da, giriş noktası işlevinizin aynı sırada her _DllMainCRTStartup şeyi yinelemesi gerekeceğinden bunu önermeyiz. VCRuntime, davranışını yinelemenize olanak sağlayan işlevler sağlar. Örneğin, __security_init_cookie'i işlem ekleme sırasında hemen çağırarak /GS (Arabellek güvenlik denetimi) arabellek denetimi seçeneğini destekleyebilirsiniz. DLL başlatma veya sonlandırma işlevlerinin geri kalanını gerçekleştirmek için, _CRT_INIT işlevini, giriş noktası işleviyle aynı parametreleri geçirerek ç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, Windows tarafından geçirilen aynı parametreleri kullanarak DllMain işlevini çağırır. Varsayılan olarak, bir DllMain işlev sağlamazsanız, Visual Studio sizin için bir işlev sağlar ve _DllMainCRTStartup her zaman bir işlevi çağırabilsin diye onu 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 sizin için DllMain işlevini sarar. Örneğin, normal bir MFC DLL'sinde, DLL'nizin gerektirdiği başlatma ve sonlandırma işlemlerini gerçekleştirmek için nesnenin CWinApp ve InitInstance üye işlevlerini uygulayınExitInstance. Daha fazla bilgi 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. Çağrılması tehlikeli olan belirli Windows API'leri hakkında daha fazla bilgi için DllMain bkz: Genel En İyi Yöntemler. En basit başlatma dışında bir şeye ihtiyacınız varsa, bunu DLL için 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 belgeleri, DLL giriş noktası işlevinin gerçek adının bağlayıcı komut satırında seçeneğiyle belirtilmesi /ENTRY gerektiğini söyler. Visual Studio ile giriş noktası işlevinizin adı DllMain ise /ENTRY seçeneğini kullanmanız gerekmez. Aslında, /ENTRY seçeneğini kullanır ve giriş noktası işlevinize DllMain dışında bir ad verirseniz, giriş noktası işleviniz _DllMainCRTStartup gibi aynı başlatma çağrılarını yapmazsa, CRT düzgün şekilde başlatılmaz.

El ile CRT başlatma _CRT_INIT

CRT'nin düzgün şekilde başlatıldığından emin olmak için C çalışma zamanı kitaplıklarından herhangi birini kullanan bir DLL oluştururken:

  • Başlatma işlevi DllMain() olarak adlandırılmalı ve giriş noktası bağlayıcı seçeneğiyle -entry:_DllMainCRTStartup@12 olarak belirtilmelidir. DLL oluştururken varsayılan davranış budur (bu yalnızca x86'dır çünkü platform, @12stdcall kullanıldığından), veya
  • DLL'nin giriş noktasından, işlem ekleme ve işlem ayırma sırasında açıkça _CRT_INIT() çağrısı yapılmalıdır. Bu, yalnızca /NOENTRY kullanıyorsanız veya özel bir giriş noktanız varsa geçerlidir. Mümkünse, /NOENTRY veya özel bir giriş noktası kullanmanızı önermeyiz, çünkü _DllMainCRTStartup'in sağladığı tüm başlatma ve sonlandırma kodunu aynı sırayla yinelemeniz gerekir.

Bu, C çalışma zamanı kitaplıklarının DLL'ye bir işlem veya iş parçacığı eklendiğinde C çalışma zamanı verilerini düzgün bir şekilde ayırmasına ve başlatmasına, bir işlem DLL'den ayrılırken C çalışma zamanı verilerini düzgün bir şekilde temizlemesine ve DLL'deki genel C++ nesnelerinin düzgün bir şekilde oluşturulup yok edilmesine izin verir.

Win32 SDK örneklerinin tümü ilk yöntemi kullanır. Win32 Programcı Başvurusu'na DllEntryPoint() ve Visual C++ belgelerine DllMain() bakın. DllMainCRTStartup(), _CRT_INIT()'i ve _CRT_INIT(), varsa uygulamanızın DllMain()'ünü çağırır.

İkinci yöntemi kullanmak ve DllMainCRTStartup() ile DllMain() yerine CRT başlatma kodunu kendiniz çağırmak istiyorsanız, iki teknik vardır:

  1. Kendi DLL giriş noktanız varsa, giriş noktasında aşağıdakileri yapın:

    a. _CRT_INIT() için (tanımlandığında process.h içinde _DECL_DLLMAIN tanımlanır) bu prototipi kullanın: BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);

    Dönüş değerleri hakkında _CRT_INIT() bilgi için DllEntryPointbelgelerine bakın; aynı değerler döndürülür.

    b. DLL_PROCESS_ATTACH ve DLL_THREAD_ATTACH üzerinde, herhangi bir C çalışma zamanı işlevi çağrılmadan veya kayan nokta işlemleri gerçekleştirilmeden önce, ilk olarak _CRT_INIT() çağrısını yapın.

    ç. Kendi işlem/iş parçacığı başlatma/sonlandırma kodunuzu çağırabilirsiniz.

    d. DLL_PROCESS_DETACH ve DLL_THREAD_DETACH üzerinde, tüm C çalışma zamanı işlevleri çağrıldıktan ve tüm kayan nokta işlemleri tamamlandıktan sonra, son olarak _CRT_INIT() çağrısında bulunun.

Giriş noktasının tüm parametrelerine _CRT_INIT() geçtiğinden emin olun; _CRT_INIT() bu parametreleri bekler, bu nedenle atlanırsa işler güvenilir bir şekilde çalışmayabilir (özellikle işlem fdwReason başlatma veya sonlandırma gerekip gerekmediğini belirlemek için gereklidir).

Aşağıda, DLL giriş noktasında bu çağrıların _CRT_INIT() ne zaman ve nasıl yapılacağını gösteren bir iskelet örnek giriş noktası işlevi verilmiştir:

BOOL WINAPI DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
    if (fdwReason == DLL_PROCESS_ATTACH || fdwReason == DLL_THREAD_ATTACH)
        if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
            return(FALSE);

    if (fdwReason == DLL_PROCESS_DETACH || fdwReason == DLL_THREAD_DETACH)
        if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved))
            return(FALSE);

    return(TRUE);
}

Not

DllMain() ve -entry:_DllMainCRTStartup@12 kullanıyorsanız bu gerekli değildir.

Normal MFC DLL'lerini başlatma

Standart 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: InitInstance ve ExitInstance üye işlevlerinde, DLL'nin CWinApp-türetilmiş sınıfında. MFC, DLL_PROCESS_ATTACH ve DLL_PROCESS_DETACH için _DllMainCRTStartup tarafından çağrılan bir DllMain işlevi sağladığı için, kendi DllMain işlevinizi yazmamalısınız. MFC tarafından sağlanan DllMain işlevi, DLL'niz yüklendiğinde InitInstance ve DLL kaldırılmadan önce ExitInstance çağırır.

Normal bir MFC DLL, TlsAlloc ve TlsGetValue işlevlerini fonksiyonunda çağırarak birden çok iş parçacığını takip edebilir. 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.dllve MFCNsürümD.dll ( burada 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 (MFCOversionD.dll) AfxOleInitModule
MFC Veritabanı (MFCDsürümüD.dll) AfxDbInitModule
MFC Soketler (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 oluşturduğu işleve DllMain 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, normal MFC DLL'sinin CWinApp türetilmiş nesnesinin üye işlevi içindedir ve bu işlem, MFC uzantı DLL'sinin dışa aktarılan sınıfları veya işlevleri kullanılmadan önce gerçekleştirilmelidir.

DllMain MFC DLL Sihirbazı'nın oluşturduğu dosya içinde, AfxInitExtensionModule çağrısı, CDynLinkLibrary nesnesi oluşturulduğunda kullanılmak üzere, modülün çalışma zamanı sınıflarını (CRuntimeClass yapıları) ve nesne fabrikalarını (COleObjectFactory nesneleri) yakalar. AfxInitExtensionModule'nin dönüş değerini kontrol etmelisiniz; AfxInitExtensionModule sıfır değer döndürdüğünde, sizin DllMain işlevinizden sıfır döndürün.

MFC uzantı DLL'niz bir yürütülebilir dosyaya doğrudan bağlanmışsa (bu, yürütülebilir dosyanın dll'ye bağlanmak için AfxLoadLibrary çağrısı yaptığı anlamına gelir), DLL_PROCESS_DETACH üzerinde AfxTermExtensionModule öğesine 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ğlıysa AfxTermExtensionModule çağrısı yapmak gerekli değildir.

MFC uzantısı DLL'lerine açıkça bağlanan uygulamaların, DLL'yi serbest bırakırken AfxTermExtensionModule çağrısını yapması gerekir. Uygulama birden çok iş parçacığı kullanıyorsa, Win32 işlevleri `LoadLibrary` ve `FreeLibrary` yerine `AfxLoadLibrary` ve `AfxFreeLibrary` kullanılmalıdır. AfxLoadLibrary ve AfxFreeLibrary kullanımı, 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, DllMain çağrı yapıldığında tamamen başlatıldığından, DllMain içinde bellek ayırabilir ve MFC fonksiyonlarını çağırabilirsiniz (MFC'nin 16 bit sürümünden farklı olarak).

Uzantı DLL'leri, DllMain işlevindeki DLL_THREAD_ATTACH ve DLL_THREAD_DETACH durumlarını işleyerek çoklu iş parçacığını yönetebilir. İş parçacıkları DLL'ye eklenip ayrıldığında bu durumlar DllMain'e geçirilir. DLL bağlanırken TlsAlloc çağrılırsa, DLL'ye bağlı olan her iş parçacığı için iş parçacığı yerel depolama (TLS) dizinlerini korumasını sağlar.

Başlık dosyası Afxdllx.h, AFX_EXTENSION_MODULE ve CDynLinkLibrary tanımları gibi MFC uzantısı DLL'lerinde kullanılan yapıların özel tanımlarını içerir. Bu üst bilgi dosyasını MFC uzantı DLL'nize eklemelisiniz.

Not

_AFX_NO_XXX içindeki makroları tanımlamamanız veya tanımlarını kaldırmamanız önemlidir (stdafx.h'da, Visual Studio 2017 ve önceki sürümlerde). Bu makrolar yalnızca belirli bir hedef platformun bu özelliği destekleyip desteklemediğini denetlemek için vardır. 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 olduğuna dikkat edin, ancak MFC ve C çalışma zamanı kitaplıklarıyla çalışması için bu işlevi DllMain adlandırmanız gerekir.

Ayrıca bkz.

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