Click here to Skip to main content
15,868,349 members
Articles / Programming Languages / C#
Article

How to Pass Managed Objects As a Parameter of a Queued Component.

Rate me:
Please Sign up or sign in to vote.
4.56/5 (8 votes)
23 Mar 20023 min read 91.2K   608   31   5
An article on how to pass managed objects through MSMQ to queued components.

Introduction

To pass an object through a queued component's method call as a parameter, the client passes the object to the COM+ recorder. The recorder marshals the object into an MSMQ message and passes it to the listener. The listener then picks up the message and passes it to the player, the player must re-instantiate the object to dispatch it to the method call specified by the client. This implies that to pass an object as parameter to the queued component, it must be able to marshal by value. Since COM+ does not provide pass-by-value semantics for standard COM objects, we need to implement IPersistStream for the parameter object.

Interfaces

The IPersistStream interface provides methods for saving and loading objects that use a simple serial stream for their storage needs. The

IPersistStream
interface inherits its definition from the IPersist interface, and so includes the GetClassID method of IPersist. The interfaces are defined in objidl.h. To implement IPersistStream in our class, we need to define it in our project first. In the native code, the Load and Save method of IPersistStream takes IStream as input parameter. Its equivalent in managed world is UCOMIStream.

C#
#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

Implement IPersistStream interface

I am passing myclass as parameter to the queued component, so I implement the IPersistStream interface in it. When recorder is to put the object in the message queue, it calls the IPersistStream.Save. In this method, I first write the length of the member variable m_str1 into the stream. The length takes 2 bytes. Then we write m_str1 to the stream. Naturally I read the first 2 bytes to determinate the length of the string in the IPersistStream.load method, and then get the m_str1 out of the stream.

Since UCOMIStream takes a byte array as input parameter for the Read and Write methods. We need to convert the string member variable m_str1 into a byte array. This is achieved by calling System.Convert.FromBase64String. To convert a byte array into a string, we can call System.Convert.ToBase64String.

The last parameter of UCOMIStream's Read and Write methods is the actual bytes that has been read or written. We need to pass a pointer to an integer to the method, which can only be done in the unsafe context. That's why Load and Save method is prefixed with unsafe keyword. We also need to set the Allow unsafe code blocks switch to True in the Configuration Properties/build tab of the project's property dialog in order to build the project successfully.

Image 1

C#
//[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 = "";
    }
}

Pass myclass as parameter to the Queued Component

To create a serviced component in .NET, we need to derive the component from ServicedComponent. We can add attributes the class to specify the COM+ configurations or configure it manually in the Component Services administrative applet.

For more information about how to create service component in .NET, you can refer to MSDN, or a piece of article in Microsoft's website at: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnmag01/html/complus0110.asp

C#
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");
    }
}

The Client

We call Marshal.BindToMoniker to create the queued component at the client side. We create a myclass object and pass it as a parameter to the IQComponent.DisplayMessage method. The parameter will be persisted to a stream and be put in the message queue. After we finished using the queued object, we call Marshal.ReleaseComObject to release it.

C#
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);
}

Test it

After compilation, we need to create add the assembly into the GAC, and use Regasm to register the assembly. Then we create a COM+ application and add the assembly into the COM+ application. To specify the object as queued, we set the COM+ application's properties in the Queuing tab like this:

  Image 2

After running the client, the queued component will not be started unless you start it manually in the Component Service administrative applet or with code. You can right click on the application and select Start to start it: 

Image 3

Conclusion

This article demonstrates how to pass managed object as parameter of queued component's method. We need to implement IPersistStream interface in the class to pass across message queue.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralProblems with the Queued Component Pin
KillerTiger4-Jun-04 23:12
KillerTiger4-Jun-04 23:12 
GeneralSmall bug: string must be below 256 chars Pin
Dirk Vaneynde9-Apr-04 2:34
Dirk Vaneynde9-Apr-04 2:34 
Generalforgot to add COMImport attribute Pin
lbagnall7-Oct-03 17:49
lbagnall7-Oct-03 17:49 
You forgot to add the COMImport attribute to the IPersist and IPersistStream interfaces. They should read:

//Definition for interface IPersistStream.
[ComImport,
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("0000010c-0000-0000-C000-000000000046")]
public interface IPersist
{
void GetClassID( /* [out] */ out Guid pClassID);
};

[ComImport,
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);
};

General.Net Que components Pin
paddun13-Aug-03 10:26
paddun13-Aug-03 10:26 
GeneralI am giving you a 5 as.... Pin
Nish Nishant25-Mar-02 8:41
sitebuilderNish Nishant25-Mar-02 8:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.