.NET Winforms: может ли среда выполнения утилизировать форму из-под меня?
Текущая декларация SendMessage поверх PInvoke.net:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(HandleRef hWnd, uint 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
не закончится с помощью дескриптора.