锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

当前位置:锐英源 / 开源技术 / C#开源 / C#算法开源英语 / Windows平台.NET移动端跨进程通信消息队列开发
联系方式
固话:0371-63888850
手机:138-0381-0136
Q Q:396806883
微信:ryysoft
服务方向
人工智能数据处理
人工智能培训
kaldi数据准备
小语种语音识别
语音识别标注
语音识别系统
语音识别转文字
kaldi开发技术服务
软件开发
运动控制卡上位机
机械加工软件
软件开发培训
Java 安卓移动开发
VC++
C#软件
汇编和破解
驱动开发

锐英源精品原创,禁止全文或局部转载,禁止任何形式的非法使用,侵权必究。点名“简易百科”和闲暇巴盗用锐英源原创内容。

Windows平台.NET移动端跨进程通信消息队列开发


最近开发平台,需要进程间通信,看了些命名管道和复杂的消息队列例子及文章,一直想找轻量的平台,本文里介绍说消息队列可以跨进程,非常感兴趣,翻译学习也,也供大家学习。请记住,看不懂codeproject,请找锐英源软件,学用开源软件,请找锐英源软件。

 

介绍

当需要在不同程序之间传递信息时,Windows Mobile 和 Windows CE 提供了多种技术和解决方案来做到这一点。信息可以通过共享存储位置传递,例如注册表、文件或数据库。对于小消息的频繁通信,可以将消息放在应用程序消息泵上或通过消息队列使用。消息队列与事件、信号量和互斥锁属于同一对象族;它们被命名为内核对象。目前 .NET Framework 不直接支持这些对象。但是通过一些 P/Invokes 声明,可以轻松访问该功能。在本文中,我将展示如何与消息队列功能进行交互。

我的目标不是对消息队列做详尽的解释,而是为读者提供足够的信息来了解这个概念并继续下去。

先决条件

本文建立在其他一些 Windows CE 内核对象的概念之上,即事件、互斥体和信号量。它还基于我在最近一篇关于.NET 的 Windows Mobile 本机线程同步的文章中介绍的代码。在阅读本文之前,请先阅读以上文章;我扩展了上一篇文章中的代码,您在阅读本文之前需要熟悉它。

原生函数和结构

有许多本机函数是本文的核心。可以在 MSDN 库中找到有关函数的详细信息。功能如下:

下面列出了用于其中一些功能的结构:

什么是消息队列

最简单的,队列是一个有序列表。可以将项目添加到列表中或从列表中移动。但是,不能从列表中的任意位置添加或删除项目。项目只能从列表的开头删除,项目只能添加到列表的末尾。这些插入和删除规则通常被标记为 FIFO(先进先出)或 FCFS(先到先服务器)。Windows CE 设备为消息队列提供了两种实现。一种实现是操作系统的一部分,用于同一设备上的进程之间的通信。另一种是MSMQ的实现,可以安装到 Windows CE 设备上,用于与其他机器进行通信。本文围绕这两个实现中的第一个展开。

消息队列可以特定于某个进程,也可以在进程之间共享。在任何一种情况下,消息队列的句柄都允许对队列进行只读或只写访问。您不能使用同一个句柄同时读取和写入消息队列。如果您已经拥有消息队列的句柄,则可以创建额外的句柄来读取或写入关联的队列。这对于未命名的队列尤其重要。

如前所述,本文中的代码基于上一篇文章中的代码构建。上一篇文章中的代码和本文中的代码之间的关系在下面的类层次图中可见。深蓝色 ( MessageQueue, MessageQueueReader, MessageQueueWriter) 中的类是本文要添加的类。

创建和打开消息队列

通过本机函数创建或打开消息队列CreateMsgQueue。与上一篇文章中的同步对象一样,必须为消息队列分配一个名称才能在进程之间共享。如果多个进程创建同名的消息队列,那么每个进程都会收到同一个消息队列的句柄。创建消息队列的调用必须传递一个MSGQUEOPTIONS结构来指定消息队列中的最大项目数、队列中每条消息的最大大小以及请求的是只读句柄还是只写句柄.

如果您的消息队列仅用于将信息移动到同一进程中的线程(在这种情况下,消息队列可能没有名称),您将需要使用您创建的第一个消息队列的句柄来创建另一个句柄使用OpenMsgQueue附加到同一个队列。如果在创建新消息句柄时没有此函数,则无法指定正在创建的句柄应附加到已存在的先前队列。

消息队列由操作系统创建,是一种系统资源。当您不再需要它时,您必须确保释放您对消息队列的句柄。您可以使用CloseMsgQueue释放消息队列句柄。

基类

与系统事件一样,信号量和互斥消息队列是可等待的,可以选择具有名称,并通过必须手动清除的句柄绑定到资源。上一篇文章中的SyncBase类已经设计为保存系统资源的句柄。类的布局如下图所示:

C#
public abstract class SyncBase : IDisposable
    {
        protected IntPtr _hSyncHandle;
        protected bool _firstInstance;

        public bool FirstInstance
        {
            get { return _firstInstance; }
        }

        public IntPtr SyncHandle
        {
            get { return _hSyncHandle; }
        }

        public static SyncBase WaitForMultipleObjects
		(int timeout, params SyncBase[] syncObjectList) {...}

        public bool SetEvent() {...}


        public static SyncBase WaitForMultipleObjects
		(params SyncBase[] syncObjectList) {...}

        public WaitObjectReturnValue Wait(int timeout) {...}

        public WaitObjectReturnValue Wait() {...}


        #region IDisposable Members

        public virtual void Dispose()
        {
            if(!_hSyncHandle.Equals(IntPtr.Zero))
                CoreDLL.CloseHandle(_hSyncHandle);
        }

        #endregion
    }

与系统事件一样,当代码在队列中等待时,它将被阻塞,直到队列处于信号状态。对于队列的只读句柄,信号状态意味着有数据准备好从队列中读取。对于只写句柄,信号状态意味着队列中有足够的空间容纳更多消息。一旦队列已满,句柄就不再发出信号。因此Wait基类中定义的方法无需修改即可使用。

必须定义构造函数并调用CreateMsgQueue. 调用返回的句柄将保存在 member 中_hSyncHandle。

构造函数

虽然有多个消息队列构造函数,但它们都调用两个基本构造函数之一。本机函数CreateMsgQueue在构造函数中使用,它需要MSGQUEUEOPTIONS创建参数的参数。我已经实现了托管类MsgQueueOptions以反映结构,并将此类实例的创建和启动包装在GetMessageQueueOptions方法中。我现在允许调用者有一个消息队列设置。它是MsgQueueOptions的 dwFlags成员。可以通过此标志设置的选项是MSGQUEUE_NOPRECOMMIT和MSGQUEUE_ALLOW_BROKEN。MSGQUEUE_NOPRECOMMIT这会阻止系统预先分配消息队列所需的内存,并根据需要分配内存。MSGQUEUE_ALLOW_BROKEN用于允许写入队列可用,即使另一端没有读取队列。如果未指定此选项,并且如果在读取器连接到队列之前尝试写入队列,则句柄将立即变得不可用(因此,我永远不会省略该标志。如果创建读取器或 writer 导致创建队列,则GetLastWin32Error返回SUCCESS(数值 0),否则返回,ERROR_ALREADY_EXISTS只要返回非零句柄CreateMsgQueue,则调用成功。GetLastWin32Error()队列的句柄刚刚创建。下面是基本构造函数的代码:

C#
internal MessageQueue(string name, bool readAccess, int maxItems, int itemSizeInBytes)
{
    MsgQueueOptions options = GetMessageQueueOptions
		(readAccess, maxItems, itemSizeInBytes);
    _hSyncHandle = CoreDLL.CreateMsgQueue(name, options);
    int lastError = Marshal.GetLastWin32Error();

    if (IntPtr.Zero.Equals(_hSyncHandle))
    {
        throw new ApplicationException(String.Format("Could not create or
		open message queue {0}.  LastWin32Error={1}", name, lastError));
    }
    _firstInstance = (0 == lastError);
}    

另一个重要的构造函数是使用另一个队列端点作为参数创建一个连接到现有队列的队列端点。如果您正在使用没有名称的队列,那么这是您能够为队列创建另一个端点的唯一方法。本机函数OpenMsgQueue用于执行此操作。像CreateMsgQueue这种方法需要一个MsgQueueOptions. 由OpenMsgQueue使用的选项结构中唯一的参数是dwSize和bReadAccess。基本消息队列类的另一个构造函数如下:

C#
internal MessageQueue(MessageQueue source, int maxCount, bool readOnly)
{
    _firstInstance = false;
    MsgQueueOptions options = GetMessageQueueOptions(readOnly, maxCount, 0);
    IntPtr processID = (IntPtr)Process.GetCurrentProcess().Id;
    _hSyncHandle = CoreDLL.OpenMsgQueue(processID, source._hSyncHandle, options);
    if (_hSyncHandle.Equals(IntPtr.Zero))
    {
        int errorCode = Marshal.GetLastWin32Error();
        QueueResult result = Win32ErrorCodeToQueueResult(errorCode);
        string errorMessage = String.Format("Error occurred opening
	   read message queue (Win32Error={0}, QueueResult={1}", errorCode, result);
        if (result == QueueResult.InvalidHandle)
        {
            CoreDLL.CloseMsgQueue(_hSyncHandle);
            _hSyncHandle = IntPtr.Zero;
        }
        throw new ApplicationException(errorMessage);
    }
}

这个消息队列实现是一次性的;当它不再被使用时,可以通过调用 dispose 消息来清理它的资源。

子类化队列

当我为消息队列功能编写包装器时,我考虑过在开发人员尝试从只写队列读取或写入只读队列时抛出异常。根本不允许开发人员执行此类无效操作更有意义。所以我把这个MessageQueue 类分成两个类;MessageQueueReader和MessageQueueWriter。每个都包含一组用于读取或写入消息队列的方法,但不能同时包含两者。这些类的构造函数只调用readOnly 参数设置为trueor的基本构造函数false。

写入队列

用于写入队列的方法使用WriteMsgQueue. 如果队列中没有空间可用于写入消息,该方法将阻止调用者。CreateMsgQueue接受一个名为dwTimeout的参数,用于指定调用者在写入请求被视为失败之前将等待多长时间。如果此值设置为INFINITE(数值 -1),则调用将无限期阻塞,直到有足够的可用空间来执行写入。

在我对这个包装器的实现中,开发人员只能以字节数组的形式传递要写入队列的信息。我考虑过使用泛型来使代码更灵活,但我发现了一些误用和滥用这种实现的方法。将她/他的数据转换为字节数组是开发人员的负担。在我的实现中公开了两种写方法。第一个接受消息字节数组和超时值。第二个仅包含消息字节并假定超时值为INFINITE。

从队列中读取

Read方法反映到Write方法;开发人员为该方法提供一个字节缓冲区和一个可选的超时值。如果没有可读取的内容,则调用将阻塞,并且超时值控制方法在读取尝试被视为失败之前等待消息的时间。

读取和写入结果

由于尝试从队列读取或写入队列的失败是正常执行流程的一部分,因此我决定在写入请求失败时不抛出异常。抛出异常会影响性能,所以我尽量不要不必要地抛出它们。相反,这些方法返回枚举类型的值QueueResult。QueueResult.OK表示读取或写入请求的成功完成,其他值表示失败的原因(例如写入请求超时)。

代码示例

读取器/写入器客户端

读取器/写入器客户端示例创建一个消息队列,并允许用户从主 (UI) 线程将消息添加到队列,并在单独的线程上处理来自队列的消息。从用户体验的角度来看,没有太多可看的。有趣的工作都在代码中。

C#
/// 
/// Waits on messages to be placed on the queue and displays them as they arrive
/// 
void ReaderThread()
{
    using(_reader)
    {
        while (!_shuttingDown)
        {
            //The following call will block this thread until there is either a message
            //on the queue to read or the thread is being signalled to run to prepare
            //for program termination. Since the following call blocks the thread until
            //it is time to do work it is not subject to the same batter killing
            //affect of other similar looking code patterns
            //( http://tinyurl.com/6rxoc6 ).
            if (SyncBase.WaitForMultipleObjects(_readerWaitEvent, _reader) == _reader)
            {
                string msg;
                _reader.Read(out msg);  //Get the next message
                AppendMessage(msg);     //Display the thread to the user
            }
        }
    }
}

/// <summary>  /// Appends processed message to top of list box.  /// </summary>  /// <param name=""message""></param>
public void  AppendMessage(string message)
{
    //If this is called from a secondary thread then marshal it to
    //the primary thread.
    if (this.InvokeRequired)
    {
        this.Invoke(_appendDelegate, new object[] { message });
    }
    else
    {
        this.lstReceivedMessages.Items.Insert(0, message);
    }
}    

写入客户端

Writer 客户端使用前面的代码示例。它连接到与前面的代码示例相同的队列,并且用户放置在队列中的任何消息都将显示在其他程序中(如果它正在运行)。如果您在不启动阅读器客户端的情况下自行运行编写器客户端,则消息将在队列中累积直到填满。如果您尝试在队列已满时将消息写入队列,请求将阻塞 4 秒,然后返回超时结果。

电源通知队列

电源通知队列示例与我在Windows Mobile Power Management上发表的一篇文章有​​关。该程序创建一个队列阅读器,并在调用本机函数时将句柄传递给阅读器队列RequestPowerNotifications。操作系统然后将消息写入队列以通知程序电源状态的变化。通知附加到列表视图的开头。在列表中选择一个项目将导致相关的电源标志显示在屏幕底部。

请求电源通知产生的消息作为结构传递,但我提供的包装器适用于字节数组。我创建了一个新的队列类型,它继承自MessageQueueReader并提供了Read返回电源队列消息的实现。重载Read方法重建PowerBroadcast结构。

C#

public PowerBroadcast Read()
{
PowerBroadcast retVal = new PowerBroadcast();
int bytesRead;
QueueResult result;
result = Read(readBuffer, out bytesRead);
if (QueueResult.OK == result)
{
int message = readBuffer[0] | readBuffer[1] << 8 |
readBuffer[2] << 0x10 | readBuffer[3] << 0x18;
int flags = readBuffer[4] | readBuffer[5] << 8 |
readBuffer[6] << 0x10 | readBuffer[7] << 0x18;
int length = readBuffer[8] | readBuffer[9] << 8 |
readBuffer[10] << 0x10 | readBuffer[11] << 0x18;

retVal.Message = (PowerBroadCastMessageType)message;
retVal.Flags = (PowerBroadcastFlags)flags;
retVal.Length = length;
if ((length > 0)&&( (retVal.Message&PowerBroadCastMessageType.PBT_TRANSITION)
==PowerBroadCastMessageType.PBT_TRANSITION))
{
retVal.SystemPowerState = TextEncoder.GetString(readBuffer,12,length);
}
}
return retVal;
}

结束

我已经介绍了 Windows 消息队列的基本知识,所提供的信息对于更多需要使用消息队列的场景来说应该绰绰有余。但不要停留在这篇文章中。继续阅读有关消息队列和 MSDN 库中的其他命名对象的信息。

友情链接
版权所有 Copyright(c)2004-2021 锐英源软件
公司注册号:410105000449586 豫ICP备08007559号 最佳分辨率 1024*768
地址:郑州大学北校区院(文化路97号院)内劳动服务器公司办公楼一层