65.9K
CodeProject 正在变化。 阅读更多。
Home

如何将托管对象作为队列组件的参数传递。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (8投票s)

2002年3月24日

3分钟阅读

viewsIcon

92303

downloadIcon

608

一篇关于如何通过 MSMQ 将托管对象传递给队列组件的文章。

引言

要通过队列组件的方法调用传递一个对象作为参数,客户端将该对象传递给 COM+ 记录器。记录器将该对象编组到 MSMQ 消息中,并将其传递给侦听器。然后,侦听器接收该消息并将其传递给播放器,播放器必须重新实例化该对象以将其分派到客户端指定的方法调用。这意味着要将一个对象作为参数传递给队列组件,它必须能够按值进行编组。由于 COM+ 不为标准 COM 对象提供按值传递语义,因此我们需要为参数对象实现 IPersistStream

接口

IPersistStream 接口提供了用于保存和加载使用简单串行流来满足其存储需求的对象的方法。 IPersistStream 接口从 IPersist 接口继承其定义,因此包括 IPersistGetClassID 方法。这些接口在 objidl.h 中定义。要在我们的类中实现 IPersistStream,我们需要首先在我们的项目中定义它。在本地代码中,IPersistStream 的 Load 和 Save 方法将 IStream 作为输入参数。它在托管世界中的等效项是 UCOMIStream

#region Interfaces of the IPersistStream
//Definition for interface IPersistStream.
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
               Guid("0000010c-0000-0000-C000-000000000046")]
public interface IPersist
{
    void GetClassID( /* [out] */ out Guid pClassID);
};
    
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
               Guid("00000109-0000-0000-C000-000000000046")]
public interface IPersistStream : IPersist
{
    new void GetClassID(out Guid pClassID);

    [PreserveSig]
    int IsDirty( ); 
    void Load([In] UCOMIStream pStm);
    void Save([In] UCOMIStream pStm, [In, MarshalAs(UnmanagedType.Bool)] bool fClearDirty);
    void GetSizeMax(out long pcbSize);
};
#endregion

实现 IPersistStream 接口

我将 myclass 作为参数传递给队列组件,所以我实现了它中的 IPersistStream 接口。当记录器要将对象放入消息队列中时,它会调用 IPersistStream.Save。在此方法中,我首先将成员变量 m_str1 的长度写入流。长度占 2 个字节。然后我们将 m_str1 写入流。自然地,我读取前 2 个字节来确定 IPersistStream.load 方法中字符串的长度,然后从流中获取 m_str1。

由于 UCOMIStream 将字节数组作为 Read 和 Write 方法的输入参数。我们需要将字符串成员变量 m_str1 转换为字节数组。这可以通过调用 System.Convert.FromBase64String 来实现。要将字节数组转换为字符串,我们可以调用 System.Convert.ToBase64String。

UCOMIStream 的 Read 和 Write 方法的最后一个参数是实际读取或写入的字节数。我们需要将一个指向整数的指针传递给该方法,这只能在不安全上下文中完成。这就是 Load 和 Save 方法以 unsafe 关键字为前缀的原因。我们还需要在项目的属性对话框的“配置属性/生成”选项卡中将“允许不安全代码块”开关设置为“True”,以便成功构建项目。

//[Serializable]
[Guid("9EC6C6C7-7843-4102-BC2E-C57053A02C97")]
public class myclass :IPersistStream
{
    public bool m_bRequiresSave;

    public void GetClassID( /* [out] */ out Guid pClassID)
    {
    Debug.WriteLine(@"IMyPersistStreamImpl::GetClassID\n");
    pClassID = new Guid("9EC6C6C7-7843-4102-BC2E-C57053A02C97");
    return ;
    }

    public int IsDirty( )
    {
        Debug.WriteLine(@"IMyPersistStreamImpl::IsDirty\n");
    return m_bRequiresSave ? 0 : -1;
    }

    //called when the instatiate the object from the stream.
    unsafe public void Load([In] UCOMIStream pStm)
    {
        Debug.WriteLine(@"IMyPersistStreamImpl::Load\n");

        Int32 cb;
        byte [] arrLen = new Byte[2];

        if (null==pStm)
            return ;

        //read the length of the string;
        Int32* pcb = &cb;
        pStm.Read(arrLen, 2, new IntPtr(pcb));

        //calculate the length.
        cb = 256 * arrLen[1] + arrLen[0];

        //read the stream to get the string.
        byte [] arr = new byte[arrLen[0]];
        pStm.Read(arr, cb, new IntPtr(pcb));

        m_str1 = Convert.ToBase64String(arr);    
        return;
    }

    //called when saving the object in the stream
    unsafe public void Save([In] UCOMIStream pStm, 
                                 [In, MarshalAs(UnmanagedType.Bool)] bool fClearDirty)
    {
        Debug.WriteLine(@"IMyPersistStreamImpl::Save\n");

        Int32 cb;
        Int32* pcb = &cb;
        byte[] arrLen = new byte[2];

        //convert the string into a byte array.
        byte [] arr =System.Convert.FromBase64String(m_str1);
        arrLen[0] = (byte)(arr.Length % 256);
        arrLen[1] = (byte)(arr.Length / 256);

        if (null==pStm)
            return ;

        //save the array in the stream
        pStm.Write(arrLen, 2, new IntPtr(pcb));
        pStm.Write(arr, arr.Length, new IntPtr(pcb));

        return;
    }

    public void GetSizeMax(out long pcbSize)
    {
        Debug.WriteLine(@"IMyPersistStreamImpl::GetSizeMax\n");

        byte [] arr =System.Convert.FromBase64String(m_str1);

        //the total size is equal to the length of the string plus 2.
        pcbSize = arr.Length +2;

        return ;
    }

    public string m_str1;

    public myclass()
    {
        m_str1 = "";
    }
}

将 myclass 作为参数传递给队列组件

要在 .NET 中创建一个服务组件,我们需要从 ServicedComponent 派生该组件。我们可以向类添加属性以指定 COM+ 配置,或者在组件服务管理单元中手动配置它。

有关如何在 .NET 中创建服务组件的更多信息,您可以参考 MSDN,或 Microsoft 网站上的一篇文章:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnmag01/html/complus0110.asp

public interface IQComponent
{
    void DisplayMessage(myclass m1);
}


[InterfaceQueuing(Interface = "IQComponent")]
public class QComponent : ServicedComponent,
    IQComponent
{
    public void DisplayMessage(myclass m1)
    {
        MessageBox.Show(m1.m_str1, "Component Processing Message");
    }
}

客户端

我们调用 Marshal.BindToMoniker 在客户端创建队列组件。我们创建一个 myclass 对象,并将其作为参数传递给 IQComponent.DisplayMessage 方法。该参数将被持久化到流中并放入消息队列中。在使用完队列对象后,我们调用 Marshal.ReleaseComObject 来释放它。

private void button1_Click(object sender, System.EventArgs e)
{
    IQComponent iQC= (IQComponent) Marshal.BindToMoniker("queue:/new:QCTest.QComponent");

    // Call into the queued component. if we're not connected to an activated
    // server object, this will place a packaged message in the queue.
    myclass m1 = new myclass();
    m1.m_str1 = "Hello World";
    iQC.DisplayMessage(m1);

    // appropriate method for releasing our queued component
    Marshal.ReleaseComObject(iQC);
}

测试它

编译后,我们需要将程序集添加到 GAC 中,并使用 Regasm 注册该程序集。然后,我们创建一个 COM+ 应用程序,并将程序集添加到 COM+ 应用程序中。要将对象指定为排队,我们在“排队”选项卡中设置 COM+ 应用程序的属性,如下所示:

 

运行客户端后,除非您在组件服务管理单元中手动启动它或使用代码,否则队列组件不会启动。您可以右键单击该应用程序并选择“启动”来启动它:

结论

本文演示了如何将托管对象作为队列组件方法的参数传递。我们需要在类中实现 IPersistStream 接口才能跨消息队列传递。

© . All rights reserved.