.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 не закончится с помощью дескриптора.