2010-10-13 17 views
10

Escribo una aplicación en C#, .NET 3.0 en VS2005 con una función de monitorización de inserción/expulsión de varias unidades extraíbles (discos flash USB, CD-ROM, etc.). No quería usar WMI, ya que a veces puede ser ambiguo (por ejemplo, puede generar múltiples eventos de inserción para una sola unidad USB), así que simplemente anulo el WndProc de mi mainform para capturar el mensaje WM_DEVICECHANGE, como se propone here. Ayer me encontré con un problema cuando resultó que tendré que usar WMI de todos modos para recuperar algunos detalles oscuros del disco como un número de serie. Resulta que llamar a rutinas WMI desde dentro del WndProc arroja el DisconnectedContext MDA.DisconnectedContext MDA al llamar a funciones WMI en la aplicación de un único subproceso

Después de excavar, terminé con una solución incómoda para eso. El código es como sigue:

// 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(); 

que básicamente significa ejecutar el procedimiento relacionados con WMI en un hilo separado - pero entonces, a la espera de que se complete.

Ahora, la pregunta es: ¿por qué lo hace funcionar, y por eso qué tiene que ser así? (o, ¿lo hace?)

No entiendo el hecho de obtener DisconnectedContext MDA o RPC_E_WRONG_THREAD en primer lugar. ¿Cómo funciona ejecutar el procedimiento GetDrives() desde un controlador de evento de clic de botón difiere de invocarlo desde un WndProc? ¿No ocurren en el mismo hilo principal de mi aplicación? Por cierto, mi aplicación es completamente de un solo hilo, entonces ¿por qué, de repente, un error que se refiere a un "hilo equivocado"? ¿El uso de WMI implica multihilo y un tratamiento especial de las funciones de System.Management?

Mientras tanto encontré otra pregunta relacionada con esa MDA, es here. OK, puedo asumir que llamar a WMI significa crear un hilo separado para el componente COM subyacente, pero todavía no se me ocurre por qué no se necesita magia al llamarlo después de presionar un botón y se necesita hacer magia cuando se llama desde el WndProc.

Estoy realmente confundido acerca de eso y agradecería alguna aclaración al respecto. Sólo hay unas pocas cosas peores que tener una solución y no saber por qué funciona:/

Saludos, Aleksander

+1

¡El mismo problema aquí! Ojalá hubiera una solución. Agregaré una recompensa ... tal vez eso ayude. – Brad

Respuesta

6

Hay una larga discusión en lugar de COM apartamentos y el mensaje de bombeo here. Pero el principal punto de interés es que la bomba de mensajes se usa para garantizar que las llamadas en una STA estén correctamente calculadas. Como el hilo de la interfaz de usuario es la STA en cuestión, habría que bombear mensajes para asegurarse de que todo funciona correctamente.

El mensaje WM_DEVICECHANGE en realidad se puede enviar a la ventana varias veces. Entonces, en el caso en que llame directamente a GetDrives, efectivamente terminará con llamadas recursivas. Ponga un punto de quiebre en la llamada GetDrives y luego conecte un dispositivo para activar el evento.

La primera vez que alcanzas el punto de quiebre, todo está bien. Ahora presione F5 para continuar y llegará al punto de quiebre por segunda vez. Esta vez, la pila de llamadas es algo así como:

[En un sueño, espera, o se unen a] DeleteMeWindowsForms.exe DeleteMeWindowsForms.Form1.WndProc (ref System.Windows.Forms.Message m) Línea 46 C# ! System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.OnMessage (ref System.Windows.Forms.Message m) + 0x13 bytes
System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.WndProc (ref System.Windows.Forms.Message m) + 0x31 bytes
System.Windows.Forms.dll! System.Windows.Forms.NativeWindow.DebuggableCallback (System.IntPtr CVent, int msg, System.IntPtr wparam, System.IntPtr lparam) + 0x64 bytes [Originario de transición controlada]
[arregló para Transición nativo]
mscorlib.dll! System.Threading.WaitHandle.InternalWaitOne (System.Runtime .InteropServices.SafeHandle waitableSafeHandle, a largo millisecondsTimeout, hasThreadAffinity bool, bool exitContext) + 0x2B bytes mscorlib.dll! System.Threading.WaitHandle.WaitOne (int millisecondsTimeout, bool exitContext) + 0x2D bytes
mscorlib.dll! System.Threading. WaitHandle.Wait Uno() + 0x10 bytes System.Management.dll! System.Management.MTAHelper.CreateInMTA (System.Type type) + 0x17b bytes
System.Management.dll! System.Management.ManagementPath.CreateWbemPath (ruta de cadena) + 0x18 bytes System.Management.dll! System.Management.ManagementClass.ManagementClass (string path) + 0x29 bytes
DeleteMeWindowsForms.exe! DeleteMeWindowsForms.Form1.GetDrives() Línea 23 + 0x1b bytes C#

Así que efectivamente el los mensajes de la ventana se están bombeando para garantizar que las llamadas COM se organicen correctamente, pero esto tiene el efecto secundario de llamar a su WndProc y GetDrives nuevamente (ya que hay mensajes WM_DEVICECHANGE pendientes) mientras aún en una llamada anterior de GetDrives. Cuando usa BeginInvoke, elimina esta llamada recursiva.

De nuevo, ponga un punto de interrupción en la llamada a GetDrives y presione F5 después de la primera vez que se golpea. La próxima vez, espere uno o dos segundos y luego presione F5 nuevamente. Algunas veces fallará, a veces no lo hará y alcanzarás tu punto de quiebre nuevamente. Esta vez, su pila de llamadas incluirá tres llamadas a GetDrives, la última activada por la enumeración de la colección diskDriveList. Porque una vez más, los mensajes se bombean para garantizar que las llamadas se distribuyan.

Es difícil precisar exactamente por qué se dispara el MDA, pero teniendo en cuenta las llamadas recursivas es razonable suponer que el contexto COM puede ser derribado prematuramente y/o un objeto se recoge antes de que el objeto COM subyacente pueda ser liberado.

+0

Estoy empezando a comprender lentamente, así que tengan paciencia conmigo. Básicamente, ¿estás diciendo que la llamada a GetDrives() requiere que WndProc se ejecute en su formulario? No entiendo cómo esto es un problema, especialmente porque permite que la base lo maneje primero. GetDrives() no se volverá a llamar porque primero está probando el tipo de mensaje, ¿sí? ¿Puedes elaborar un poco más o señalarme en la dirección correcta? Perdón por mi confusión. ¡Gracias! – Brad

+0

@Brad - No hay problema. Si construyes una muestra que usa código como el que se muestra arriba, verás una traza de pila similar a la de mi respuesta. Puedes ver que GetDrives está en la parte inferior. También recuerde que capturé ese rastro de pila después de que mi punto de interrupción en la llamada de GetDrives fue golpeado. Por lo tanto, se trata de entrar en otra llamada de GetDrives. – CodeNaked

+1

@Brad: se envían varios mensajes WM_DEVICECHANGE. Entonces, la primera vez que se llama a WndProc, maneja el primero de esos mensajes. La llamada GetDrives bombea los mensajes para ordenar cualquier llamada COM en el hilo STA (como los valores de retorno de los objetos WMI). Dado que hay más mensajes WM_DEVICECHANGE esperando procesarse, el bombeo de la cola de mensajes forzará a estos a pasar por la anulación de WndProc. Por lo tanto, la recursión. – CodeNaked

Cuestiones relacionadas