2010-10-13 15 views
10

Tôi viết một ứng dụng trong C#, .NET 3.0 trong VS2005 với tính năng giám sát chèn/đẩy các ổ đĩa di động khác nhau (đĩa flash USB, CD-ROM, v.v.). Tôi không muốn sử dụng WMI, vì nó có thể đôi khi không rõ ràng (ví dụ: nó có thể sinh ra nhiều sự kiện chèn cho một ổ đĩa USB), vì vậy tôi chỉ cần ghi đè WndProc của biểu mẫu chính để nhận thông báo WM_DEVICECHANGE, như được đề xuất here. Hôm qua tôi gặp phải vấn đề khi nó bật ra rằng tôi sẽ phải sử dụng WMI anyway để lấy một số chi tiết đĩa tối nghĩa như một số serial. Nó chỉ ra rằng gọi WMI thói quen từ bên trong WndProc ném DisconnectedContext MDA.DisconnectedContext MDA khi gọi các hàm WMI trong ứng dụng đơn luồng

Sau một số lần đào, tôi đã kết thúc với một giải pháp khó xử cho điều đó. Mã này như sau:

// the function for calling WMI 
    private void GetDrives() 
    { 
     ManagementClass diskDriveClass = new ManagementClass("Win32_DiskDrive"); 
     // THIS is the line I get DisconnectedContext MDA on when it happens: 
     ManagementObjectCollection diskDriveList = diskDriveClass.GetInstances(); 
     foreach (ManagementObject dsk in diskDriveList) 
     { 
      // ... 
     } 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     // here it works perfectly fine 
     GetDrives(); 
    } 


    protected override void WndProc(ref Message m) 
    { 
     base.WndProc(ref m); 

     if (m.Msg == WM_DEVICECHANGE) 
     { 
      // here it throws DisconnectedContext MDA 
      // (or RPC_E_WRONG_THREAD if MDA disabled) 
      // GetDrives(); 
      // so the workaround: 
      DelegateGetDrives gdi = new DelegateGetDrives(GetDrives); 
      IAsyncResult result = gdi.BeginInvoke(null, ""); 
      gdi.EndInvoke(result); 
     } 
    } 
    // for the workaround only 
    public delegate void DelegateGetDrives(); 

về cơ bản có nghĩa là chạy quy trình liên quan đến WMI trên một sợi riêng biệt - nhưng sau đó, chờ đợi hoàn thành.

Bây giờ, câu hỏi là: lý do tại sao hoạt động và tại sao phải làm như vậy? (hoặc, phải không?)

Tôi không hiểu thực tế nhận được DisconnectedContext MDA hoặc RPC_E_WRONG_THREAD ở địa điểm đầu tiên. Làm thế nào để chạy thủ tục GetDrives() từ trình xử lý sự kiện nhấn nút khác với việc gọi nó từ WndProc? Chúng không xảy ra trên cùng một chuỗi chính của ứng dụng của tôi? BTW, ứng dụng của tôi là hoàn toàn đơn luồng, vậy tại sao tất cả các lỗi đột ngột đề cập đến một số 'sai thread'? Việc sử dụng WMI ngụ ý đa luồng và xử lý đặc biệt các chức năng từ System.Management?

Trong thời gian chờ đợi, tôi đã tìm thấy một câu hỏi khác liên quan đến MDA đó, đó là here. OK, tôi có thể gọi nó là WMI có nghĩa là tạo ra một chuỗi riêng biệt cho thành phần COM cơ bản - nhưng nó vẫn không xảy ra với tôi tại sao không cần có ma thuật khi gọi nó sau khi nhấn nút và thực hiện phép thuật khi gọi nó từ WndProc.

Tôi thực sự bối rối về điều đó và sẽ đánh giá cao một số làm rõ về vấn đề đó. Chỉ có một vài tồi tệ hơn mọi thứ vì phải một giải pháp và không biết lý do tại sao nó hoạt động:/

Chúc mừng, Aleksander

+1

Cùng một sự cố ở đây! Tôi ước có một giải pháp. Tôi sẽ thêm tiền thưởng ... có lẽ điều đó sẽ giúp ích. – Brad

Trả lời

6

Có một cuộc thảo luận khá dài của COM căn hộ và thông điệp bơm here. Nhưng điểm quan tâm chính là bơm thông điệp được sử dụng để đảm bảo rằng các cuộc gọi trong một STA được sắp xếp chính xác. Vì chuỗi giao diện người dùng là STA được đề cập, thư sẽ cần phải được bơm để đảm bảo rằng mọi thứ hoạt động bình thường.

Tin nhắn WM_DEVICECHANGE thực sự có thể được gửi đến cửa sổ nhiều lần. Vì vậy, trong trường hợp bạn gọi GetDrives trực tiếp, bạn có hiệu quả kết thúc với các cuộc gọi đệ quy. Đặt một điểm ngắt trên cuộc gọi GetDrives và sau đó đính kèm một thiết bị để kích hoạt sự kiện.

Lần đầu tiên bạn nhấn điểm ngắt, mọi thứ đều ổn. Bây giờ nhấn F5 để tiếp tục và bạn sẽ nhấn điểm break lần thứ hai. Lần này các cuộc gọi stack là một cái gì đó như:

[Trong một giấc ngủ, chờ đợi, hoặc tham gia] DeleteMeWindowsForms.exe DeleteMeWindowsForms.Form1.WndProc (ref System.Windows.Forms.Message m) Đường 46 C# ! System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.OnMessage (ref System.Windows.Forms.Message m) + 0x13 byte
System.Windows.Forms.Hệ thống.Windows.Forms.Control.ControlNativeWindow.WndProc (ref System.Windows.Forms.Message m) + 0x31 bytes
System.Windows.Forms.dll! System.Windows.Forms.NativeWindow.DebuggableCallback (System.IntPtr hWnd, int msg, System.IntPtr wParam, System.IntPtr lparam) + 0x64 byte [Native để chuyển Managed]
[Managed để chuyển Native]
mscorlib.dll! System.Threading.WaitHandle.InternalWaitOne (System.Runtime .InteropServices.SafeHandle waitableSafeHandle, millisecondsTimeout dài, bool hasThreadAffinity, bool exitContext) + 0x2b byte mscorlib.dll! System.Threading.WaitHandle.WaitOne (int millisecondsTimeout, bool exitContext) + 0x2d byte
mscorlib.dll! System.Threading. WaitHandle.Wait Một() + 0x10 byte System.Management.dll! System.Management.MTAHelper.CreateInMTA (Loại System.Type) + 0x17b bytes
System.Management.dll! System.Management.ManagementPath.CreateWbemPath (chuỗi đường dẫn) + 0x18 byte System.Management.ManagementClass.ManagementClass (string path) System.Management.dll! + 0x29 byte
DeleteMeWindowsForms.exe! DeleteMeWindowsForms.Form1.GetDrives() Dòng 23 + 0x1B byte C#

Vì vậy, hiệu quả thông báo cửa sổ đang được bơm để đảm bảo các cuộc gọi COM được sắp xếp đúng cách, nhưng điều này có tác dụng phụ khi gọi lại cho WndProc và GetDrives của bạn (vì có các tin nhắn WM_DEVICECHANGE đang chờ xử lý) trong khi vẫn còn trong cuộc gọi GetDrives trước. Khi bạn sử dụng BeginInvoke, bạn loại bỏ cuộc gọi đệ quy này.

Một lần nữa, đặt điểm ngắt trên cuộc gọi GetDrives và nhấn F5 sau lần đầu tiên nhấn. Lần sau, chờ một hoặc hai giây rồi nhấn F5 lần nữa. Đôi khi nó sẽ thất bại, đôi khi nó sẽ không và bạn sẽ nhấn breakpoint của bạn một lần nữa. Lần này, callstack của bạn sẽ bao gồm ba cuộc gọi đến GetDrives, với lần cuối cùng được kích hoạt bởi việc liệt kê bộ sưu tập diskDriveList. Bởi vì một lần nữa, các thông điệp được bơm để đảm bảo các cuộc gọi được sắp xếp.

Thật khó để xác định chính xác lý do tại sao MDA được kích hoạt, nhưng với các cuộc gọi đệ quy nó hợp lý để giả định bối cảnh COM có thể được xé xuống sớm và/hoặc một đối tượng được thu thập trước khi đối tượng COM cơ bản có thể được phát hành.

+0

Tôi dần dần bắt đầu hiểu, vì vậy hãy chịu đựng tôi. Về cơ bản, bạn đang nói rằng các cuộc gọi đến GetDrives() yêu cầu WndProc trên hình thức của mình để được chạy? Tôi không hiểu làm thế nào đây là một vấn đề, đặc biệt là kể từ khi ông cho phép các cơ sở để xử lý nó đầu tiên. GetDrives() sẽ không được gọi lại, bởi vì anh ta đang thử nghiệm loại tin nhắn đầu tiên, phải không? Bạn có thể xây dựng thêm một chút hay chỉ cho tôi đúng hướng không? Xin lỗi vì sự nhầm lẫn của tôi. Cảm ơn! – Brad

+0

@Brad - Không sao cả. Nếu bạn xây dựng một mẫu có sử dụng mã như ông đã ở trên, bạn sẽ thấy một dấu vết ngăn xếp tương tự với một trong câu trả lời của tôi. Bạn có thể thấy GetDrives ở dưới cùng. Cũng nên nhớ rằng tôi đã bắt được dấu vết ngăn xếp đó sau khi điểm ngắt của tôi trên cuộc gọi GetDrives bị trúng. Vì vậy, nó sắp đi vào một cuộc gọi GetDrives khác. – CodeNaked

+1

@Brad - Có nhiều tin nhắn WM_DEVICECHANGE đang được gửi. Vì vậy, lần đầu tiên WndProc được gọi, nó xử lý đầu tiên của các thông báo như vậy. Cuộc gọi GetDrives bơm các thông điệp để sắp xếp bất kỳ cuộc gọi COM nào vào luồng STA (chẳng hạn như các giá trị trả về từ các đối tượng WMI). Vì có nhiều tin nhắn WM_DEVICECHANGE đang chờ xử lý, việc bơm hàng đợi tin nhắn sẽ buộc các thông báo này được đẩy qua ghi đè WndProc. Vì vậy, đệ quy. – CodeNaked

Các vấn đề liên quan