Feel like a geek and get yourself Ema Personal Wiki for Android and Windows

17 June 2009

Using MSMQ to communicate between .NET and VB6 applications

The try-out continues. In part 1 of my try-out, i succesfully installed MSMQ and setup a local queue. In part2 I was with some success able to setup communication between two different machines. In this part I will try to setup communication between a .NET application on computer 1 and a VB6 application on computer 2.

I did not use MSMQ in a VB6 application before, and there is surprisingly few documentation about it on the Bathroom Wall Of Code. But it is quite simple actually.

1. Create a new EXE project
2. Add a reference to "Microsoft Message Queue 3.0 Object Library"
3. Create a form with a button. In the button_click, add this (left out errorhandling and creation of the queue):
 Dim q As MSMQQueue
Dim msg As MSMQMessage
With New MSMQQueueInfo
.FormatName = "DIRECT:OS=COMPUTERNAME\Private$\QueueName"
Set q = .Open(MQ_SEND_ACCESS, MQ_DENY_NONE)
End With

Set msg = New MSMQMessage
msg.Label = "TestMessage"
msg.Body = "I am here"
msg.Send q

q.Close

4. Test the exe: works on my machine. I can see a message appearing in the queue, using compmgmt.msc

Now I need a .NET exe that can receive the message and do something with it.

1. Create a new C# WPF exe project.

2. Add System.Messaging reference

3. Create a form with a ListView on it, which binds to an instance variable "ReceivedData":
<Window x:Class="Project.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="332" Width="417"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ListView HorizontalAlignment="Left" Margin="12,12,0,0" Name="listView1" VerticalAlignment="Top" ItemsSource="{Binding ReceivedData}" Height="270" Width="371">
<ListView.View>
<GridView>
<GridViewColumn Header="Data" DisplayMemberBinding="{Binding Text}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>

4. Create the ReceivedData instance variable, which is of type System.Collections.ObjectModel.ObservableCollection<QueueData>:
        private ObservableCollection<QueueData> receivedData = new ObservableCollection<QueueData>();
public ObservableCollection<QueueData> ReceivedData
{
get
{
return receivedData;
}
}

And the QueueData class:
    public class QueueData
{
public string Text { get; set; }
}


5. In the constructor, start listening to the queue. The Formatter should be an ActiveXMessageFormatter, because we are sending plain strings.
        private MessageQueue q;
public Window1()
{
InitializeComponent();

q = new MessageQueue(@"FormatName:Direct=OS:COMPUTERNAME\private$\QueueName");
q.Formatter = new ActiveXMessageFormatter();
q.ReceiveCompleted += messageReceiver;
q.BeginReceive();
}

6. In the messageReceiver handler, add the received data to the listbox by updating the bound collection:
        private void messageReceiver(object sender, ReceiveCompletedEventArgs e)
{
try
{
var msg = q.EndReceive(e.AsyncResult);
var contents = (string)msg.Body;
Dispatcher.Invoke(new Action(addInfo), contents);
}
catch (Exception ex)
{

MessageBox.Show(ex.Message);
}
q.BeginReceive();
}

private void addInfo(string info)
{
ReceivedData.Add(new QueueData() { Text = info });
}

7. And clean up when the window is closed:
        protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (q != null) {
q.ReceiveCompleted -= messageReceiver;
q.Close();
}
}

8. Try it out. And again, works on my machine:


Now that I have the two applications that I need, I will deploy one of them on another machine and see if the two applications will still be able to communicate.

1. Copy the VB6 exe to a virtual machine and try to send a message.

2. "The queue path name specified is invalid". That was caused by myself using the PathName property of the QueueInfo object. The PathName property only works on a local machine, but not on remote machines. I should have used FormatName (fixed in the example). I do not really see the logic behind these propertynames and it cost me quite some time to figure out what was wrong. If PathName is not able to specify remote paths, then why is it possible to include the server name in it? Well, maybe there is some use for it, for example in an AD setup.

3. try again with the fix. Don't forget to allow anonymous user on the queueu. After that, I see the message appear in the remote queue.

In the meantime I have set up another VirtualPC to find out what was wrong in step 2. The message queue that I use now, is on VPC1. VPC1 and VPC2 both have the VB6 client and are both able to write to the queue.

4. Install the WPF viewer application on VPC1 and start it: it reads and displays the messages from Vb6 client on both VPC1 and VPC2 correctly. Install same viewer on VPC2: now we created a race condition (2 WPF viewers processing messages from the same queue), but whatever: it works as well.

The last thing I want to be able to do, is send a message from the WPF application back to the VB6 exe.

1. In the VB6 form, create a Timer Timer1 which ticks every 1000 milliseconds.
2. Create an instance variable which holds another queue, initialize in Form_Load. The queue should have MQ_RECEIVE_ACCESS instead of MQ_SEND_ACCESS.
3. In the Timer1_Timer, wait for the message for 100 milliseconds and process the message if one arrived:
Private Sub Timer1_Timer()

Dim msg As MSMQMessage
Set msg = Me.receiveQueue.Receive(WantDestinationQueue:=True, WantBody:=True, ReceiveTimeout:=100)

If Not msg Is Nothing Then
MsgBox CStr(msg.Body)
End If

End Sub

4. Create the queue on the computer where it should be and assign the anonymous user the proper rights.

In the WPF application should be updated to enable it to send a message in this new queue.
1. Add the same queue just added in the Vb6 app
2. Add a button. In the click handler:
        private void button1_Click(object sender, RoutedEventArgs e)
{
outQueue.Send("WPF application says hi!");
}


Copy new exe's to VPC1 and VPC2. Start the vb6 exe, start the wpf exe and try: works on my machine.

Conclusion for now: MSMQ can be a very handy mechanism if you need to communicate between .NET and legacy VB6 applications. If there are problems, it is often not trivial to debug the problem. But on the other hand, it is a proven and mature technology, which implies a lot of knowledge and experience in the field.

1 comment:

John Breakwell said...

"If PathName is not able to specify remote paths, then why is it possible to include the server name in it? Well, maybe there is some use for it, for example in an AD setup."

That's correct. PathNames require Active Directory to be resolved into FormatNames which MSMQ then turns into IP addresses for sending messages.
PathNames are not used as much but do expose functionality not available with FormatNames.

Cheers
John Breakwell (MSFT)