.NET Winforms: может ли среда выполнения утилизировать форму из-под меня?

Текущая декларация SendMessage поверх PInvoke.net:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(HandleRef hWnd, **** Msg, 
 IntPtr wParam, IntPtr lParam);

Примечание. hWnd больше не является IntPtr и заменен на HandleRef. Дается простое объяснение изменения:

Вы можете заменить "hWnd" на "IntPtr" вместо "HandleRef". Однако вы рискуют при этом - это может привести к сбою вашего кода с расой условия. Время выполнения .NET может и будет удалять ваши окна. из-под вашего сообщения - вызывая все виды неприятных проблем!

Кто-то wiki'd ответил на вопрос:

Вопрос: Нельзя решить эту последнюю проблему с маршалингом, специально закрепляющим?

И кто-то ответил:

Ответ: Вы можете использовать GC.KeepAlive() сразу после SendMessage() с Объект формы как параметр KeepAlive().

Все это "избавление от вашей формы под вами" кажется странным для меня. SendMessage - синхронный вызов. Он не будет возвращаться до тех пор, пока не будет обработано отправленное сообщение.

Затем подразумевается, что дескриптор формы может быть уничтожен в любое время. Например:

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
}

Это означает, что дескриптор окна может стать недействительным между временем, которое я использую, и временем окончания метода?

Обновить один

Я понимаю, что после того, как форма выходит за пределы области видимости, дескриптор недействителен. например:.

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
}

Мне очевидно, что дескриптор формы недействителен после возврата DoStuff. То же самое было бы верно независимо от того, какой метод - если форма не удерживается в какой-либо области, она недействительна для использования.

Я бы не согласился с (todo link guy) тем, что форма будет храниться до тех пор, пока все сообщения отправки не будут получены. CLR не знает, кому, возможно, был предоставлен мой дескриптор окна формы, и он не знает, кто может назвать SendMessage() в будущем.

Другими словами, я не могу себе представить, что вызов:

IntPtr hWnd = this.Handle;

теперь предотвратит сбор мусора.

Обновить два

Я не могу себе представить, что при помощи дескриптора окна будет сохранена форма сбора мусора. то есть:.

Clipboard.AsText = this.Handle.ToString();
IntPtr theHandle = (IntPtr)(int)Clipboard.AsText;

Ответ

Но это альтернативные моменты - исходный вопрос по-прежнему:

Может ли среда выполнения использовать форму справиться из-под меня?

Ответ, оказывается, нет. Среда выполнения не избавит вас от формы из-под меня. Он будет распоряжаться неопубликованной формой, но формы без ссылок не под меня. "Under Me" означает, что у меня есть ссылка на форму.

С другой стороны, объект Form, лежащий в основе дескриптора окна Windows, может быть уничтожен из-под меня (и, действительно, как он мог это сделать), обработчики окон не засчитываются, и они не должны быть):

IntPtr hwnd = this.Handle;
this.RightToLeft = RightToLeft.Yes;
//hwnd is now invalid

Также важно отметить, что HandleRef не поможет предотвратить проблемы, вызванные созданием оберток объектов вокруг оконных окон Windows:

Причина 1 Если объект формы уничтожается, потому что у вас нет ссылки на него, тогда вы просто глупы, чтобы попытаться поговорить с формой, которая по правам больше не будет существовать. Просто потому, что GC не добрался до него, но не делает вас умным - вам повезло. HandleRef - это взломать ссылку на форму. Вместо использования:

HandleRef hr = new HandleRef(this, this.Handle);
DoSomethingWithHandle(this.Handle);

вы можете легко использовать:

Object o = this;
DoSomethingWithHandle(this.Handle);

Причина 2 HandleRef не будет препятствовать повторному созданию формы под дескриптором окна, например:

HandleRef hr = new HandleRef(this, this.Handle);
this.RightToLeft = RightToLeft.Yes;
//hr.Hande is now invalid

Итак, в то время как оригинальный модификатор SendMessage on P/Invoke указал на проблему, его решение не является решением.

1 ответ

Часто, когда вы вызываете SendMessage, вы делаете это из другого потока или, по крайней мере, другого компонента отдельно от вашей Формы. Я предполагаю, что дело в том, что только потому, что у вас есть IntPtr, который в какой-то момент содержал допустимый дескриптор окна, вы не можете предположить, что он по-прежнему действителен.

Скажем, у вас был этот класс:

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

и где-то еще:

private void DoOtherStuff() {
 Form f = new Form();
 mc = new MyClass(f.Handle);
 }

потому что f вышел из области видимости, его Dispose в конечном итоге вызывается финализатором GC. Поэтому вам может понадобиться использовать Gc.KeepAlive в этом типе ситуации. f должен оставаться в живых до тех пор, пока mc не закончится с помощью дескриптора.

licensed under cc by-sa 3.0 with attribution.