Karışık Derlemeleri Başlatma

Windows geliştiricileri, sırasında DllMainkod çalıştırırken her zaman yükleyici kilidine karşı 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. Karma DLL yükleme sorunu, Microsoft Windows işletim sistemi yükleyicisinin çevresinde ortalanıyor.

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 yedekli olarak yükleyebilmesini 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, kullanıcılar yükleyici kilidi tutulduğunda (örneğin, statik başlatıcılardan DllMain veya statik başlatıcılarda) Microsoft ara diline (MSIL) derlenmiş işlevleri yürütmeye çalışırsa 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 (aracılığıyla DllMain) ve yönetilen başlatma daha sonra aracılığıyla gerçekleştirilir. NET destekli .cctor yapı. İkinci seçenek, kullanılmadığı veya /NODEFAULTLIB kullanılmadığı sürece /Zl 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 şimdi yeniden üretilir 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ülebileceği kalan 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ı gerçekleşebileceğinden, msil için kullanıcı tarafından sağlanan DllMain hiçbir işlev derlenmemelidir. Ayrıca, içinde köklendirilen DllMain ç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, başka bir modüldeki bir işlevi çağıran DllMain ve ardından MSIL yürütmeyi deneyen durumları algılayamaz.

Bu senaryo hakkında daha fazla bilgi için bkz . Tanılamaya Yönelik Impediments.

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ğerlendirilemez 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 ile /clr derlenip derlenmediğine ve MSIL'in yürütülip 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, ile /clr derlenen statik değişkenler, yönetilmeyen başlatma aşaması tamamlandıktan ve yükleyici kilidi serbest bırakıldıktan sonra tarafından .cctorbaş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 unmanaged derlenebilir.

  • 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şlatma sırasında başlatılmasına bağlı olduğu, kullanıcı tarafından sağlanan birkaç işlev vardır. Örneğin, ve delete işleçleri gibi C++ içindeki işleçleri genel olarak new aşırı yüklerken, C++ Standart Kitaplığı başlatma ve yok etme dahil olmak üzere kullanıcı tarafından sağlanan sürümler 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 malloc bir kullanıcının sonuçları aynı olur. 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ılamaya Yönelik Impediments.

Ö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ı, yönergesi kullanılarak yerel koda #pragma unmanaged 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ılamaya Yönelik Impediments

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.

Üst Bilgilerde 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ı üst bilgi hem etkin hem de devre dışı olan C++ dosyalarına /clr dahil edilebileceğinden veya bir #include bir #pragma unmanaged bloğun içine sarmalanabildiği için, üst bilgilerde uygulama 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, yönergesi kullanılarak başka bir yerde yerel koda açıkça derlenmiş olsa bile bir işlevin #pragma unmanaged 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ı bir üçüncü tarafa aitse, bu değişikliği yapmanın en kolay yolu, #include yönergesini soruna neden olan üst bilgi dosyası için göndermek ve açmaktır #pragma unmanaged . (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 ilgilenen kullanıcılara kolaylık sağlamak için bağlayıcı, her ikisi de sunulduğunda yönetilen yerine yerel uygulamayı seçer. 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. Sanal işlevler genel işlev işaretçileri aracılığıyla çağrıldığından bu senaryo tablodur. Ö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 kilit senaryoları altında MSIL'den bazılarını maskeler.

Yükleyici kilidi sorunlarının hatalarını ayıklama

BIR MSIL işlevi çağrıldığında CLR'nin oluşturduğu tanılama, CLR'nin yürütmeyi askıya almasına neden olur. Bu da işlemdeki debuggee çalıştırılırken Visual C++ karma mod hata ayıklayıcının 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:

  1. 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. (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.) mscoree.dll yolunu ekleyin ve PDB dosyalarını arama listesine mscorwks.dll. 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.

  2. Hata ayıklayıcı modunu yalnızca yerel moda ayarlayın.

    Çözümde başlangıç projesi için Özellikler kılavuzunu açın. Yapılandırma Özellikleri>Hata Ayıklama'ya tıklayın. Hata Ayıklayıcı Türü özelliğini Yalnızca Yerel olarak ayarlayın.

  3. Hata ayıklayıcıyı (F5) başlatın.

  4. /clr Tanılama oluşturulduğunda Yeniden Dene'yi ve ardından Kes'i seçin.

  5. Çağrı yığını penceresini açın. (Menü çubuğunda Windows>Çağrı Yığınında Hata Ayıklama>.) Sorunlu DllMain veya 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.

  6. Hemen penceresini açın (Menü çubuğunda Windows>Anında Hatalarını Ayıkla'yı>seçin.)

  7. SOS hata ayıklama hizmetini yüklemek için Anında penceresine girin.load sos.dll.

  8. İç /clr yığının tam listesini almak için Anında penceresine girin!dumpstack.

  9. _CorDllMain (soruna neden oluyorsa DllMain ) veya _VTableBootstrapThunkInitHelperStub ya da GetTargetForVTableEntry (soruna statik başlatıcı neden oluyorsa) ilk örneğini (yığının en altına yakın) 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.

  10. Ö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.

Ayrıca bkz.

Karışık (Yerel ve Yönetilen) Derlemeler