หมายเลขอ้างอิงของ Windows คืออะไร


คำตอบ:


167

มันเป็นค่าอ้างอิงเชิงนามธรรมไปยังทรัพยากรซึ่งมักจะเป็นหน่วยความจำหรือไฟล์ที่เปิดอยู่หรือไปป์

อย่างถูกต้องใน Windows (และโดยทั่วไปในการคำนวณ) การจัดการเป็นนามธรรมซึ่งซ่อนที่อยู่หน่วยความจำจริงจากผู้ใช้ API ช่วยให้ระบบสามารถจัดระเบียบหน่วยความจำทางกายภาพโปร่งใสกับโปรแกรม การแก้ไขหมายเลขอ้างอิงเป็นตัวชี้ล็อคหน่วยความจำและการปล่อยหมายเลขอ้างอิงจะทำให้ตัวชี้ใช้ไม่ได้ ในกรณีนี้ให้คิดว่ามันเป็นดัชนีในตารางพอยน์เตอร์ ... คุณใช้ดัชนีสำหรับการเรียก API ของระบบและระบบสามารถเปลี่ยนตัวชี้ในตารางได้ตามต้องการ

อีกทางเลือกหนึ่งอาจเป็นตัวชี้ที่แท้จริงเป็นที่จับเมื่อผู้เขียน API ตั้งใจว่าผู้ใช้ของ API จะได้รับการหุ้มฉนวนจากข้อมูลเฉพาะของที่อยู่ที่ส่งกลับจุดไปยัง; ในกรณีนี้จะต้องพิจารณาว่าจุดจับอาจเปลี่ยนแปลงได้ตลอดเวลา (จากรุ่น API ไปเป็นรุ่นหรือแม้กระทั่งจากการเรียกไปยังการเรียก API ที่ส่งกลับหมายเลขอ้างอิง) - การจัดการจึงควรถือว่าเป็นค่าทึบแสง มีความหมายเฉพาะกับ API

ฉันควรเพิ่มสิ่งนั้นในระบบปฏิบัติการที่ทันสมัยแม้กระทั่งสิ่งที่เรียกว่า "ตัวชี้จริง" ยังคงจัดการทึบแสงในพื้นที่หน่วยความจำเสมือนของกระบวนการซึ่งทำให้ O / S สามารถจัดการและจัดเรียงหน่วยความจำใหม่โดยไม่ทำให้ตัวชี้ภายในกระบวนการ .


4
ฉันซาบซึ้งกับการตอบสนองที่รวดเร็ว แต่น่าเสียดายที่ผมคิดว่าผมคงมีมากเกินไปของมือใหม่เข้าใจมัน :-(
อัล C

4
คำตอบที่ขยายออกของฉันทำให้เกิดแสงสว่างหรือไม่?
Lawrence Dol

100

A HANDLEเป็นตัวระบุเฉพาะบริบท โดยเฉพาะบริบทฉันหมายถึงว่าหมายเลขอ้างอิงที่ได้รับจากบริบทหนึ่งไม่สามารถใช้ในบริบท aribtrary อื่น ๆ ที่ใช้ได้กับHANDLEs ด้วย

ตัวอย่างเช่นGetModuleHandleส่งคืนตัวบ่งชี้เฉพาะไปยังโมดูลที่โหลดในปัจจุบัน หมายเลขอ้างอิงที่ส่งคืนสามารถใช้ในฟังก์ชันอื่น ๆ ที่ยอมรับหมายเลขอ้างอิงของโมดูล ไม่สามารถมอบให้กับฟังก์ชั่นที่ต้องใช้มือจับชนิดอื่นได้ ตัวอย่างเช่นคุณไม่สามารถให้จับกลับมาจากการGetModuleHandleไปHeapDestroyและคาดว่าจะทำอะไรบางอย่างที่เหมาะสม

HANDLEตัวเองเป็นเพียงชนิดหนึ่ง โดยปกติแล้ว แต่ไม่จำเป็นว่าจะเป็นตัวชี้ไปยังตำแหน่งหรือหน่วยความจำประเภทพื้นฐานบางอย่าง ตัวอย่างเช่นการHANDLEส่งคืนโดยGetModuleHandleเป็นตัวชี้ไปยังที่อยู่หน่วยความจำเสมือนพื้นฐานของโมดูล แต่ไม่มีกฎที่ระบุว่าการจัดการต้องเป็นตัวชี้ หมายเลขอ้างอิงอาจเป็นจำนวนเต็มอย่างง่าย (ซึ่งอาจใช้โดย Win32 API บางตัวเป็นดัชนีลงในอาร์เรย์)

HANDLEs เป็นตัวแทนทึบแสงโดยเจตนาที่ให้การห่อหุ้มและนามธรรมจากทรัพยากร Win32 ภายใน ด้วยวิธีนี้ Win32 APIs อาจเปลี่ยนประเภทพื้นฐานที่อยู่เบื้องหลัง HANDLE โดยไม่ส่งผลกระทบต่อรหัสผู้ใช้ในทางใดทางหนึ่ง (อย่างน้อยก็เป็นความคิด)

พิจารณาทั้งสามการใช้งานที่แตกต่างกันภายในของ Win32 API ที่ผมเพิ่งทำขึ้นและคิดว่าเป็นWidgetstruct

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);
}

ตัวอย่างแรก exposes รายละเอียดภายในเกี่ยวกับ API: มันช่วยให้รหัสผู้ใช้ที่จะรู้ว่าผลตอบแทนที่ชี้ไปที่GetWidget struct Widgetสิ่งนี้มีผลสืบเนื่องสองประการ:

  • รหัสผู้ใช้จะต้องมีการเข้าถึงไฟล์ส่วนหัวที่กำหนดWidgetstruct
  • รหัสผู้ใช้อาจจะปรับเปลี่ยนชิ้นส่วนภายในของกลับWidgetstruct

ทั้งสองอย่างนี้อาจไม่เป็นที่พึงปรารถนา

void *ตัวอย่างที่สองซ่อนรายละเอียดภายในนี้จากรหัสผู้ใช้โดยการกลับเพียง รหัสผู้ใช้ไม่จำเป็นต้องเข้าถึงส่วนหัวที่กำหนดWidgetstruct

ตัวอย่างที่สามนั้นเหมือนกับที่สอง แต่เราแค่เรียกvoid *a HANDLEแทน บางทีนี่อาจเป็นสาเหตุทำให้รหัสผู้ใช้ไม่สามารถระบุได้ว่าเป็นvoid *จุดใด

ทำไมต้องเจอกับปัญหานี้? ลองพิจารณาตัวอย่างที่สี่ของ API รุ่นเดียวกันนี้:

typedef void * HANDLE;

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

    w = findImprovedWidget(name);

    return reinterpret_cast<HANDLE>(w);
}

ขอให้สังเกตว่าอินเทอร์เฟซของฟังก์ชันเหมือนกับตัวอย่างที่สามด้านบน ซึ่งหมายความว่ารหัสผู้ใช้สามารถใช้ API เวอร์ชันใหม่นี้ได้อย่างต่อเนื่องโดยไม่มีการเปลี่ยนแปลงใด ๆ แม้ว่าการใช้ "เบื้องหลัง" ได้เปลี่ยนไปใช้NewImprovedWidgetstruct แทน

หมายเลขอ้างอิงในตัวอย่างเหล่านี้เป็นชื่อใหม่ที่น่าเป็นมิตรvoid *ซึ่งน่าจะเป็นสิ่งที่HANDLEอยู่ใน Win32 API (ดูที่ MSDN ) มันให้ผนังทึบระหว่างรหัสผู้ใช้และการเป็นตัวแทนภายในของห้องสมุด Win32 ที่เพิ่มความสะดวกในการพกพาระหว่างรุ่นของ Windows ของรหัสที่ใช้ Win32 API


5
โดยเจตนาหรือไม่คุณขวา - เป็นแนวคิดทึบแสงแน่นอน (อย่างน้อยให้ฉัน :-)
อัล C

5
ฉันขยายคำตอบดั้งเดิมของฉันด้วยตัวอย่างที่เป็นรูปธรรม หวังว่านี่จะทำให้แนวคิดมีความโปร่งใสมากขึ้น
Dan Molding

2
การขยายตัวที่เป็นประโยชน์มาก ... ขอบคุณ!
Al C

4
นี่จะเป็นการตอบคำถามที่ดีที่สุดโดยตรงและเป็นลายลักษณ์อักษรที่ดีที่สุดสำหรับคำถามใด ๆ ที่ฉันได้เห็นในขณะที่ ขอบคุณอย่างจริงใจที่สละเวลาเขียนมัน!
แอนดรู

@DanMoulding: ดังนั้นเหตุผลหลักที่ใช้handleแทนvoid *คือทำให้ไม่สามารถใช้รหัสผู้ใช้ในการหาว่า void * ชี้ไปที่อะไร ฉันถูกไหม?
Lion Lai

37

ด้ามจับในการเขียนโปรแกรม Win32 เป็นโทเค็นที่แสดงถึงทรัพยากรที่ได้รับการจัดการโดยเคอร์เนลของ Windows หมายเลขอ้างอิงสามารถไปที่หน้าต่างไฟล์ ฯลฯ

ที่จับเป็นเพียงวิธีการระบุทรัพยากรอนุภาคที่คุณต้องการทำงานด้วยการใช้ Win32 APIs

ตัวอย่างเช่นหากคุณต้องการสร้างหน้าต่างและแสดงบนหน้าจอคุณสามารถทำสิ่งต่อไปนี้:

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

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

ในตัวอย่างข้างต้น HWND หมายถึง "การจัดการกับหน้าต่าง"

หากคุณคุ้นเคยกับภาษาเชิงวัตถุคุณสามารถนึกถึง HANDLE เป็นตัวอย่างของคลาสที่ไม่มีวิธีการใดที่สถานะของมันสามารถแก้ไขได้โดยฟังก์ชันอื่นเท่านั้น ในกรณีนี้ฟังก์ชั่นShowWindowจะแก้ไขสถานะของ Window HANDLE

ดูที่จัดการและประเภทข้อมูลสำหรับข้อมูลเพิ่มเติม


วัตถุที่อ้างอิงผ่านHANDLEADT ได้รับการจัดการโดยเคอร์เนล หมายเลขอ้างอิงอื่น ๆ ที่คุณตั้งชื่อ ( HWNDและอื่น ๆ ) จะเป็นวัตถุของ USER สิ่งเหล่านี้ไม่ได้ถูกจัดการโดยเคอร์เนลของ Windows
IIsspectable

1
@Ispectable คาดเดาสิ่งเหล่านั้นได้รับการจัดการโดยสิ่ง User32.dll?
the_endian

8

หมายเลขอ้างอิงเป็นตัวระบุเฉพาะสำหรับวัตถุที่จัดการโดย Windows มันเหมือนตัวชี้แต่ไม่ใช่ตัวชี้ในความหมายที่ไม่ใช่ที่อยู่ซึ่งสามารถถูกอ้างอิงโดยรหัสผู้ใช้เพื่อเข้าถึงข้อมูลบางอย่าง แทนที่จะส่งผ่านหมายเลขอ้างอิงไปยังชุดของฟังก์ชันที่สามารถดำเนินการกับวัตถุที่หมายเลขอ้างอิงระบุได้


5

ดังนั้นในระดับพื้นฐานที่สุดด้ามจับของการเรียงลำดับใด ๆ คือตัวชี้ไปยังตัวชี้หรือ

#define HANDLE void **

ทีนี้ทำไมคุณถึงต้องการใช้มัน

ให้ทำการตั้งค่า:

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);
}

ดังนั้นเนื่องจาก obj ถูกส่งผ่านตามค่า (ทำสำเนาและมอบให้กับฟังก์ชัน) เพื่อ foo, printf จะพิมพ์ค่าเดิมที่ 1

ตอนนี้ถ้าเราอัพเดต foo เป็น:

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

มีโอกาสที่ printf จะพิมพ์ค่าที่อัพเดตเป็น 2 แต่ยังมีความเป็นไปได้ที่ foo จะทำให้หน่วยความจำเสียหายหรือข้อยกเว้นบางรูปแบบ

เหตุผลก็คือขณะที่คุณใช้ตัวชี้เพื่อส่ง obj ไปยังฟังก์ชั่นที่คุณจัดสรรหน่วยความจำ 2 เมกะไบต์ซึ่งอาจทำให้ระบบปฏิบัติการย้ายหน่วยความจำรอบการอัพเดตตำแหน่งของ obj เนื่องจากคุณผ่านตัวชี้ตามค่าถ้า obj ถูกย้ายแล้วระบบปฏิบัติการจะอัปเดตตัวชี้ แต่ไม่คัดลอกในฟังก์ชันและอาจทำให้เกิดปัญหา

การปรับปรุงครั้งสุดท้ายเพื่อ foo ของ:

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

นี่จะพิมพ์ค่าที่อัพเดตเสมอ

โปรดดูเมื่อคอมไพเลอร์จัดสรรหน่วยความจำสำหรับพอยน์เตอร์มันจะทำเครื่องหมายว่าเป็นอสังหาริมทรัพย์ดังนั้นการสับหน่วยความจำใหม่ที่เกิดจากวัตถุขนาดใหญ่ที่จัดสรรค่าที่ส่งผ่านไปยังฟังก์ชันจะชี้ไปยังที่อยู่ที่ถูกต้องเพื่อค้นหาตำแหน่งสุดท้ายในหน่วยความจำ ปรับปรุง

ด้ามจับชนิดใด ๆ (hWnd, FILE, ฯลฯ ) เป็นโดเมนเฉพาะและชี้ไปที่โครงสร้างประเภทหนึ่งเพื่อป้องกันความเสียหายของหน่วยความจำ


1
นี่คือเหตุผลที่มีข้อบกพร่อง; ระบบย่อยการจัดสรรหน่วยความจำ C ไม่สามารถลบล้างพอยน์เตอร์ได้ตามต้องการ มิฉะนั้นจะไม่มีโปรแกรม C หรือ C ++ ที่สามารถพิสูจน์ได้อย่างถูกต้อง โปรแกรมใดที่มีความซับซ้อนเพียงพอจะไม่ถูกต้องตามข้อกำหนด นอกจากร้ายคู่ไม่ได้ความช่วยเหลือหากหน่วยความจำแหลมโดยตรงกับการถูกย้ายไปรอบ ๆ ที่อยู่ภายใต้โปรแกรมเว้นแต่ตัวชี้เป็นจริงตัวเองเป็นนามธรรมจากหน่วยความจำจริง - ซึ่งจะทำให้มันเป็นที่จับ
Lawrence Dol

1
ระบบปฏิบัติการ Macintosh (ในเวอร์ชันสูงสุด 9 หรือ 8) ทำสิ่งที่กล่าวมาแล้วข้างต้น หากคุณจัดสรรวัตถุระบบบางอย่างคุณมักจะได้รับการจัดการปล่อยให้ระบบปฏิบัติการเป็นอิสระในการเคลื่อนย้ายวัตถุรอบ ๆ ด้วยขนาดหน่วยความจำที่ จำกัด ของ Mac เครื่องแรกที่ค่อนข้างสำคัญ
Rhialto ให้การสนับสนุนโมนิกา

5

หมายเลขอ้างอิงเป็นเหมือนค่าคีย์หลักของระเบียนในฐานข้อมูล

แก้ไข 1: ดีทำไม downvote เป็นคีย์หลักระบุระเบียนฐานข้อมูลและหมายเลขอ้างอิงในระบบ Windows ระบุหน้าต่างหน้าต่างเปิดไฟล์ ฯลฯ นั่นคือสิ่งที่ฉันพูด


1
ฉันไม่คิดว่าคุณสามารถยืนยันได้ว่ามือจับนั้นมีเอกลักษณ์ มันอาจจะไม่ซ้ำกันต่อสถานี Windows ของผู้ใช้ แต่ไม่รับประกันว่าจะไม่ซ้ำกันหากมีผู้ใช้หลายคนเข้าถึงระบบเดียวกันในเวลาเดียวกัน นั่นคือผู้ใช้หลายคนสามารถเรียกคืนค่าการจัดการที่เหมือนกันเป็นตัวเลข แต่ในบริบทของ Windows Station ของผู้ใช้พวกเขาแมปกับสิ่งต่าง ๆ ...
Nick

2
@nick มันไม่เหมือนใครในบริบทที่กำหนด คีย์หลักจะไม่ซ้ำกันระหว่างตารางที่แตกต่างกันเช่นกัน ...
Benny Mackney

2

คิดว่าหน้าต่างใน Windows เป็นโครงสร้างที่อธิบาย struct นี้เป็นส่วนภายในของ Windows และคุณไม่จำเป็นต้องรู้รายละเอียด แต่ Windows จะให้ typedef สำหรับตัวชี้ไปยัง struct สำหรับ struct นั้น นั่นคือ "การจัดการ" โดยที่คุณสามารถกดค้างไว้ที่หน้าต่าง


จริง แต่มันก็คุ้มค่าที่จะจำได้ว่าหมายเลขอ้างอิงนั้นมักจะไม่ใช่ที่อยู่หน่วยความจำและรหัสผู้ใช้หนึ่งรหัสไม่ควรตรวจสอบซ้ำ
sharptooth
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.