温故知新,遇见BeginInvoke/Invoke,适用于MFC/WinFroms的线程和进程间消息通信,从源码视角一探究竟

由消息驱动的Windows程序

Windows是一个消息驱动式系统,Windows消息提供应用程序与应用程序之间,应用程序与Windows系统之间进行通信的手段。

image

  1. 操作系统首先捕获到来自键盘或鼠标等输入系统的消息,并将获取到的消息存放到消息队列中。
  2. 应用程序一直通过GetMessage()从消息队列中获取消息。
  3. 应用程序再将获取到的消息通过DispatchMessage()分派到操作系统
  4. 操作系统再执行“窗口过程

image

使用Win32 API来创建的程序成为Win32程序。提供Win32 API的dll被加载到应用程序的进程中,应用程序通过这些API来创建线程、窗口和控件。Win32程序中,所有窗口和控件都是一个窗口类的实例,都拥有一个窗口句柄,窗口对象属于内核对象,由Windows子系统来维护。Windows子系统为标准控件定义了窗口类,并使用GDI来绘制这些标准控件。

Windows消息类型

  • 窗口消息,大概是系统中最为常见的消息,它是指由操作系统和控制其他窗口的窗口所使用的消息。例如CreateWindow、DestroyWindow和MoveWindow等都会激发窗口消息,还有我们在上面谈到的单击鼠标所产生的消息也是一种窗口消息。
  • 命令消息,这是一种特殊的窗口消息,他用来处理从一个窗口发送到另一个窗口的用户请求,例如按下一个按钮,他就会向主窗口发送一个命令消息。
  • 控件通知消息,是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows公共控件如树状视图、列表视图等。例如,单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息。 她类似于命令消息,当用户与控件窗口交互时,那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消息的存在并不是为了处理用户命令,而是为了让主窗口能够改变控件,例如加载、显示数据。例如按下一个按钮,他向父窗口发送的消息也可以看作是一个控件通知消息;单击鼠标所产生的消息可以由主窗口直接处理,然后交给控件窗口处理。

Windows消息发送途径

从消息的发送途径来看,消息可以分成2种:队列消息非队列消息。消息队列由可以分成系统消息队列线程消息队列。系统消息队列由Windows维护,线程消息队列则由每个GUI线程自己进行维护,为避免给non-GUI现成创建消息队列,所有线程产生时并没有消息队列,仅当线程第一次调用GDI函数数系统给线程创建一个消息队列。队列消息送到系统消息队列,然后到线程消息队列;非队列消息直接送给目的窗口过程。

对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,还有一些其它的消息,例如:WM_PAINT、WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的相应队列,下面的事情就该由线程消息队列操心了,Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。

Win32程序采用消息循环机制:

image

Windows消息循环的启动

image

在启动窗体的方法Application.Run中执行了消息循环(While(GetMessage()))的逻辑。

public static void Main(string[] args)
{
   Form f = new Form();
   Application.Run(f);
}

Windows消息发送实现

Windows消息机制是Windows平台上的线程或者进程间通信机制之一。Windows消息值其实就是定义的一个数据结构,最重要的是消息的类型,它就是一个整数;然后就是消息的参数。消息的参数可以表示很多东西。

image

Windows提供了一些api用来向一个线程的消息队列发送消息。因此,一个线程可以向另一个线程的消息队列发送消息从而告诉对方做什么,这样就完成了线程间的通信。有些api发送消息需要一个窗口句柄,这种函数可以把消息发送到指定窗口的主线程消息队列;而有些则可以直接通过线程句柄,把消息发送到该线程消息队列中。

  • SendMessage是windows api,用来把一个消息发送到一个窗口的消息队列。这个方法是个阻塞方法,也就是操作系统会确保消息的确发送到目的消息队列,并且该消息被处理完毕以后,该函数才返回。返回之前,调用者将会被暂时阻塞
  • PostMessage也是一个用来发送消息到窗口消息队列的api函数,但这个方法是非阻塞的。也就是它会马上返回,而不管消息是否真的发送到目的地,也就是调用者不会被阻塞。

Control类实现Invoke和BeginInvoke

如果从另外一个线程操作windows窗体上的控件,就会和主线程产生竞争,造成不可预料的结果,甚至死锁。因此Windows GUI编程有一个规则,就是只能通过创建控件的线程来操作控件的数据,否则就可能产生不可预料的结果。

因此,DotNet里面,为了方便地解决这些问题,Control类实现了ISynchronizeInvoke接口,提供了InvokeBeginInvoke方法来提供让其它线程更新GUI界面控件的机制。

image

Invoke或者BeginInvoke方法都需要一个委托对象作为参数。委托类似于回调函数的地址,因此调用者通过这两个方法就可以把需要调用的函数地址封送给界面线程。这些方法里面如果包含了更改控件状态的代码,那么由于最终执行这个方法的是界面线程,从而避免了竞争条件,避免了不可预料的问题。如果其它线程直接操作界面线程所属的控件,那么将会产生竞争条件,造成不可预料的结果。

使用Invoke完成一个委托方法的封送,就类似于使用SendMessage方法来给界面线程发送消息,是一个同步方法。也就是说在Invoke封送的方法被执行完毕前,Invoke方法不会返回,从而调用者线程将被阻塞。

使用BeginInvoke方法封送一个委托方法,类似于使用PostMessage进行通信,这是一个异步方法。也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。但是调用者也可以使用EndInvoke方法或者其它类似WaitHandle机制等待异步操作的完成。

但是在内部实现上,InvokeBeginInvoke都是用了PostMessage方法,从而避免了SendMessage带来的问题。而Invoke方法的同步阻塞是靠WaitHandle机制来完成的。

如果你的后台线程在更新一个UI控件的状态后不需要等待,而是要继续往下处理,那么你就应该使用BeginInvoke来进行异步处理。

如果你的后台线程需要操作UI控件,并且需要等到该操作执行完毕才能继续执行,那么你就应该使用Invoke。否则,在后台线程和主截面线程共享某些状态数据的情况下,如果不同步调用,而是各自继续执行的话,可能会造成执行序列上的问题,虽然不发生死锁,但是会出现不可预料的显示结果或者数据处理错误。

可以看到ISynchronizeInvoke有一个属性,InvokeRequired。这个属性就是用来在编程的时候确定,一个对象访问UI控件的时候是否需要使用Invoke或者BeginInvoke来进行封送。如果不需要那么就可以直接更新。在调用者对象和UI对象同属一个线程的时候这个属性返回false。在后面的代码分析中我们可以看到,Control类对这一属性的实现就是在判断调用者和控件是否属于同一个线程的。

Delegate.BeginInvoke

通过一个委托来进行同步方法的异步调用(有点绕嘴),也是.Net提供的异步调用机制之一。但是Delegate.BeginInvoke方法是从ThreadPool取出一个线程来执行这个方法,以获得异步执行效果的。也就是说,如果采用这种方式提交多个异步委托,那么这些调用的顺序无法得到保证。而且由于是使用线程池里面的线程来完成任务,使用频繁,会对系统的性能造成影响。

Delegate.BeginInvoke同样也是将一个委托方法封送到其它线程,从而通过异步机制执行一个方法。调用者线程则可以在完成封送以后去继续它的工作。但是这个方法封送到的最终执行线程是运行库从ThreadPool里面选取的一个线程。

这里需要纠正一个误区,那就是Control类上的异步调用BeginInvoke并没有开辟新的线程完成委托任务,而是让界面控件的所属线程完成委托任务的。看来异步操作就是开辟新线程的说法不一定准确。

从源码窥探跨UI线程调用的实现原理

https://referencesource.microsoft.com/#System/compmod/system/componentmodel/ISynchronizeInvoke.cs

ISynchronizeInvoke接口定义

//------------------------------------------------------------------------------
// <copyright file="ISynchronizeInvoke.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.ComponentModel {
    using System; 
    using System.Security.Permissions;
         
    /// <devdoc>
    ///    <para>Provides a way to synchronously or asynchronously execute a delegate.</para>
    /// </devdoc>
    public interface ISynchronizeInvoke {
    
        /// <devdoc>
        /// <para>Gets a value indicating whether the caller must call <see cref='System.ComponentModel.ISynchronizeInvoke.Invoke'/> when calling an object that implements 
        ///    this interface.</para>
        /// </devdoc>
        bool InvokeRequired{get;}
                
        /// <devdoc>
        ///    <para> 
        ///       Executes the given delegate on the main thread that this object executes on.</para>
        /// </devdoc>
        [HostProtection(Synchronization=true, ExternalThreading=true)]
        IAsyncResult BeginInvoke(Delegate method, object[] args);            
        
        /// <devdoc>
        ///    <para>Waits until the process you started by 
        ///       calling <see cref='System.ComponentModel.ISynchronizeInvoke.BeginInvoke'/> completes, and then returns
        ///       the value generated by the process.</para>
        /// </devdoc>
        object EndInvoke(IAsyncResult result);                      
        
        /// <devdoc>
        ///    <para> 
        ///       Executes the given delegate on the main thread that this object
        ///       executes on.</para>
        /// </devdoc>
        object Invoke(Delegate method, object[] args);        
    }
}

System.Windows.Forms.Control类集成了ISynchronizeInvoke接口,并且实现了InvokeBeginInvoke方法来提供让其它线程更新GUI界面控件的机制。

https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs

namespace System.Windows.Forms
{
    public partial class Control :
    Component,
    UnsafeNativeMethods.IOleControl,
    UnsafeNativeMethods.IOleObject,
    UnsafeNativeMethods.IOleInPlaceObject,
    UnsafeNativeMethods.IOleInPlaceActiveObject,
    UnsafeNativeMethods.IOleWindow,
    UnsafeNativeMethods.IViewObject,
    UnsafeNativeMethods.IViewObject2,
    UnsafeNativeMethods.IPersist,
    UnsafeNativeMethods.IPersistStreamInit,
    UnsafeNativeMethods.IPersistPropertyBag,
    UnsafeNativeMethods.IPersistStorage,
    UnsafeNativeMethods.IQuickActivate,
    ISupportOleDropSource,
    IDropTarget,
    ISynchronizeInvoke,
    IWin32Window,
    IArrangedElement,
    IBindableComponent,
    IKeyboardToolTip {
        
    }
}

BeginInvoke的实现

System.Windows.Forms.Control类中ISynchronizeInvoke接口的BeginInvoke的实现。

/// <include file='doc\Control.uex' path='docs/doc[@for="Control.BeginInvoke"]/*' />
/// <devdoc>
///     Executes the given delegate on the thread that owns this Control's
///     underlying window handle.  The delegate is called asynchronously and this
///     method returns immediately.  You may call this from any thread, even the
///     thread that owns the control's handle.  If the control's handle doesn't
///     exist yet, this will follow up the control's parent chain until it finds a
///     control or form that does have a window handle.  If no appropriate handle
///     can be found, BeginInvoke will throw an exception.  Exceptions within the
///     delegate method are considered untrapped and will be sent to the
///     application's untrapped exception handler.
///
///     There are five functions on a control that are safe to call from any
///     thread:  GetInvokeRequired, Invoke, BeginInvoke, EndInvoke and CreateGraphics.
///     For all other method calls, you should use one of the invoke methods to marshal
///     the call to the control's thread.
/// </devdoc>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public IAsyncResult BeginInvoke(Delegate method) {
    return BeginInvoke(method, null);
}

/// <include file='doc\Control.uex' path='docs/doc[@for="Control.BeginInvoke1"]/*' />
/// <devdoc>
///     Executes the given delegate on the thread that owns this Control's
///     underlying window handle.  The delegate is called asynchronously and this
///     method returns immediately.  You may call this from any thread, even the
///     thread that owns the control's handle.  If the control's handle doesn't
///     exist yet, this will follow up the control's parent chain until it finds a
///     control or form that does have a window handle.  If no appropriate handle
///     can be found, BeginInvoke will throw an exception.  Exceptions within the
///     delegate method are considered untrapped and will be sent to the
///     application's untrapped exception handler.
///
///     There are five functions on a control that are safe to call from any
///     thread:  GetInvokeRequired, Invoke, BeginInvoke, EndInvoke and CreateGraphics.
///     For all other method calls, you should use one of the invoke methods to marshal
///     the call to the control's thread.
/// </devdoc>
[EditorBrowsable(EditorBrowsableState.Advanced)]        
[SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
public IAsyncResult BeginInvoke(Delegate method, params Object[] args) {
    using (new MultithreadSafeCallScope()) {
        Control marshaler = FindMarshalingControl();
        return(IAsyncResult)marshaler.MarshaledInvoke(this, method, args, false);
    }
}

Invoke的实现

System.Windows.Forms.Control类中ISynchronizeInvoke接口的Invoke的实现。

/// <include file='doc\Control.uex' path='docs/doc[@for="Control.Invoke"]/*' />
/// <devdoc>
///     Executes the given delegate on the thread that owns this Control's
///     underlying window handle.  It is an error to call this on the same thread that
///     the control belongs to.  If the control's handle doesn't exist yet, this will
///     follow up the control's parent chain until it finds a control or form that does
///     have a window handle.  If no appropriate handle can be found, invoke will throw
///     an exception.  Exceptions that are raised during the call will be
///     propapgated back to the caller.
///
///     There are five functions on a control that are safe to call from any
///     thread:  GetInvokeRequired, Invoke, BeginInvoke, EndInvoke and CreateGraphics.
///     For all other method calls, you should use one of the invoke methods to marshal
///     the call to the control's thread.
/// </devdoc>
public Object Invoke(Delegate method) {
    return Invoke(method, null);
}

/// <include file='doc\Control.uex' path='docs/doc[@for="Control.Invoke1"]/*' />
/// <devdoc>
///     Executes the given delegate on the thread that owns this Control's
///     underlying window handle.  It is an error to call this on the same thread that
///     the control belongs to.  If the control's handle doesn't exist yet, this will
///     follow up the control's parent chain until it finds a control or form that does
///     have a window handle.  If no appropriate handle can be found, invoke will throw
///     an exception.  Exceptions that are raised during the call will be
///     propapgated back to the caller.
///
///     There are five functions on a control that are safe to call from any
///     thread:  GetInvokeRequired, Invoke, BeginInvoke, EndInvoke and CreateGraphics.
///     For all other method calls, you should use one of the invoke methods to marshal
///     the call to the control's thread.
/// </devdoc>
public Object Invoke(Delegate method, params Object[] args) {
    using (new MultithreadSafeCallScope()) {
        Control marshaler = FindMarshalingControl();
        return marshaler.MarshaledInvoke(this, method, args, true);
    }
}

这里的FindMarshalingControl方法通过一个循环向上回溯,从当前控件开始回溯父控件,直到找到最顶级的父控件,用它作为封送对象。例如,我们调用窗体上一个进度条的Invoke方法封送委托,但是实际上会回溯到主窗体,通过这个控件对象来封送委托。因为主窗体是主线程消息队列相关的,发送给主窗体的消息才能发送到界面主线程消息队列。

我们可以看到InvokeBeginInvoke方法使用了同样的实现,只是MarshaledInvoke方法的最后一个参数值不一样。

MarshaledInvoke的实现

System.Windows.Forms.Control类中InvokeBeginInvoke调用的MarshaledInvoke的实现

private Object MarshaledInvoke(Control caller, Delegate method, Object[] args, bool synchronous) {
 
    // Marshaling an invoke occurs in three steps:
    //
    // 1.  Create a ThreadMethodEntry that contains the packet of information
    //     about this invoke.  This TME is placed on a linked list of entries because
    //     we have a gap between the time we PostMessage and the time it actually
    //     gets processed, and this gap may allow other invokes to come in.  Access
    //     to this linked list is always synchronized.
    //
    // 2.  Post ourselves a message.  Our caller has already determined the
    //     best control to call us on, and we should almost always have a handle.
    //
    // 3.  If we're synchronous, wait for the message to get processed.  We don't do
    //     a SendMessage here so we're compatible with OLE, which will abort many
    //     types of calls if we're within a SendMessage.
    //

    if (!IsHandleCreated) {
        throw new InvalidOperationException(SR.GetString(SR.ErrorNoMarshalingThread));
    }

    // We have to demand unmanaged code permission here for the control hosted in
    // the browser case. Without this check, we will expose a security hole, because
    // ActiveXImpl.OnMessage() will assert unmanaged code for everyone as part of
    // its implementation.
    // The right fix is to remove the Assert() on top of the ActiveXImpl class, and
    // visit each method to see if it needs unmanaged code permission, and if so, add
    // the permission just to that method(s).
    //
    ActiveXImpl activeXImpl = (ActiveXImpl)Properties.GetObject(PropActiveXImpl);
    if (activeXImpl != null) {
        IntSecurity.UnmanagedCode.Demand();
    }

    // We don't want to wait if we're on the same thread, or else we'll deadlock.
    // It is important that syncSameThread always be false for asynchronous calls.
    //
    bool syncSameThread = false;
    int pid; // ignored
    if (SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, Handle), out pid) == SafeNativeMethods.GetCurrentThreadId()) {
        if (synchronous)
            syncSameThread = true;
    }

    // Store the compressed stack information from the thread that is calling the Invoke()
    // so we can assign the same security context to the thread that will actually execute
    // the delegate being passed.
    //
    ExecutionContext executionContext = null;
    if (!syncSameThread) {
        executionContext = ExecutionContext.Capture();
    }
    ThreadMethodEntry tme = new ThreadMethodEntry(caller, this, method, args, synchronous, executionContext);

    lock (this) {
        if (threadCallbackList == null) {
            threadCallbackList = new Queue();
        }
    }

    lock (threadCallbackList) {
        if (threadCallbackMessage == 0) {
            threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
        }
        threadCallbackList.Enqueue(tme);
    }

    if (syncSameThread) {
        InvokeMarshaledCallbacks();
    }  else {
        // 

        UnsafeNativeMethods.PostMessage(new HandleRef(this, Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
    }

    if (synchronous) {
        if (!tme.IsCompleted) {
            WaitForWaitHandle(tme.AsyncWaitHandle);
        }
        if (tme.exception != null) {
            throw tme.exception;
        }
        return tme.retVal;
    }
    else {
        return(IAsyncResult)tme;
    }
}

看到PostMessage了吧?通过Windows消息机制实现了封送。而需要封送的委托方法作为消息的参数进行了传递。

PostMessage的实现

UnsafeNativeMethods方法中关于PostMessage的调用实现

https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/UnsafeNativeMethods.cs

namespace System.Windows.Forms {
 
    using Accessibility;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;
    using System.Runtime.ConstrainedExecution;
    using System;
    using System.Security.Permissions;
    using System.Collections;
    using System.IO;
    using System.Text;
    using System.Security;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Drawing;
 
    using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
 
    [
    SuppressUnmanagedCodeSecurity()
    ]
    internal static class UnsafeNativeMethods {

        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool PostMessage(HandleRef hwnd, int msg, IntPtr wparam, IntPtr lparam);
    }
}

SendMessage的实现

namespace System.Windows.Forms {
 
    using Accessibility;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;
    using System.Runtime.ConstrainedExecution;
    using System;
    using System.Security.Permissions;
    using System.Collections;
    using System.IO;
    using System.Text;
    using System.Security;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Drawing;
 
    using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
 
    [
    SuppressUnmanagedCodeSecurity()
    ]
    internal static class UnsafeNativeMethods {

        [DllImport(ExternDll.User32, CharSet = CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, bool wParam, int lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, int[] lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int[] wParam, int[] lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, ref int wParam, ref int lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, string lParam);
 
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, IntPtr wParam, string lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, StringBuilder lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.TOOLINFO_T lParam);        
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.TOOLINFO_TOOLTIP lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, ref NativeMethods.TBBUTTON lParam);        
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, ref NativeMethods.TBBUTTONINFO lParam);        
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, ref NativeMethods.TV_ITEM lParam);        
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, ref NativeMethods.TV_INSERTSTRUCT lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.TV_HITTESTINFO lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.LVBKIMAGE lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern int SendMessage(HandleRef hWnd, int msg, int wParam, ref NativeMethods.LVHITTESTINFO lParam);
        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.TCITEM_T lParam);
 
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, ref NativeMethods.HDLAYOUT hdlayout);
 
        //for Tooltips
        //
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, HandleRef wParam, int lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, HandleRef lParam);        
 
 
        // For RichTextBox
        //
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] NativeMethods.PARAFORMAT lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] NativeMethods.CHARFORMATA lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] NativeMethods.CHARFORMAT2A lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, [In, Out, MarshalAs(UnmanagedType.LPStruct)] NativeMethods.CHARFORMATW lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern int SendMessage(HandleRef hWnd, int msg, int wParam, [Out, MarshalAs(UnmanagedType.IUnknown)]out object editOle);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.CHARRANGE lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.FINDTEXT lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.TEXTRANGE lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.POINT lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, NativeMethods.POINT wParam, int lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.REPASTESPECIAL lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.EDITSTREAM lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.EDITSTREAM64 lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, NativeMethods.GETTEXTLENGTHEX wParam, int lParam);
 
        // For Button
        //
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, [In, Out] NativeMethods.SIZE lParam);        
       
        // For ListView
        //
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, [In, Out] ref NativeMethods.LVFINDINFO lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.LVHITTESTINFO lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.LVCOLUMN_T lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, [In, Out] ref NativeMethods.LVITEM lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.LVCOLUMN lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.LVGROUP lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, NativeMethods.POINT wParam, [In, Out] NativeMethods.LVINSERTMARK lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.LVINSERTMARK lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern bool SendMessage(HandleRef hWnd, int msg, int wParam, [In, Out] NativeMethods.LVTILEVIEWINFO lParam);        
        
        // For MonthCalendar
        //
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.MCHITTESTINFO lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.SYSTEMTIME lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.SYSTEMTIMEARRAY lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, [In, Out] NativeMethods.LOGFONT lParam);        
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, NativeMethods.MSG lParam);
        [DllImport(ExternDll.User32, CharSet = CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessage(HandleRef hWnd, int msg, IntPtr wParam, IntPtr lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public extern static IntPtr SendMessage(HandleRef hWnd, int Msg, IntPtr wParam, [In, Out] ref NativeMethods.RECT lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public extern static IntPtr SendMessage(HandleRef hWnd, int Msg, ref short wParam, ref short lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public extern static IntPtr SendMessage(HandleRef hWnd, int Msg, [In, Out, MarshalAs(UnmanagedType.Bool)] ref bool wParam, IntPtr lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public extern static IntPtr SendMessage(HandleRef hWnd, int Msg, int wParam, IntPtr lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public extern static IntPtr SendMessage(HandleRef hWnd, int Msg, int wParam, [In, Out] ref NativeMethods.RECT lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public extern static IntPtr SendMessage(HandleRef hWnd, int Msg, int wParam, [In, Out] ref Rectangle lParam);
        [DllImport(ExternDll.User32, CharSet=CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public extern static IntPtr SendMessage(HandleRef hWnd, int Msg, IntPtr wParam, NativeMethods.ListViewCompareCallback pfnCompare);
       
        [DllImport(ExternDll.User32, CharSet=System.Runtime.InteropServices.CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        public static extern IntPtr SendMessageTimeout(HandleRef hWnd, int msg, IntPtr wParam, IntPtr lParam, int flags, int timeout, out IntPtr pdwResult);
    }
    
}

InvokeRequired的实现

System.Windows.Forms.Control类中ISynchronizeInvoke接口的InvokeRequired的实现。

/// <include file='doc\Control.uex' path='docs/doc[@for="Control.InvokeRequired"]/*' />
/// <devdoc>
///     Determines if the caller must call invoke when making method
///     calls to this control.  Controls in windows forms are bound to a specific thread,
///     and are not thread safe.  Therefore, if you are calling a control's method
///     from a different thread, you must use the control's invoke method
///     to marshal the call to the proper thread.  This function can be used to
///     determine if you must call invoke, which can be handy if you don't know
///     what thread owns a control.
///
///     There are five functions on a control that are safe to call from any
///     thread:  GetInvokeRequired, Invoke, BeginInvoke, EndInvoke and
///     CreateGraphics.  For all other method calls, you should use one of the
///     invoke methods.
/// </devdoc>
[
Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
SRDescription(SR.ControlInvokeRequiredDescr)
]
public bool InvokeRequired {
    get {

        using (new MultithreadSafeCallScope())
        {
            HandleRef hwnd;
            if (IsHandleCreated) {
                hwnd = new HandleRef(this, Handle);
            }
            else {
                Control marshalingControl = FindMarshalingControl();

                if (!marshalingControl.IsHandleCreated) {
                    return false;
                }

                hwnd = new HandleRef(marshalingControl, marshalingControl.Handle);
            }

            int pid;
            int hwndThread = SafeNativeMethods.GetWindowThreadProcessId(hwnd, out pid);
            int currentThread = SafeNativeMethods.GetCurrentThreadId();
            return(hwndThread != currentThread);
        }
    }
}

这是在判断windows窗体线程和当前的调用者线程是否是同一个,如果是同一个就没有必要封送了,直接访问这个GUI控件吧。

进程间传递数据实践

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace SendMsg
{
    #region 结构体定义
    [StructLayout(LayoutKind.Sequential)]
    public struct COPYDATASTRUCT
    {
        public int dwData;
        public int cbData;
        public int lpData;
    }

    //定义要传递的Struct
    [StructLayout(LayoutKind.Sequential)]
    struct SendMsgInfo
    {
        /// <summary>
        /// 参数(单号)
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
        public string AccessionNum;
        /// <summary>
        /// 参数(原文)
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5000)]
        public string ReportTxt;
        /// <summary>
        /// 参数(Ris用户)
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
        public string RisUid;
        /// <summary>
        /// 调用方法名
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
        public string FunctionNum;
        /// <summary>
        /// 端口
        /// </summary>
        public int Handle;
        /// <summary>
        /// 返回值
        /// </summary>
        public bool Result;
        /// <summary>
        /// Err信息
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 200)]
        public string ResultMsg;
    }
    #endregion

    class CaSendMsg
    {
        Process pro;
        const int WM_COPYDATA = 0x004A;

        private string _winFrmName = @"校验程序";
        private string _path = @"校验.exe";
        private int _interval = 500;
        private int _maxTimes = 5;

        /// <summary>
        /// 窗体名称
        /// </summary>
        public string WinFrmName
        {
            set { _winFrmName = value; }
            get { return _winFrmName; }
        }

        /// <summary>
        /// 程序路径
        /// </summary>
        public string Path
        {
            set { _path = value; }
            get { return _path; }
        }

        /// <summary>
        /// 等待轮序频率 秒/次
        /// </summary>
        public int Interval
        {
            set { _interval = value; }
            get { return _interval; }
        }

        /// <summary>
        /// 上限次数
        /// </summary>
        public int MaxTimes
        {
            set { _maxTimes = value; }
            get { return _maxTimes; }
        }

        [DllImport("user32", EntryPoint = "SendMessageA")]
        public static extern int SendMessage(int hWnd, int wMsg, int wParam, ref COPYDATASTRUCT lParam);
        
        [DllImport("User32.dll", EntryPoint = "FindWindow")]
        public static extern int FindWindow(string lpClassName, string
            lpWindowName);        

        /// <summary>
        /// ca校验
        /// </summary>
        /// <param name="h"></param>
        /// <returns></returns>
        public bool CACheck(SendMsgInfo h)
        {
            int hWnd = GetWinFrmHwnd();
            if (hWnd <= 0)
                return false;
            return CACheck(h, hWnd);
        }

        /// <summary>
        /// 获取程序句柄
        /// </summary>
        /// <returns></returns>
        private int GetWinFrmHwnd()
        {
            int hWnd = FindWindow(null, _winFrmName);
            if (hWnd <= 0)
            {
                hWnd = StartNewPro(_path);
            }
            return hWnd;
        }

        /// <summary>
        /// 开启新的校验进程
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private int StartNewPro(string path)
        {
            try
            {
                pro = new Process();
                pro.StartInfo.FileName = path;
                pro.Start();
                return pro.MainWindowHandle.ToInt32();
            }
            catch
            {
                return 0;
            }
        }

        public bool CACheck(SendMsgInfo info, int hWnd)
        {
            try
            {
                int size = Marshal.SizeOf(typeof(SendMsgInfo));
                byte[] Bytes = new byte[size];
                //根据定义的尺寸分配内存块
                GCHandle GC = GCHandle.Alloc(Bytes, GCHandleType.Pinned);
                IntPtr ptr1 = GC.AddrOfPinnedObject();
                //获得Struct对应的IntPtr
                Marshal.StructureToPtr(info, ptr1, false);
                COPYDATASTRUCT SendData = new COPYDATASTRUCT();
                SendData.lpData = ptr1.ToInt32();
                SendData.cbData = size;
                
                if (hWnd > 0)
                {
                    SendMessage(hWnd, WM_COPYDATA, 0, ref (SendData));
                }

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool getCaResult(System.Windows.Forms.Message m)
        {
            COPYDATASTRUCT RecvData = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
            SendMsgInfo info = (SendMsgInfo)Marshal.PtrToStructure((IntPtr)RecvData.lpData, typeof(SendMsgInfo));
            //h.ResultMsg;
            return info.Result;
        }

    }
}

接收消息

//接收消息
protected override void DefWndProc(ref System.Windows.Forms.Message m)
        {
            switch (m.Msg)
            {
                case WM_COPYDATA:
                    COPYDATASTRUCT RecvData = (COPYDATASTRUCT)m.GetLParam(typeof(COPYDATASTRUCT));
                    SendMsgInfo h = (SendMsgInfo)Marshal.PtrToStructure((IntPtr)RecvData.lpData, typeof(SendMsgInfo));

                    textBox1.Text = h.MessageType.ToString();
                    textBox2.Text = h.MessageText;
                   
            //返回校验结果
            SendMsgInfo returnInfo = new SendMsgInfo();
            returnInfo.Handle = h.Handle;
            returnInfo.AccessionNum = h.AccessionNum;
            returnInfo.Result = result;
            returnInfo.ResultMsg = "";
            CaSendMsg c = new CaSendMsg();
            c.CACheck(returnInfo, returnInfo.Handle);
                default:
                    base.DefWndProc(ref m);
                    break;
            }
        }

参考

posted @ 2021-09-06 22:09  TaylorShi  阅读(327)  评论(0编辑  收藏  举报