Windows Tanıtıcısı nedir?


153

Windows'ta kaynakları tartışırken "Tanıtıcı" nedir? Nasıl çalışırlar?

Yanıtlar:


167

Bir kaynağa, genellikle belleğe, açık bir dosyaya veya bir boruya soyut bir referans değerdir.

Düzgün olarak , Windows'da (ve genellikle bilgi işlemde), tanıtıcı, API kullanıcısının gerçek bir bellek adresini gizleyen ve sistemin fiziksel belleği programa şeffaf bir şekilde yeniden düzenlemesine izin veren bir soyutlamadır. Bir tanıtıcıyı işaretçiye dönüştürmek belleği kilitler ve tutamacı serbest bırakmak işaretçiyi geçersiz kılar. Bu durumda bunu bir işaretçi tablosuna dizin olarak düşünün ... sistem API çağrıları için dizini kullanırsınız ve sistem tablodaki işaretçiyi istediği gibi değiştirebilir.

Alternatif olarak, API yazıcısı, API kullanıcısının adresin gösterdiği noktanın özelliklerinden yalıtılmasını istediğinde, tanıtıcı olarak gerçek bir işaretçi verilebilir; bu durumda, tutamacın herhangi bir zamanda değişebileceği dikkate alınmalıdır (API sürümünden sürüme veya hatta, tanıtıcıyı döndüren API'nin çağrısından çağrısına) - bu nedenle, tanıtıcı basit bir opak değer olarak ele alınmalıdır. sadece API için anlamlıdır .

Ben herhangi bir modern işletim sisteminde, sözde "gerçek işaretçiler" bile hala işlemin sanal bellek alanı içinde opak kolları vardır, bu O / S işlem işaretçileri geçersiz olmadan belleği yönetmek ve yeniden düzenlemek için olanak sağlar .


4
Hızlı yanıtı gerçekten takdir ediyorum. Ne yazık ki, hala tam olarak anlayamayacak bir acemi olduğumu düşünüyorum :-(
Al C

4
Genişletilmiş cevabım ışık saçıyor mu?
Lawrence Dol

100

A HANDLE, bağlama özgü benzersiz bir tanımlayıcıdır. Bağlama özgü olarak, bir bağlamdan elde edilen bir tutamacın, HANDLEs üzerinde de çalışan başka herhangi bir diğer bağlamda mutlaka kullanılamayacağını kastediyorum .

Örneğin, GetModuleHandleyüklü bir modüle benzersiz bir tanımlayıcı döndürür. Döndürülen tutamak, modül tutamaçlarını kabul eden diğer işlevlerde kullanılabilir. Diğer tutamaç türlerini gerektiren işlevlere verilemez. Örneğin, dönen bir kolu veremedim GetModuleHandleetmek HeapDestroyve bir şey mantıklı şekilde davranmasını bekliyoruz.

HANDLEKendisi sadece ayrılmaz bir türüdür. Genellikle, ancak zorunlu olmamakla birlikte, altta yatan bazı tür veya bellek konumunun bir göstergesidir. Örneğin, HANDLEdöndürülen GetModuleHandle, aslında modülün temel sanal bellek adresinin bir göstergesidir. Ancak, tutamaçların işaretçi olması gerektiğini belirten bir kural yoktur. Bir tanıtıcı da basit bir tamsayı olabilir (muhtemelen bazı Win32 API tarafından bir dizinin indeksi olarak kullanılabilir).

HANDLEs, dahili Win32 kaynaklarından kapsülleme ve soyutlama sağlayan kasıtlı olarak opak gösterimlerdir. Bu şekilde, Win32 API'leri kullanıcı kodunu herhangi bir şekilde etkilemeden bir HANDLE'ın arkasındaki temel türü değiştirebilir (en azından fikir budur).

Sadece uydurduğum bir Win32 API bu üç farklı iç uygulamalarını düşünün ve farz Widgetbir olduğunu struct.

Widget * GetWidget (std::string name)
{
    Widget *w;

    w = findWidget(name);

    return w;
}
void * GetWidget (std::string name)
{
    Widget *w;

    w = findWidget(name);

    return reinterpret_cast<void *>(w);
}
typedef void * HANDLE;

HANDLE GetWidget (std::string name)
{
    Widget *w;

    w = findWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

İlk örnek, API ile ilgili dahili ayrıntıları ortaya koyar: kullanıcı kodunun GetWidgetbir işaretçiyi bir struct Widget. Bunun birkaç sonucu vardır:

  • kullanıcı kodunun Widgetyapıyı tanımlayan başlık dosyasına erişimi olmalıdır
  • kullanıcı kodu, döndürülen Widgetyapının dahili bölümlerini değiştirebilir

Bu sonuçların her ikisi de istenmeyen olabilir.

İkinci örnek, bu iç ayrıntıyı yalnızca döndürerek kullanıcı kodundan gizler void *. Kullanıcı kodu, Widgetyapıyı tanımlayan başlığa erişim gerektirmez .

Üçüncü örnek, tam olarak ikinci aynıdır, ama biz sadece diyoruz void *a HANDLEyerine. Belki de bu, kullanıcı kodunun tam olarak neyi void *işaret ettiğini bulmaya çalışmaktan vazgeçirir .

Neden bu sorunu yaşıyorsun? Aynı API'nın daha yeni bir sürümünün bu dördüncü örneğini düşünün:

typedef void * HANDLE;

HANDLE GetWidget (std::string name)
{
    NewImprovedWidget *w;

    w = findImprovedWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

Fonksiyonun arayüzünün yukarıdaki üçüncü örnekle aynı olduğuna dikkat edin. Bu, "sahne arkasında" uygulaması NewImprovedWidgetyapıyı kullanmak için değişmiş olsa bile, kullanıcı kodunun API'nın bu yeni sürümünü herhangi bir değişiklik yapmadan kullanmaya devam edebileceği anlamına gelir .

Bu örnekte kolları için gerçekten sadece yeni, muhtemelen dostça, isim olan void *bir tam olarak ne olduğu, HANDLE(bana arama Win32 API olan MSDN at ). Kullanıcı kodu ile Win32 kitaplığının iç gösterimleri arasında, Windows sürümleri arasında Win32 API'sini kullanan kodun taşınabilirliğini artıran opak bir duvar sağlar.


5
Kasıtlı ya da değil, haklısın - konsept kesinlikle opak (en azından benim için :-)
Al C

5
Bazı somut örneklerle orijinal cevabımı genişlettim. Umarım bu, konsepti biraz daha şeffaf hale getirir.
Dan Moulding

2
Çok yardımcı genişleme ... Teşekkürler!
Al C

4
Bu, bir süredir gördüğüm herhangi bir soruya en temiz, doğrudan ve en iyi yazılmış cevaplardan biri olmalı. Yazmaya zaman ayırdığınız için içtenlikle teşekkür ederiz!
Andrew

@DanMoulding: temel nedeni kullanmak Yani handleyerine void *ise tam olarak ne hükümsüz * işaret ettiği anlamaya çalışıyorum zorlaştırır, kullanıcı kodu . Doğrumuyum?
Lion Lai

37

Win32 programlamasındaki bir HANDLE, Windows çekirdeği tarafından yönetilen bir kaynağı temsil eden bir simgedir. Tanıtıcı bir pencere, dosya vb. Olabilir.

Tanıtıcılar, Win32 API'lerini kullanarak çalışmak istediğiniz parçalı bir kaynağı tanımlamanın bir yoludur.

Örneğin, bir Pencere oluşturmak ve ekranda göstermek istiyorsanız aşağıdakileri yapabilirsiniz:

// Create the window
HWND hwnd = CreateWindow(...); 
if (!hwnd)
   return; // hwnd not created

// Show the window.
ShowWindow(hwnd, SW_SHOW);

Yukarıdaki örnekte HWND "pencereye bir tutamaç" anlamına gelir.

Nesneye yönelik bir dile alışkınsanız, bir HANDLE'ı, durumu yalnızca diğer işlevler tarafından değiştirilebilen yöntemsiz bir sınıf örneği olarak düşünebilirsiniz. Bu durumda, ShowWindow işlevi Pencere KOLU durumunu değiştirir.

Daha fazla bilgi için bkz. Tanıtıcılar ve Veri Türleri .


HANDLEADT aracılığıyla başvurulan nesneler çekirdek tarafından yönetilir. Öte yandan adlandırdığınız ( HWND, vb.) Diğer tanıtıcı türleri USER nesneleridir. Bunlar Windows çekirdeği tarafından yönetilmez.
IInspectable

1
@IInspectable tahmin bu User32.dll şeyler tarafından yönetiliyor?
the_endian

8

Tanıtıcı, Windows tarafından yönetilen bir nesne için benzersiz bir tanımlayıcıdır. Bu bir işaretçi gibidir , ancak bazı verilere erişim elde etmek için kullanıcı kodu tarafından kaldırılabilecek bir adres olmadığı için bir işaretçi değildir. Bunun yerine bir tanıtıcı, tanıtıcıyı tanımladığı nesne üzerinde eylemler gerçekleştirebilen bir işlevler kümesine aktarılır.


5

Yani en temel seviyede herhangi bir türden bir HANDLE bir işaretçiye veya

#define HANDLE void **

Şimdi neden kullanmak isteyeceğinize

Bir kurulum yapalım:

class Object{
   int Value;
}

class LargeObj{

   char * val;
   LargeObj()
   {
      val = malloc(2048 * 1000);
   }

}

void foo(Object bar){
    LargeObj lo = new LargeObj();
    bar.Value++;
}

void main()
{
   Object obj = new Object();
   obj.val = 1;
   foo(obj);
   printf("%d", obj.val);
}

Nesne foo değerine göre geçtiğinden (bir kopyasını oluştur ve işleve ver), printf 1'in orijinal değerini yazdıracaktır.

Şimdi foo'yu şu şekilde güncellersek:

void foo(Object * bar)
{
    LargeObj lo = new LargeObj();
    bar->val++;
}

Printf 2'nin güncellenmiş değerini basma şansı vardır. Ancak foo'nun bir tür bellek bozulmasına veya istisnasına neden olma olasılığı da vardır.

Bunun nedeni, şimdi 2 Megs bellek ayırdığınız işleve nesneyi iletmek için bir işaretçi kullanırken, işletim sisteminin nesneyi güncelleştirmek için belleği hareket ettirmesine neden olabilir. İşaretçiyi değere göre geçtiğinizden, nesne hareket ettirilirse işletim sistemi işaretçiyi güncelleştirir ancak işlevdeki kopyayı güncellemez ve muhtemelen sorunlara neden olur.

Aşağıdakilerin yapılması için son güncelleme:

void foo(Object **bar){
    LargeObj lo = LargeObj();
    Object * b = &bar;
    b->val++;
}

Bu her zaman güncellenen değeri basacaktır.

Derleyici, işaretçiler için bellek ayırdığında, bunları taşınmaz olarak işaretler, bu nedenle, tahsis edilen büyük nesnenin neden olduğu belleğin yeniden karıştırılması, işleve iletilen değerin, bellekteki son konumu bulmak için doğru adrese işaret eder. Güncelleme.

Belirli HANDLE türleri (hWnd, FILE, vb.) Alana özgüdür ve bellek bozulmasına karşı koruma sağlamak için belirli bir yapı türüne işaret eder.


1
Bu hatalı akıl yürütmedir; C bellek ayırma alt sistemi yalnızca işaretçileri isteyerek geçersiz kılamaz. Aksi takdirde hiçbir C veya C ++ programı ispatlanamaz şekilde doğru olamazdı; daha da kötüsü, yeterli karmaşıklığa sahip herhangi bir program, tanım gereği açıkça yanlış olur. Bir bulunması şeklinde - Ayrıca, çift indirection işaretçi aslında kendisi gerçek bellekten bir soyutlama olmadığı sürece bellek doğrudan programın altına etrafında hareket ettirilir işaret değilse bu moral kolu .
Lawrence Dol

1
Macintosh işletim sistemi (9 veya 8'e kadar olan sürümlerde) tam olarak yukarıdaki işlemleri yaptı. Bir sistem nesnesi tahsis ettiyseniz, OS'yi nesneyi hareket ettirmek için serbest bırakarak genellikle bir tanıtıcı alırsınız. İlk Mac'lerin sınırlı bellek boyutu ile oldukça önemliydi.
Rhialto, Monica'yı

5

Tanıtıcı, veritabanındaki bir kaydın birincil anahtar değeri gibidir.

edit 1: iyi, neden bir birincil anahtar downvote, bir veritabanı kaydını benzersiz olarak tanımlar ve Windows sistemindeki bir tanıtıcı bir pencere, açık bir dosya, vb benzersiz bir şekilde tanımlar, Ben böyle söylüyorum.


1
Sapın benzersiz olduğunu iddia edebileceğinizi düşünmüyorum. Bir kullanıcının Windows Station'a göre benzersiz olabilir, ancak aynı sisteme aynı anda erişen birden fazla kullanıcı varsa benzersiz olduğu garanti edilmez. Yani, birden fazla kullanıcı sayısal olarak aynı olan bir tutamaç değerini geri alabilir, ancak kullanıcının Windows Station bağlamında farklı şeylerle
Nick

2
@nick Belirli bir bağlamda benzersizdir. Birincil anahtar farklı tablolar arasında da benzersiz olmayacak ...
Benny Mackney

2

Windows'daki pencereyi, onu tanımlayan bir yapı olarak düşünün. Bu yapı, Windows'un dahili bir parçasıdır ve ayrıntılarını bilmenize gerek yoktur. Bunun yerine Windows, bu yapı için işaretçiyi yapılandırması için bir typedef sağlar. Bu, pencerede tutunabileceğiniz "tanıtıcı" dır.,


Doğru, ancak her zaman tanıtıcı genellikle bir bellek adresi değildir ve bir kullanıcı kodunun adresinizi geri çevirmemesi gerektiğini hatırlamakta fayda var.
sharptooth
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.