c# - tabulacion - Formas de pago de.NET: ¿puede el tiempo de ejecución disponer de un formulario fuera de mí?
orden de tabulacion visual basic (1)
A menudo, cuando llamas a SendMessage
, lo haces desde otro hilo o al menos otro componente separado de tu Formulario. Supongo que el punto es que solo porque tiene un IntPtr que en un momento dado contenía un identificador de ventana válido, no puede suponer que aún es válido.
Digamos que tienes esta clase:
class MyClass {
IntPtr hwnd;
public MyClass(IntPtr hwnd) {
this.hwnd = hwnd;
}
...
private void DoStuff()
{
//n.b. we don''t necessarily know if the handle is still valid
DoSomethingWithTheHandle(hwnd);
}
}
y en otro lugar:
private void DoOtherStuff() {
Form f = new Form();
mc = new MyClass(f.Handle);
}
entonces, debido a que f
ha salido del alcance, su Dispose
eventualmente será llamado por el finalizador de GC. Es por eso que tal vez necesite usar Gc.KeepAlive
en este tipo de situación. f
debe permanecer con vida hasta que mc
termine con el mango.
La declaración actual de SendMessage en PInvoke.net es:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(HandleRef hWnd, uint Msg,
IntPtr wParam, IntPtr lParam);
Nota: El hWnd ya no es un IntPtr y ha sido reemplazado por HandleRef . Se da una explicación muy amplia para el cambio:
Puede reemplazar "hWnd" por "IntPtr" en lugar de "HandleRef". Sin embargo, corres el riesgo de hacer esto: puede hacer que tu código falle con las condiciones de carrera. El tiempo de ejecución de .NET puede, y lo hará, eliminar los identificadores de las ventanas de debajo de su mensaje, ¡causando todo tipo de problemas desagradables!
Alguien ha hecho una pregunta de seguimiento:
Pregunta: ¿No se puede abordar este último problema con la recopilación de referencias, específicamente con la fijación?
Y alguien respondió:
Respuesta: Puede usar GC.KeepAlive () inmediatamente después de SendMessage () con el objeto Form como parámetro para KeepAlive ().
Todo esto "disponer tu forma debajo de ti" me parece extraño. SendMessage es una llamada síncrona . No volverá hasta que el mensaje enviado haya sido procesado.
La implicación es que un identificador de formulario se puede destruir en cualquier momento. Por ejemplo:
private void DoStuff()
{
//get the handle
IntPtr myHwnd = this.Handle;
//Is the handle still valid to use?
DoSomethingWithTheHandle(myHwnd); //handle might not be valid???
//fall off the function
}
Esto significa que el identificador de ventana puede dejar de ser válido entre el momento en que lo uso y el momento en que finaliza el método.
Actualizar uno
Entiendo la noción de que una vez que un formulario sale del alcance, su identificador no es válido. p.ej:
private IntPtr theHandle = IntPtr.Zero;
private void DoStuff()
{
MyForm frm = new MyForm())
theHandle = frm.Handle;
//Note i didn''t dispose of the form.
//But since it will be unreferenced once this method ends
//it will get garbage collected,
//making the handle invalid
}
Es obvio para mí que el identificador del formulario no es válido una vez que DoStuff ha regresado. Lo mismo sería cierto sin importar la técnica: si el formulario no se mantiene en algún ámbito, no es válido.
No estoy de acuerdo con (todo tipo de enlace) en que un formulario se mantendrá hasta que se hayan recibido todos los mensajes de envío. El CLR no sabe a quién se le ha dado el identificador de ventana de mi formulario, y no tiene forma de saber quién puede llamar a SendMessage () en el futuro.
En otras palabras, no me puedo imaginar esa llamada:
IntPtr hWnd = this.Handle;
ahora evitará que esto sea basura.
Actualización dos
No me puedo imaginar que tener un asa de ventana evitará que se forme un formulario. es decir:
Clipboard.AsText = this.Handle.ToString();
IntPtr theHandle = (IntPtr)(int)Clipboard.AsText;
Responder
Pero estos son puntos importantes, la pregunta original sigue siendo:
¿Puede el tiempo de ejecución disponer de un formulario fuera de mí?
La respuesta, resulta, es no. El tiempo de ejecución no eliminará un formulario de debajo de mí. Dispondrá de un formulario sin referencia, pero las formas sin referencia no están debajo de mí. "Under Me" significa que tengo una referencia al formulario.
Por otro lado, el identificador subyacente de Windows de un objeto Form puede ser destruido por debajo de mí (y realmente, ¿cómo podría no ser así? Los manejadores de ventanas no tienen referencias contadas, ni deberían serlo):
IntPtr hwnd = this.Handle;
this.RightToLeft = RightToLeft.Yes;
//hwnd is now invalid
También es importante tener en cuenta que HandleRef no ayudará a evitar los problemas causados por la creación de envoltorios de objetos alrededor de los identificadores de ventanas de Windows:
Motivo 1 Si se destruye un objeto de formulario porque no tenía una referencia, entonces es simplemente estúpido intentar hablar con un formulario que, por derecho, ya no debería existir. El hecho de que el GC no haya llegado a su fin todavía no te hace inteligente, te hace afortunado. Un HandleRef es un truco para mantener una referencia al formulario. En lugar de usar:
HandleRef hr = new HandleRef(this, this.Handle);
DoSomethingWithHandle(this.Handle);
puedes usarlo fácilmente:
Object o = this;
DoSomethingWithHandle(this.Handle);
Reason 2 HandleRef no evitará que un formulario vuelva a crear su identificador de ventana subyacente, por ejemplo:
HandleRef hr = new HandleRef(this, this.Handle);
this.RightToLeft = RightToLeft.Yes;
//hr.Hande is now invalid
Entonces, si bien el modificador original de SendMessage en P / Invoke señaló un problema, su solución no es una solución.