Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Windows geliştiricileri, DllMain sırasında kod çalıştırırken yükleyici kilidine karşı her zaman tedbirli olmalıdır. Ancak, C++/CLI karma mod derlemeleriyle ilgilenirken dikkate alınması gereken bazı ek sorunlar vardır.
DllMain içindeki kodun .NET Ortak Dil Çalışma Zamanı'na (CLR) erişmemesi gerekir. Bu, DllMain yönetilen işlevlere doğrudan veya dolaylı olarak çağrı yapılmaması gerektiği anlamına gelir; içinde DllMainhiçbir yönetilen kod bildirilmemeli veya uygulanmamalıdır; içinde çöp toplama veya otomatik kitaplık yükleme gerçekleşmemelidir DllMain.
Yükleyici Kilidinin Nedenleri
.NET platformunun kullanıma sunulmasıyla birlikte, bir yürütme modülünü (EXE veya DLL) yüklemek için iki ayrı mekanizma vardır: biri yönetilmeyen modüller için kullanılan Windows için, diğeri de .NET derlemelerini yükleyen CLR için. Microsoft Windows işletim sistemi yükleyicisine odaklanan karma DLL yükleme sorunu.
Bir işleme yalnızca .NET yapılarını içeren bir derleme yüklendiğinde, CLR yükleyicisi tüm gerekli yükleme ve başlatma görevlerinin kendisini gerçekleştirebilir. Ancak, yerel kod ve veri içerebilen karma derlemeleri yüklemek için Windows yükleyicisinin de kullanılması gerekir.
Windows yükleyicisi, başlatılmadan önce hiçbir kodun söz konusu DLL'deki koda veya verilere erişemebileceğini garanti eder. Ayrıca, kısmen başlatılırken hiçbir kodun DLL'yi gereksiz yere yükleyememesini sağlar. Bunu yapmak için Windows yükleyicisi, modül başlatma sırasında güvenli olmayan erişimi engelleyen işlem genel kritik bölümü ("yükleyici kilidi" olarak adlandırılır) kullanır. Sonuç olarak, yükleme işlemi birçok klasik kilitlenme senaryosuna karşı savunmasızdır. Karma derlemeler için aşağıdaki iki senaryo kilitlenme riskini artırır:
İlk olarak, eğer kullanıcılar yükleyici kilidi tutulduğunda (
DllMainveya statik başlatıcılarda) Microsoft Ara Diline (MSIL) derlenmiş işlevleri yürütmeye çalışırsa bu, kilitlenmeye neden olabilir. MSIL işlevinin henüz yüklenmemiş bir derlemedeki bir türe başvurduğunu düşünün. CLR bu derlemeyi otomatik olarak yüklemeyi dener ve bu da Windows yükleyicisinin yükleyici kilidini engellemesini gerektirebilir. Yükleyici kilidi çağrı dizisinde daha önce kod tarafından tutulmuş olduğundan kilitlenme oluşur. Ancak, MSIL'in yükleyici kilidi altında yürütülmesi kilitlenmenin gerçekleşeceğini garanti etmez. Bu senaryoya tanılamayı ve düzeltmeyi zorlaştıran da budur. Başvuruda bulunılan türün DLL'sinin yerel yapı içermemesi ve tüm bağımlılıklarının yerel yapı içermemesi gibi bazı durumlarda, başvuruda bulunılan türün .NET derlemesini yüklemek için Windows yükleyicisi gerekli değildir. Ayrıca, gerekli derleme veya karma native/.NET bağımlılıkları başka bir kod tarafından yüklenmiş olabilir. Sonuç olarak, kilitlenmeyi tahmin etmek zor olabilir ve hedef makinenin yapılandırmasına bağlı olarak değişebilir.İkincisi, .NET Framework'ün 1.0 ve 1.1 sürümlerinde DLL'leri yüklerken, CLR yükleyici kilidinin tutulmadığını varsayar ve yükleyici kilidi altında geçersiz olan birkaç eylem gerçekleştirir. Yükleyici kilidinin tutulmadığını varsayma, yalnızca .NET DLL'leri için geçerli bir varsayımdır. Ancak karma DLL'ler yerel başlatma yordamlarını yürüttüğünden yerel Windows yükleyicisine ve dolayısıyla yükleyici kilidine ihtiyaç duyarlar. Bu nedenle, geliştirici DLL başlatma sırasında herhangi bir MSIL işlevi yürütmeye çalışmasa bile, .NET Framework 1.0 ve 1.1 sürümlerinde belirsiz kilitlenme olasılığı hala küçüktü.
Belirlenemeyen tüm öğeler karma DLL yükleme işleminden kaldırıldı. Şu değişikliklerle gerçekleştirilir:
CLR artık karma DLL'leri yüklerken yanlış varsayımlarda bulunmaz.
Yönetilmeyen ve yönetilen başlatma iki ayrı ve ayrı aşamada yapılır. Yönetilmeyen başlatma önce gerçekleşir (aracılığıyla
DllMain) ve yönetilen başlatma daha sonra, .NET destekli.cctoryapısı aracılığıyla gerçekleşir. İkincisi,/Zlveya/NODEFAULTLIBkullanılmadığı sürece kullanıcıya tamamen saydamdır. Daha fazla bilgi için bkz/NODEFAULTLIB. (Kitaplıkları Yoksay) ve/Zl(Varsayılan Kitaplık Adını Atla).
Yükleyici kilidi yine de oluşabilir, ancak artık tekrarlanabilir bir şekilde ortaya çıkar ve algılanır. MSIL yönergeleri içeriyorsa DllMain , derleyici uyarı Derleyici Uyarısı (düzey 1) C4747 oluşturur. Ayrıca, CRT veya CLR, MSIL'yi yükleyici kilidi altında yürütme girişimlerini algılamaya ve raporlamaya çalışır. CRT algılama, çalışma zamanı tanılama C Çalışma Zamanı Hatası R6033 ile sonuçlanır.
Bu makalenin geri kalanında, MSIL'in yükleyici kilidi altında yürütülmesi mümkün olan diğer senaryolar açıklanmaktadır. Bu senaryoların her biri altında sorunun nasıl çözüleceğini ve hata ayıklama tekniklerini gösterir.
Senaryolar ve Geçici Çözümler
Kullanıcı kodunun yükleyici kilidi altında MSIL'i yürütebileceği birkaç farklı durum vardır. Geliştirici, kullanıcı kodu uygulamasının bu koşulların her biri altında MSIL yönergelerini yürütmeye çalışmadığından emin olmalıdır. Aşağıdaki alt bölümlerde, en yaygın durumlarda sorunların nasıl çözüleceğinin tartışıldığı tüm olasılıklar açıklanmaktadır.
DllMain
DllMain İşlev, DLL için kullanıcı tanımlı bir giriş noktasıdır. Kullanıcı aksini belirtmediği sürece, DllMain bir işlem veya iş parçacığı içeren DLL'ye her eklendiğinde veya dll'den ayrıldığında çağrılır. Yükleyici kilidi tutulurken bu çağrının gerçekleşebilmesi nedeniyle, kullanıcı tarafından sağlanan hiçbir işlev MSIL'a derlenmemelidir. Kökü DllMain olan çağrı ağacındaki hiçbir işlev MSIL'e derlenemez. Buradaki sorunları çözmek için, tanımlayan DllMain kod bloğu ile #pragma unmanageddeğiştirilmelidir. Çağıran DllMain her işlev için aynı işlem yapılmalıdır.
Bu işlevlerin diğer çağırma bağlamları için MSIL uygulaması gerektiren bir işlevi çağırması gereken durumlarda, hem .NET hem de aynı işlevin yerel sürümünün oluşturulduğu bir yineleme stratejisi kullanabilirsiniz.
Alternatif olarak, gerekli değilse DllMain veya yükleyici kilidi altında yürütülmesi gerekmiyorsa, kullanıcı tarafından sağlanan DllMain uygulamayı kaldırarak sorunu ortadan kaldırabilirsiniz.
MSIL doğrudan yürütülmeye çalışılırsa DllMain , Derleyici Uyarısı (düzey 1) C4747 sonuçlanır. Ancak derleyici, DllMain ile başka bir modüldeki bir işlevin çağrılması ve bu işlevin de MSIL yürütmeyi denemesi durumlarını algılayamaz.
Bu senaryo hakkında daha fazla bilgi için bkz: Tanılamaya Yönelik Engeller.
Statik Nesneleri Başlatma
Dinamik başlatıcı gerekiyorsa statik nesnelerin başlatılması kilitlenmeye neden olabilir. Basit durumlar (örneğin, derleme zamanında bilinen bir değeri statik bir değişkene atadığınızda) dinamik başlatma gerektirmez, bu nedenle kilitlenme riski yoktur. Ancak, bazı statik değişkenler, derleme zamanında değerlendirilemeyen işlev çağrıları, oluşturucu çağrıları veya ifadeler tarafından başlatılır. Bu değişkenlerin tümü modül başlatma sırasında kodun yürütülmesini gerektirir.
Aşağıdaki kodda dinamik başlatma gerektiren statik başlatıcı örnekleri gösterilmektedir: işlev çağrısı, nesne oluşturma ve işaretçi başlatma. (Bu örnekler statik değildir, ancak aynı etkiye sahip genel kapsamda tanımları olduğu varsayılır.)
// dynamic initializer function generated
int a = init();
CObject o(arg1, arg2);
CObject* op = new CObject(arg1, arg2);
Bu kilitlenme riski, içeren modülün /clr ile derlenip derlenmediğine ve MSIL'in yürütülüp yürütülmeyeceğine bağlıdır. Özellikle, statik değişken olmadan /clr derlenmişse (veya bir #pragma unmanaged bloktaysa) ve bunu başlatmak için gereken dinamik başlatıcı MSIL yönergelerinin yürütülmesine neden oluyorsa kilitlenme oluşabilir. Bunun nedeni, olmadan /clrderlenen modüller için statik değişkenlerin başlatılması DllMain tarafından gerçekleştirilir. Buna karşılık, /clr ile derlenen statik değişkenler, yönetilmeyen başlatma aşaması tamamlandıktan ve yükleyici kilidi serbest bırakıldıktan sonra .cctor tarafından başlatılır.
Statik değişkenlerin dinamik olarak başlatılmasından kaynaklanan kilitlenmeye yönelik bir dizi çözüm vardır. Bunlar, sorunu çözmek için gereken zamana göre kabaca burada düzenlenmiştir:
Statik değişkeni içeren kaynak dosya ile
/clrderlenebilir.Statik değişken tarafından çağrılan tüm işlevler, yönergesi kullanılarak yerel koda
#pragma unmanagedderlenebilir.Statik değişkenin bağlı olduğu kodu el ile kopyalayarak hem .NET hem de farklı adlara sahip bir yerel sürüm sağlayın. Geliştiriciler daha sonra yerel statik başlatıcılardan yerel sürümü çağırabilir ve .NET sürümünü başka bir yerde çağırabilir.
Başlatmayı Etkileyen Kullanıcı Tarafından Sağlanan İşlevler
Kitaplıkların başlangıçta başlatma için bağımlı olduğu, kullanıcı tarafından sağlanan birkaç işlev vardır. Örneğin, C++ dilinde new ve delete gibi operatörler genel kapsamda aşırı yüklenirken, bu operatörlerin kullanıcı tarafından sağlanan sürümleri, C++ Standart Kütüphanesi'nin başlatma ve yok etme süreçleri dahil olmak üzere her yerde kullanılır. Sonuç olarak, C++ Standart Kitaplığı ve kullanıcı tarafından sağlanan statik başlatıcılar bu işleçlerin kullanıcı tarafından sağlanan sürümlerini çağırır.
Kullanıcı tarafından sağlanan sürümler MSIL'de derlenirse, yükleyici kilidi tutulurken bu başlatıcılar MSIL yönergelerini yürütmeye çalışır. Kullanıcı tarafından sağlanan bir malloc aynı sonuçlara yol açar. Bu sorunu çözmek için bu aşırı yüklemelerden veya kullanıcı tarafından sağlanan tanımlardan herhangi birinin yönergesi #pragma unmanaged kullanılarak yerel kod olarak uygulanması gerekir.
Bu senaryo hakkında daha fazla bilgi için bkz Tanılamada Engeller.
Özel Yerel Ayarlar
Kullanıcı özel bir genel yerel ayar sağlarsa, bu yerel ayar statik olarak başlatılan akışlar da dahil olmak üzere gelecekteki tüm G/Ç akışlarını başlatmak için kullanılır. Bu genel yerel ayar nesnesi MSIL'e derlenmişse, yükleyici kilidi tutulurken MSIL'e derlenen yerel ayar nesnesi üye işlevleri çağrılabilir.
Bu sorunu çözmek için üç seçenek vardır:
Tüm genel G/Ç akış tanımlarını içeren kaynak dosyalar seçeneği kullanılarak /clr derlenebilir. Statik başlatıcılarının yükleyici kilidi altında yürütülmesini engeller.
Özel yerel ayar işlev tanımları, #pragma unmanaged yönergesi kullanılarak yerel makine koduna derlenebilir.
Yükleyici kilidi serbest bırakılana kadar özel yerel ayarı genel yerel ayar olarak ayarlamaktan kaçının. Ardından özel yerel ayar ile başlatma sırasında oluşturulan G/Ç akışlarını açıkça yapılandırın.
Tanıda Engeller
Bazı durumlarda kilitlenmelerin kaynağını saptamak zordur. Aşağıdaki alt bölümlerde bu senaryolar ve bu sorunlara geçici çözüm yolları ele alınıyor.
Başlıklarda Uygulama
Belirli durumlarda, üst bilgi dosyalarının içindeki işlev uygulamaları tanılamayı karmaşıklaştırabilir. Satır içi işlevlerin ve şablon kodunun her ikisi de işlevlerin bir üst bilgi dosyasında belirtilmesi gerekir. C++ dili, aynı ada sahip işlevlerin tüm uygulamalarını anlamsal olarak eşdeğer olacak şekilde zorlayan Tek Tanım Kuralı'nı belirtir. Sonuç olarak, C++ bağlayıcısı belirli bir işlevin yinelenen uygulamalarına sahip nesne dosyalarını bir araya getirirken dikkat edilmesi gereken özel noktalara gerek yoktur.
Visual Studio 2005'in önceki Visual Studio sürümlerinde bağlayıcı, bu eşanlamlı eşdeğer tanımlardan en büyük olanı seçer. Farklı kaynak dosyalar için farklı iyileştirme seçenekleri kullanıldığında ileriye dönük bildirimleri ve senaryoları barındırmak için yapılır. Karışık yerel ve .NET DLL'leri için bir sorun oluşturur.
Aynı başlık dosyası hem /clr etkin hem de devre dışı olan C++ dosyalarına dahil edilebileceğinden veya bir #include ifadesi bir #pragma unmanaged bloğu içine yerleştirilebileceğinden, başlık dosyalarında uygulamalar sağlayan işlevlerin hem MSIL hem de yerel sürümlerine sahip olmak mümkündür. MSIL ve yerel uygulamalar, yükleyici kilidi altında başlatma için farklı semantiklere sahiptir ve bu da tek tanım kuralını etkili bir şekilde ihlal eder. Sonuç olarak, bağlayıcı en büyük uygulamayı seçtiğinde, başka bir yerde #pragma unmanaged yönergesi kullanılarak açıkça yerel koda derlenmiş olsa bile bir işlevin MSIL sürümünü seçebilir. Bir şablonun veya satır içi işlevin MSIL sürümünün hiçbir zaman yükleyici kilidi altında çağrılmadığından emin olmak için, yükleyici kilidi altında çağrılan her işlevin her tanımı yönergesiyle #pragma unmanaged değiştirilmelidir. Üst bilgi dosyası üçüncü bir tarafa aitse, bu değişikliği yapmanın en kolay yolu, ilgili üst bilgi dosyasını içeren #include yönergesi etrafında #pragma unmanaged yönergesini push ve pop yapmaktır. (Bir örnek için bkz. yönetilen, yönetilmeyen .) Ancak, bu strateji doğrudan .NET API'lerini çağırması gereken diğer kodları içeren üst bilgiler için çalışmaz.
Yükleyici kilidiyle uğraşan kullanıcılara kolaylık sağlamak için, bağlayıcı hem yerel hem de yönetilen uygulama sunulduğunda, yerel uygulamayı tercih eder. Bu varsayılan ayar yukarıdaki sorunlardan kaçınıyor. Ancak, derleyiciyle ilgili iki çözülmemiş sorun nedeniyle bu sürümde bu kuralın iki özel durumu vardır:
- Satır içi işlev çağrısı genel statik işlev işaretçisi üzerinden yapılır. Bu senaryo, sanal işlevler global fonksiyon işaretçileri aracılığıyla çağrıldığından mümkün değildir. Örneğin,
#include "definesmyObject.h"
#include "definesclassC.h"
typedef void (*function_pointer_t)();
function_pointer_t myObject_p = &myObject;
#pragma unmanaged
void DuringLoaderlock(C & c)
{
// Either of these calls could resolve to a managed implementation,
// at link-time, even if a native implementation also exists.
c.VirtualMember();
myObject_p();
}
Hata Ayıklama Modunda Tanılama
Yükleyici kilidi sorunlarının tüm tanılamaları Hata ayıklama derlemeleriyle yapılmalıdır. Yayın derlemeleri tanılama üretmeyebilir. Yayın modunda yapılan iyileştirmeler, yükleyici kilitleme senaryoları altında MSIL'in bazı bölümlerini gizleyebilir.
Yükleyici kilidi sorunlarını ayıklama
MSIL işlevi çağrıldığında CLR'nin ürettiği tanılama, CLR'nin yürütmeyi durdurmasına neden olur. Bu da işlemde debuggee çalışırken, Visual C++ karışık mod hata ayıklayıcının da askıya alınmasına neden olur. Ancak işleme eklerken, karışık hata ayıklayıcısını kullanarak hata ayıklama için yönetilen bir çağrı yığını elde etmek mümkün değildir.
Yükleyici kilidi altında çağrılan belirli MSIL işlevini tanımlamak için geliştiriciler aşağıdaki adımları tamamlamalıdır:
mscoree.dll ve mscorwks.dll simgelerinin kullanılabilir olduğundan emin olun.
Simgeleri iki şekilde kullanılabilir hale getirebilirsiniz. İlk olarak, mscoree.dll ve mscorwks.dll pdb'leri sembol arama yoluna eklenebilir. Bunları eklemek için simge arama yolu seçenekleri iletişim kutusunu açın. (Araçlar
menüsünden Seçenekler'i seçin. Seçenekler iletişim kutusunun sol bölmesinde Hata Ayıklamadüğümünü açın ve Simgeler'i seçin.) mscoree.dll ve mscorwks.dll PDB dosyalarının yollarını arama listesine ekleyin. Bu PDB'ler %VSINSTALLDIR%\SDK\v2.0\sembollerine yüklenir. Tamam'ı seçin. İkincisi, mscoree.dll ve mscorwks.dll pdb'leri Microsoft Symbol Server'dan indirilebilir. Sembol Sunucusu'nu yapılandırmak için simge arama yolu seçenekleri iletişim kutusunu açın. (Kaynak:Araçlar menüsünde Seçenekler'i seçin. Seçenekler iletişim kutusunun sol bölmesinde Hata Ayıklama düğümünü açın ve Simgeler'i seçin.) Bu arama yolunu arama listesine ekleyin:
https://msdl.microsoft.com/download/symbols. Sembol sunucusu önbellek metin kutusuna bir sembol önbellek dizini ekleyin. Tamam'ı seçin.Hata ayıklayıcı modunu yalnızca yerel moda ayarlayın.
Çözümde başlangıç projesinin Özellikler kılavuzunu açın. Yapılandırma ÖzellikleriHata Ayıklama. Hata Ayıklayıcı Türü özelliğini Yalnızca Yerel olarak ayarlayın.
Hata ayıklayıcıyı (F5) başlatın.
Tanılama oluşturulduğunda Yeniden Dene seçeneğini ve ardından Kes seçeneğini seçin.
Çağrı yığını penceresini açın. (Menü çubuğunda
Hata Ayıklama Windows Yığın Çağrı .) Sorunluveya statik başlatıcı yeşil bir okla tanımlanır. Sorunlu işlev tanımlanmadıysa, bu işlevi bulmak için aşağıdaki adımların izlenmesi gerekir. İzleme penceresini açın (Menü çubuğunda, Hata Ayıkla >Windows>İzleme.)
.load sos.dllifadesini Hemen penceresine SOS hata ayıklama hizmetini yüklemek için girin.!dumpstackifadesini iç Anında penceresine girerek yığının tam listesini alın/clr._CorDllMain (soruna neden oluyorsa
DllMain) veya _VTableBootstrapThunkInitHelperStub ya da GetTargetForVTableEntry (soruna statik başlatıcı neden oluyorsa) yığın altında ilk örneğini arayın. Bu çağrının hemen altındaki yığın girişi, yükleyici kilidi altında yürütülmeye çalışılan MSIL uygulanan işlevinin çağrılmasıdır.Önceki adımda tanımlanan kaynak dosyaya ve satır numarasına gidin ve Senaryolar bölümünde açıklanan senaryoları ve çözümleri kullanarak sorunu düzeltin.
Örnek
Açıklama
Aşağıdaki örnek, kodu genel DllMain bir nesnenin oluşturucusuna taşıyarak yükleyici kilidinin nasıl önleneceklerini gösterir.
Bu örnekte, oluşturucusunda başlangıçta içinde DllMainbulunan yönetilen nesneyi içeren genel bir yönetilen nesne vardır. Bu örneğin ikinci bölümü derlemeye başvurur ve başlatmayı yerine getiren modül oluşturucuyu çağırmak için yönetilen nesnenin bir örneğini oluşturur.
Kod
// initializing_mixed_assemblies.cpp
// compile with: /clr /LD
#pragma once
#include <stdio.h>
#include <windows.h>
struct __declspec(dllexport) A {
A() {
System::Console::WriteLine("Module ctor initializing based on global instance of class.\n");
}
void Test() {
printf_s("Test called so linker doesn't throw away unused object.\n");
}
};
#pragma unmanaged
// Global instance of object
A obj;
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) {
// Remove all managed code from here and put it in constructor of A.
return true;
}
Bu örnekte, karma derlemelerin başlatılmasıyla ilgili sorunlar gösterilmektedir:
// initializing_mixed_assemblies_2.cpp
// compile with: /clr initializing_mixed_assemblies.lib
#include <windows.h>
using namespace System;
#include <stdio.h>
#using "initializing_mixed_assemblies.dll"
struct __declspec(dllimport) A {
void Test();
};
int main() {
A obj;
obj.Test();
}
Bu kod şu çıkışı oluşturur:
Module ctor initializing based on global instance of class.
Test called so linker doesn't throw away unused object.