.NET Winforms: Может ли среда выполнения избавиться от ручки формы из-под меня? - PullRequest
14 голосов
/ 09 декабря 2008

Текущее объявление 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 время выполнения может и будет распоряжаться оконными ручками из-под вашего сообщения - вызывая все виды неприятных проблем!

Кто-то вики задал следующий вопрос:

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

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

Ответ: Вы можете использовать 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
}

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


Update One

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

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;

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


Обновление Два

Я не могу себе представить, что ручка окна будет мешать собирать мусор. i.e.:

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

Ответ

Но это важные моменты - первоначальный вопрос все еще таков:

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

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

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

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 не будет препятствовать повторному созданию формой своего основного дескриптора окна, например ::1100

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

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

1 Ответ

3 голосов
/ 09 декабря 2008

Часто, когда вы звоните 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 не закончится с ручкой.

...