Feel like a geek and get yourself Ema Personal Wiki for Android and Windows
Showing posts with label VB6. Show all posts
Showing posts with label VB6. Show all posts

22 September 2009

"convenience" back doors

It can be tempting to create "convenience back doors" in code to make short cuts. "Isn't it easier to skip all these layers and just call layer 1 from layer 3?". Yes, it is easier in the short run, but later on it will make refactoring very hard. I think the word "just" in any code-related conversation should set off alarms in your head. Layers are there for a reason.

I am currently involved in replacing a DAL for an existing legacy application. The existing DAL creates SQL statements and passes them to a handler that executes the statements with a connection from a connection pool. My job is to remove the SQL statements and replace them by service calls. Luckily the statements are very simple, because the application once used DBase, which isn't a relational database. So most of the SQL statements don't even use joins. This is of course very inefficient, but in this case my luck.

I started bottom-up, so I abstracted the SQL handler first to be able to monitor what is going through. Secondly i started looking for calls to the SQL handler class. It is a Vb6 application, but there is actually a layered architecture, so I figured that too wouldn't be too hard.

Until i found a function in the application layer. The function was there for "convenience". With the function anyone can throw any SQL statement at the SQL handler. And did i mention ... the function is part of the COM component's public interface.

The point of making classes and functions or, more generally, layers, is to abstract implementation details away from its users.

So much for abstraction.

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.

16 June 2009

Registration-free COM components try-out (continued)

As described in the previous post, I am trying out Side By Side (reg free) COM, trying to solve versioning and deployment issues we have with our software. In the previous post, I looked at VB6 and COM, this post is about my adventures with .NET and COM. The conclusion is: don't use the walkthrough as is, you won't be able to get a successful regfree installation.

.NET and COM



Step 1 Read the walkthrough.

Step 2 Create the COM enabled .NET dll and the VB6 client app
Use Guid.NewGuid().ToString() to create GUIDs for the .NET assembly, class and interface if guidgen is not working for some reason. I created a new windows forms exe which copies a new Guid to the clipboard and exits. Start the vb6 exe to see if it works.

Step 3 unregister the .NET dll
with c:\windows\Microsoft.NET\Framework\v2.0.50727\regasm /u SideBySide.dll. Start the vb6 exe and see that it doesn't work any more.

Step 4 Create the manifest files for the dll and the client exe.
The example manifest in the walkthrough contains errors. FAIL. For example: //assemblyIdentity/@name=" SideBySide" should be "SideBySide", without the space. And //clrClass/@progid="SideBySide.SideBySide" should be "SideBySide.SideBySideClass".

Step 5 Do voodoo magic to embed the manifest into the dll
- Create a resource definition file which references the SideBySide.manifest file
- Create a build.cmd file which creates the resource file and compiles it into SideBySide.dll
Run build.cmd in a VS command prompt (%comspec% /k ""C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"" x86)

Step 6 Start client.exe
Now it should work... gaaah! Automation error!

Step 7 Take hours to troubleshoot
- The error is 80070002, which is supposed to mean "file not found". There are however no entries in the windows event log, and no entries in fuslogvw by which i could get a diagnosis of the problem.
- Googling on the error is not helpful.
- The assembly should have an embedded manifest. Open the file in VS, check: obviously has the right manifest file.
- Manifest files seem to be correct. But because the example in the walkthrough had errors, i feel uncertain about it.
- Because the dll has no version information if you view the properties in Windows Explore, i though that might have to do with the problem. I tried to embed the manifest using Visual Studio. After that, it works, but that is because "register for COM interop" was still checked. Regasm-ing /u and it won't work any more. The file still has no file version properties - but i'm not sure if that is a problem.
- Tried adding the interface to the manifest file also, still fails.
- Tried to build everything from scratch: same automation error occurs.
- I was thinking: we had issues with our own software that appeared after building VB6 exe's against .NET COM dll's and then rebuilding the COM dll's. That could be a problem, so i will test it: build the .NET dll with embedded manifest file and after that rebuild the vb6 exe. Still fails.
- Ok starting from scratch again.
- Tried all variations of the manifest files i could think of: Still fails with the automation error

Step 8 Give up for now.
I have no ideas left. Do some hopeless i-give-up-but-i-dont-want-to-admit-it-browsing and hey, a tool to generate the manifest files instead of having to make them yourself. The tool is called genman32, and is made by Junfeng Zhang, who has some articles about SxS / RegFree com.

Step 9 Having new hope, try again with the genman32 tool
The tool generates a manifest using reflection and is able to embed it in the dll. The generated manifest is unfortunately not correct.. But i could correct the errors and then use the tool to embed the corrected manifest.
- Create manifest with "genman32 <dll>"
- Correct the manifest (<dll>.manifest): Remove /assembly/assemblyIdentity/@processorArchitecture and add /assembly/assemblyIdentity/@type='win32'.
- Embed the manifest in the dll with "genman32 <dllname> /add /manifest:<manifestname>

Step 10 Start the Vb6 exe
Ha! Now it works!! Double check by regasm-ing /u the assembly... still works!! The only difference with my "manual" tries is that the dll now has version information embedded as well. It seems that having this version information is not optional. The manual process described in the walkthrough removes this information, while the genman32 tool preserves it (which is compiled into the dll by Visual Studio automatically).

Conclusion


The document "Registration-Free Activation of .NET-Based Components: A Walkthrough" is not correct and it cost me time and frustration. And i am probably not the only person with this experience. I will provide this story as feedback on the article and i hope the article will be corrected.

But apart from that, the good news is: I do have a way of using .NET COM components in VB6 regfree. I should try this with .NET OCX files too...

Registration-Free COM components try-out

We have both .NET components and VB6 legacy apps. We created a communication layer between the two, both ways, using COM interop.
Unfortunately, this breaks the xcopy install for our .NET software. And introduces unexpected and nasty versioning issues.

Enter reg-free com. This might solve some of these issues. Updating is reduced to copying a new version of the dll, and it even makes possible that different versions of one COM server exist side-by-side (which happened to be the reason behind this technology in the first place).

I record my adventures using registration free COM on this blog for future reference.

VB6 and COM


Step 1 Read the walkthrough.

Step 2 Create the COM and client applications

Step 3 regsvr32 /u sidebyside.dll
And notice that the client exe will now fail.

Step 3 Create manifest files
which hold the information that makes XP find the COM server without the registry.

To know by which GUIDs the component is known, the OLE/COM object viewer is a very nice tool. It can be downloaded from the Microsoft site. The download does not include an essential dll, iviewers.dll, (obviously this has not been tested thoroughly), which can be downloaded from this location. After install, your system has not been polluted with shorcuts to any exe whatsoever, and the program can be found at this intuitive location: "%ProgramFiles%\Resource Kit"

I created a sendto command to drop the dll's onto the exe, which will then show you the information you need.

The "walkthrough" does not walk me through which GUID maps to what attribute in the xml file, but my guessing was right:
/assembly/file/comClass/@clsid maps on the uuid on "coclass SideBySideClass"
/assembly/file/typeLib/@tlbid maps on the uuid on "library SideBySide" (topmost uuid)
/assembly/comInterfaceExternalProxyStub/@iid maps on uuid on "interface _SideBySideClass : IDispatch"
/assembly/comInterfaceExternalProxyStub/@tlbid is the same as /assembly/file/typeLib/@tlbid

Step 4 Start the client exe and celebrate that it works for VB6 exe to COM dll.

Tomorrow: .NET and COM.