การทำงานร่วมกันระหว่าง COM และตัวห่อที่เรียกใช้ได้ขณะรันไทม์
เรียกใช้คอมโพเนนต์ COM จาก C# ด้วย RCW นำเข้าไลบรารีชนิดข้อมูล และจัดการข้อยกเว้น HRESULT
การทำงานร่วมกันระหว่าง COM และตัวห่อที่เรียกใช้ได้ขณะรันไทม์ เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน
COM Interop คืออะไร
COM (โมเดลอ็อบเจ็กต์คอมโพเนนต์) คือมาตรฐานอินเทอร์เฟซไบนารีรุ่นเก่าของ Microsoft ซึ่งยังคงใช้โดย Office, Windows Shell, API รุ่นเก่าของ DirectX และเครื่องมือสำหรับองค์กรอีกมากมาย .NET สามารถใช้คอมโพเนนต์ COM ผ่านตัวห่อหุ้มการทำงานร่วมกัน ซึ่งแปลงระหว่างอ็อบเจ็กต์ที่จัดการโดย .NET กับอินเทอร์เฟซ COM
ตัวห่อหุ้มที่เรียกใช้ได้ขณะทำงาน (RCW)
เมื่อคุณเข้าถึงอ็อบเจ็กต์ COM จาก .NET, CLR จะสร้างตัวห่อหุ้มที่เรียกใช้ได้ขณะทำงาน (RCW) ซึ่งเป็นพร็อกซีที่จัดการโดย .NET และห่อหุ้มอ็อบเจ็กต์ COM ไว้ RCW จัดการการนับจำนวนการอ้างอิง (AddRef/Release) การทำงานของเธรดแบบอพาร์ตเมนต์ และการมาร์แชลลิงระหว่างชนิดข้อมูลของ COM กับ .NET โดยอัตโนมัติ
// The RCW is created automatically when you instantiate a COM class
// via a registered ProgID or CLSID
// Example: create an Excel Application COM object
Type excelType = Type.GetTypeFromProgID("Excel.Application")!;
object excelApp = Activator.CreateInstance(excelType)!;
// excelApp is an RCW — the CLR wraps the underlying IDispatch COM object
// COM AddRef is called; CLR tracks references
Console.WriteLine(excelApp.GetType().Name); // ApplicationClassการนำเข้าไลบรารีชนิดข้อมูล (TLB)
เครื่องมือ tlbimp.exe (ตัวนำเข้าไลบรารีชนิดข้อมูล) จะอ่านไลบรารีชนิดข้อมูล COM (.tlb หรือไลบรารีที่ฝังอยู่ใน .dll) แล้วสร้างแอสเซมบลีการทำงานร่วมกันของ .NET ซึ่งมีคลาส RCW และคำจำกัดความอินเทอร์เฟซที่มีชนิดข้อมูลชัดเจน
// From a Developer Command Prompt:
// tlbimp MyComLib.dll /out:MyComLib.Interop.dll
// Or reference via Visual Studio:
// Add Reference → COM → Microsoft Excel 16.0 Object Library
// → Generates Microsoft.Office.Interop.Excel.dll automatically
// The generated Interop assembly contains:
// - Interface types (matching COM vtable layout)
// - Co-class wrappers (implement the interfaces)
// - Enum types (from the type library)
// - Delegate types for COM event sinksการใช้อ็อบเจ็กต์ COM ผ่านแอสเซมบลีการทำงานร่วมกัน
เมื่ออ้างอิงแอสเซมบลีการทำงานร่วมกันแล้ว ชนิดข้อมูลของ COM จะดูเหมือนชนิดข้อมูล .NET ทั่วไป การเรียกเมธอดจะถูกแปลงผ่าน RCW ไปเป็นการเรียกใช้ผ่านตาราง vtable ของ COM ควรคืนทรัพยากรของอ็อบเจ็กต์ COM อย่างถูกต้องเสมอ เพื่อหลีกเลี่ยงการรั่วไหลของทรัพยากร
using Microsoft.Office.Interop.Excel;
Application excel = new Application();
excel.Visible = false;
Workbooks books = excel.Workbooks;
Workbook wb = books.Add();
Worksheet ws = (Worksheet)wb.Sheets[1];
((Range)ws.Cells[1, 1]).Value = "Hello, COM!";
wb.SaveAs(@"C:\Temp\test.xlsx");
wb.Close();
// Release COM RCW explicitly
Marshal.ReleaseComObject(ws);
Marshal.ReleaseComObject(wb);
Marshal.ReleaseComObject(books);
excel.Quit();
Marshal.ReleaseComObject(excel);Marshal.ReleaseComObject
COM ใช้การนับจำนวนการอ้างอิง คือ AddRef/Release RCW จะเรียก Release ก็ต่อเมื่อถูกเก็บโดย GC ซึ่งอาจเกิดขึ้นในภายหลังมาก ให้เรียก Marshal.ReleaseComObject() เพื่อลดจำนวนการอ้างอิงของ RCW และปล่อยอ็อบเจ็กต์ COM ทันทีโดยไม่ต้องรอ GC
// Pattern: release COM objects in finally block
Application? excel = null;
Workbook? wb = null;
try
{
excel = new Application();
wb = excel.Workbooks.Add();
// ... do work ...
}
finally
{
if (wb != null) Marshal.ReleaseComObject(wb);
if (excel != null)
{
excel.Quit();
Marshal.ReleaseComObject(excel);
}
// Force GC to clean up any remaining RCWs
GC.Collect();
GC.WaitForPendingFinalizers();
}HRESULT และข้อยกเว้น COM
เมธอด COM จะส่งคืนค่า HRESULT เพื่อบ่งชี้ว่าสำเร็จหรือล้มเหลว RCW จะตรวจสอบ HRESULT โดยอัตโนมัติ และโยน COMException หรือข้อยกเว้นที่เฉพาะเจาะจงกว่านั้นเมื่อค่าดังกล่าวบ่งชี้ถึงความล้มเหลว คุณจัดการข้อยกเว้นเหล่านี้ด้วย try/catch ตามปกติ
try
{
// COM method that might fail
Workbook wb = excel.Workbooks.Open(@"C:\missing.xlsx");
}
catch (COMException ex) when (ex.HResult == unchecked((int)0x800A03EC))
{
// Excel-specific HRESULT for file not found
Console.WriteLine($"Excel error: {ex.Message}");
}
catch (COMException ex)
{
// General COM failure
Console.WriteLine($"COM error 0x{ex.HResult:X8}: {ex.Message}");
}การผูกล่าช้าด้วย dynamic
หากคุณไม่มีไลบรารีชนิดข้อมูลหรือแอสเซมบลีการทำงานร่วมกัน คุณสามารถใช้คีย์เวิร์ด dynamic ของ C# เพื่อเรียกใช้ COM แบบผูกล่าช้า (IDispatch) ได้ วิธีนี้ช้ากว่าเนื่องจากรหัสการเรียกใช้จะถูกค้นหาขณะทำงาน แต่ไม่จำเป็นต้องมีตัวห่อหุ้มที่สร้างขึ้น
// Late-bound COM via dynamic — no interop DLL needed
Type type = Type.GetTypeFromProgID("Word.Application")!;
dynamic word = Activator.CreateInstance(type)!;
word.Visible = false;
dynamic docs = word.Documents;
dynamic doc = docs.Add();
doc.Content.Text = "Late-bound COM example";
doc.SaveAs2(@"C:\Temp\test.docx");
doc.Close();
word.Quit();
Marshal.ReleaseComObject(doc);
Marshal.ReleaseComObject(docs);
Marshal.ReleaseComObject(word);ตัวรับเหตุการณ์ของ COM
อ็อบเจ็กต์ COM ส่งเหตุการณ์ผ่านจุดเชื่อมต่อ แอสเซมบลีการทำงานร่วมกันจะสร้างอินเทอร์เฟซตัวรับเหตุการณ์ให้ คุณสมัครรับเหตุการณ์โดยใช้ดีลีเกตหรือเหตุการณ์ของ .NET ตามปกติ และ RCW จะจัดการการเชื่อมต่อ IConnectionPoint ของ COM ให้
using Microsoft.Office.Interop.Excel;
Application excel = new Application();
excel.Visible = true;
// Subscribe to COM event via generated event wrapper
excel.WorkbookBeforeClose += (wb, ref cancel) =>
{
Console.WriteLine($"Closing: {wb.Name}");
// Set cancel = true to prevent close
};
excel.WorkbookOpen += (wb) =>
{
Console.WriteLine($"Opened: {wb.Name}");
};
// Open a workbook to trigger events
excel.Workbooks.Open(@"C:\Temp\test.xlsx");อพาร์ตเมนต์ของ COM: STA กับ MTA
คอมโพเนนต์ COM จำนวนมาก โดยเฉพาะคอมโพเนนต์ที่เกี่ยวข้องกับ UI เช่น Office ต้องใช้อพาร์ตเมนต์แบบเธรดเดียว (STA) ให้กำกับเธรดที่สร้างอ็อบเจ็กต์ COM แบบ STA ด้วย [STAThread] (Main) หรือตั้งค่าสถานะอพาร์ตเมนต์ของเธรดก่อนเริ่มทำงานเสมอ
// Console apps default to MTA — COM Office automation requires STA
// Option 1: Mark Main with [STAThread]
[STAThread]
static void Main()
{
var excel = new Microsoft.Office.Interop.Excel.Application();
// ...
}
// Option 2: Run on an STA thread manually
Thread staThread = new Thread(() =>
{
var excel = new Microsoft.Office.Interop.Excel.Application();
// ...
excel.Quit();
Marshal.ReleaseComObject(excel);
});
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start();
staThread.Join();ComImport และการประกาศ COM ด้วยตนเอง
คุณสามารถประกาศอินเทอร์เฟซ COM ด้วยตนเองโดยใช้แอตทริบิวต์ [ComImport], [Guid] และ [InterfaceType] ซึ่งมีประโยชน์เมื่อไม่มีไลบรารีชนิดข้อมูล หรือเมื่อคุณต้องการใช้เพียงบางส่วนของ API COM ขนาดใหญ่
[ComImport]
[Guid("00000000-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IUnknown
{
void QueryInterface(ref Guid riid, out IntPtr ppvObject);
int AddRef();
int Release();
}
// Declare a specific COM interface you want to consume:
[ComImport]
[Guid("0000010C-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPersist
{
void GetClassID(out Guid pClassID);
}ตรวจสอบความเข้าใจ
Runtime Callable Wrapper (RCW) ใน COM Interop ของ .NET มีหน้าที่อะไร
ทบทวน: COM Interop และตัวห่อหุ้มที่เรียกใช้ได้ขณะทำงาน
ประเด็นสำคัญ:
- RCW คือพร็อกซีที่จัดการโดย .NET ซึ่งห่อหุ้มอ็อบเจ็กต์ COM ไว้ และ CLR จะสร้างขึ้นโดยอัตโนมัติสำหรับอ็อบเจ็กต์ COM แต่ละรายการ
- ใช้
tlbimp.exeหรือการอ้างอิงของ VS เพื่อสร้างแอสเซมบลีการทำงานร่วมกันที่มีชนิดข้อมูลชัดเจน - เรียก
Marshal.ReleaseComObject()เพื่อปล่อยอ็อบเจ็กต์ COM ทันที โดยไม่ต้องรอ GC - ข้อผิดพลาดของ COM จะปรากฏเป็น
COMExceptionพร้อม HRESULT เดิม dynamicช่วยให้เรียกใช้ COM แบบผูกล่าช้าได้โดยไม่ต้องมีแอสเซมบลีการทำงานร่วมกัน- คอมโพเนนต์ COM ของ Office/UI ต้องใช้เธรด STA ให้ใช้
[STAThread]หรือตั้งค่าสถานะอพาร์ตเมนต์
คำถามที่พบบ่อย
บทเรียน “การทำงานร่วมกันระหว่าง COM และตัวห่อที่เรียกใช้ได้ขณะรันไทม์” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การทำงานร่วมกันระหว่าง COM และตัวห่อที่เรียกใช้ได้ขณะรันไทม์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การทำงานร่วมกันระหว่าง COM และตัวห่อที่เรียกใช้ได้ขณะรันไทม์”
เรียกใช้คอมโพเนนต์ COM จาก C# ด้วย RCW นำเข้าไลบรารีชนิดข้อมูล และจัดการข้อยกเว้น HRESULT คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “การทำงานร่วมกันระหว่าง COM และตัวห่อที่เรียกใช้ได้ขณะรันไทม์” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม
ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- พื้นฐาน P/Invoke
- LibraryImport และ P/Invoke ที่สร้างจากซอร์ส
- โค้ดไม่ปลอดภัย พอยน์เตอร์ และบัฟเฟอร์ขนาดคงที่
- การทำงานร่วมกันระหว่าง COM และตัวห่อที่เรียกใช้ได้ขณะรันไทม์