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
.
#region Interfaces of the IPersistStream
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("0000010c-0000-0000-C000-000000000046")]
public interface IPersist
{
void GetClassID( 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.
[Guid("9EC6C6C7-7843-4102-BC2E-C57053A02C97")]
public class myclass :IPersistStream
{
public bool m_bRequiresSave;
public void GetClassID( 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;
}
unsafe public void Load([In] UCOMIStream pStm)
{
Debug.WriteLine(@"IMyPersistStreamImpl::Load\n");
Int32 cb;
byte [] arrLen = new Byte[2];
if (null==pStm)
return ;
Int32* pcb = &cb;
pStm.Read(arrLen, 2, new IntPtr(pcb));
cb = 256 * arrLen[1] + arrLen[0];
byte [] arr = new byte[arrLen[0]];
pStm.Read(arr, cb, new IntPtr(pcb));
m_str1 = Convert.ToBase64String(arr);
return;
}
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];
byte [] arr =System.Convert.FromBase64String(m_str1);
arrLen[0] = (byte)(arr.Length % 256);
arrLen[1] = (byte)(arr.Length / 256);
if (null==pStm)
return ;
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);
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
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.
private void button1_Click(object sender, System.EventArgs e)
{
IQComponent iQC= (IQComponent) Marshal.BindToMoniker("queue:/new:QCTest.QComponent");
myclass m1 = new myclass();
m1.m_str1 = "Hello World";
iQC.DisplayMessage(m1);
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:
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:
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.