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

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.

MSMQ continued

Created a simple server app, which creates the queue if instructed, and sends a message down the line. Also created a client app, which listenes to a queue and displays the string message. This works if used on the same machine. But how will it work if i deploy it to another machine?

Step 1: try to make the client app work with the queue name FormatName:Direct=TCP:<ip>\private$\<queuename>

Client app fails on the local machine when I use the ip address that is assigned this computer, but the client app succeeds if I use the loopback address 127.0.0.1

Step 2: copy the client app to a virtual machine that has access to the host machine.

Step 3: start the client, pointing it to the queue on the host machine.
Ouch!!
InvalidOperationException occurred: Message Queuing has not been installed on this computer.

I was silently hoping that Message Queuing would not have to be installed on client machines. In our situation we may have hundreds of client computers that will need this installation. Would there be another way of deploying MSMQ?

Step 4: read about deployment in the FAQ.
Some quotes:
"I want to configure a central queue on a Message Queuing server and have multiple remote clients read from it. Is this a good design? [..leave out some shades of gray...] No."

"The supporting server should be running the same (or later) Message Queuing version as the dependent client"

"Message Queuing does not scale well with multi-CPU computers"

And lots of cryptic error message with their explanations. If a solution requires a 133 page Word document for FAQ alone... what will it be like to work with MSMQ in the field? Looks like a horror scenario to me. Imagine getting this message from a client: "Unsupported option at CQ2QMsgF::QMsg, at (or near) line 2323". Or: "Message Queuing may function in an unpredictable fashion". How about this prose: "This error [MQ_ERROR_INSUFFICIENT_RESOURCES] can be returned by several APIs. It has several (unrelated) reasons"

The document does not answer my question so far. But this discussion seems to do. It involves a nicely formatted batch file which also acts as a configuration file for the installation that it invokes. The file should be named MSMQsetup.bat.
;@ECHO OFF 
;sysocmgr.exe /i:sysoc.inf /u:MSMQsetup.bat
;GOTO Finished
[Components]
msmq_Core = ON
msmq_LocalStorage = ON
msmq_ADIntegrated = ON
msmq_TriggersService = ON
msmq_HTTPSupport = OFF
msmq_RoutingSupport = OFF
msmq_MQDSService = OFF
;:Finished


Step 5: try out the magic batch file on my VM
Ha! it works: installation completed successfully.

Step 6: start my client app again
MessageQueueException occurred: The queue does not exist or you do not have sufficient permissions to perform the operation.
Bad UX from developer point of view: this error message does not give you very much information about how to solve this. But OK, that's what we get paid for: to solve problems that we or other developers create. So:

Step 7: Try out if settings the appropriate permissions on the queue solves the issue.
There are 2 ways to do this: 1) programmatically, 2) by starting compmgmt.msc > services > messsage queuing > private queues > right click on appropriate queue > properties and set permissions.
Setting the permissions programmatically would involve something like this:
  createdQueueu.SetPermissions(
new MessageQueueAccessControlEntry(
new Trustee("Iedereen"),
MessageQueueAccessRights.FullControl));

The "Iedereen" string is Dutch for "Everyone", which obviously is translated to the language of the OS. Very handy. I could not find a (programmatic) way to find out how the "Everyone" group is called on the local machine. But anyway, you would want to configure that and don't want to put that hard-coded in you app.

Step 8: Start the client app again on the VM
Still not working. Set the ANONYMOUS_LOGON permissions to Full Control. Still not working. Could be something with the VM / Host communication. So created the queue on the VM, tried to connect from host. Not helping.

OK I don't have a clue anymore about what could be the problem.

Try something else.
Not
FormatName:Direct=TCP:<ip>\private$\<queuename>
But
FormatName:Direct=OS:<hostname>\private$\<queuename>

Step 9: Start "server" app on host machine, "client" app on VM...
Eureka, it's working now.
Whatever.
Remove permissions for ANONYMOUS_LOGON:
Aha! A different error message:
MessageQueueException occurred: Access to Message Queuing system is denied.

So the problem with the TCP is yet unresolved, but I do have a working remote queue installation now.


By the way. The server app:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Messaging;

namespace ServerApp
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: ServerApp <queue> [create]");
Console.WriteLine(" Will send a message \"Hallo!\" down the line.");
Console.WriteLine(" Specifying the \"create\" command will create the queue.");
return;
}

var queueName = args[0];

try
{
using (var q = (args.Length == 2 ? MessageQueue.Create(queueName) : new MessageQueue(queueName)))
{
q.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) });
q.Send("Hallo!");
Console.WriteLine("Message sent");
}
}
catch (Exception e)
{
Console.WriteLine(e.GetType().Name + " occurred. " + e.Message);
}
}
}
}]]>



The client app:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Messaging;

namespace ClientApp
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("usage: ClientApp <queuename>");
Console.WriteLine(" reads 1 message from the given queue");
return;
}

var queueName = args[0];

try
{
using (MessageQueue q = new MessageQueue(queueName))
{
q.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) });
Console.WriteLine("Waiting for message...");
Console.WriteLine("Received message: " + (string)q.Receive().Body);
}
}
catch (Exception e)
{
Console.WriteLine(e.GetType().Name + " occurred: " + e.Message);
}
}
}
}

15 June 2009

MSMQ try-out

MSMQ (Microsoft Message Queuing) seems like a powerful thing:
  • Transactional
  • Robust
  • Works with both COM (vb6!) and .NET
So I decided I should at least know how it works and how I can use it in a project. There are nice .NET wrappers around it, abstracting it away, like NServiceBus, but I should know how it works anyway before using any framework.

Step 1: try out an example from The Bathroom Wall Of Code
Failed. "MSMQ is not installed on this machine". It needs an install of an extra windows component! FAIL! How can I ever use this on my server that is managed by Mordac? Note to self: try out Ayende's Rhino Queues, xcopy install, with no dependency on MSMQ.

Step 2: install MSMQ
Software > Add Windows Component > MSMQ > Installation failed for unclear reasons. Some searching revealed that I had to reset the Distributed Transaction Log by some obscure command. Should of course have thought of that before trying the install. Retry install: fail again. This component has been registered for removal. Ok. Restart machine. Retry again. Now it installs.
Update: John Breakwell blogged about the details and solution of this problem here.

Step 3: Make the queue

MessageQueue q = MessageQueue.Create(@".\Private$\TestQueue");

Step 4: create and configure the queue in 2 applications
MessageQueue q = new MessageQueue(@".\Private$\TestQueue");
q.Formatter = new XmlMessageFormatter(new Type[] { typeof(String) });

Step 5: send message from application 1
q.Send("hi!", "message");

Step 6: receive the message in application 2
Console.WriteLine((string)q.Receive().Body);

Step 7: Watch it work.

31 March 2009

Quickstart tutorial for NHibernate.Search

To use NHibernate.Search one has to extract the instructions from a lot of different places, which sometimes have outdated information. 
This blog post is a summary of how I was able to successfully add an NHibernate.Search-powered search function to an existing NHibernate project.

This guide assumes you have an NHibernate project and know how it works.

1. Assemblies
Add references to NHibernate.Search.dll and Lucene.Net.dll to your project. There is no official download for NHibernate.Search, you have to compile it yourself using the nhcontrib project. But you could also download the dll's I compiled on this site.

2. Configuration
Some properties have to be added to the Configuration object:
  
  'configure directory where indices are stored
  cnf.SetProperty("hibernate.search.default.directory_provider", _
    GetType(FSDirectoryProvider).AssemblyQualifiedName)
  cnf.SetProperty("hibernate.search.default.indexBase", "~/Index")
  cnf.SetProperty("hibernate.search.default.indexBase.create", "true")

  'event listeners to automagically update indices
  cnf.SetListener(NHibernate.Event.ListenerType.PostUpdate, _
    New FullTextIndexEventListener())
  cnf.SetListener(NHibernate.Event.ListenerType.PostInsert, _
    New FullTextIndexEventListener())
  cnf.SetListener(NHibernate.Event.ListenerType.PostDelete, _
    New FullTextIndexEventListener())

The direcotry where the indices are stored can be a relative path by using the ~ as the application root. This will be translated by NHibernate.Search to a real path.

3. Add search attributes to model 
  <Indexed(Index:="MyDomainClass")> _
as attribute on the class definition 

  <Field(Index.Tokenized, Store:=Store.No)> _
For searchable textfields 

  <DocumentId()> _
For the Id field.

4. Decorate the session 
Decorate the session with a full text searchable session:

Imports NHibernate.Search
[...]
session = Search.CreateFullTextSession(sessionFactory.OpenSession())

5. Convert existing data 
Often you will want existing data to be searchable. This can be done by indexing the old data once. New entries and changes will automatically be merged with the existing indices.
Here is a generic method to index existing data.

   '''
    ''' create full text index for type T
    '''
    Public Sub CreateIndex(Of T)()
      'decorate the session
      Dim fullTextSession = _
        NHibernate.Search.Search.CreateFullTextSession(Session)
      Using trans = fullTextSession.BeginTransaction()
        Dim query = fullTextSession.CreateQuery( _
          String.Concat("from ", GetType(T).Name) _
        )
        For Each thing In query.List()
          fullTextSession.Index(thing)
        Next
        trans.Commit()
      End Using
    End Sub

6. Add search to your repository (or [some layer] )
Now you can search with nhibernate! Lucene has some powerful features, but the nhibernate search in its simplest form looks like this:

  Dim query = fullTextSession.CreateFullTextQuery(Of MyDomainClass)(  _
    "MyProperty:searchterm1 OR AnotherProperty:searchword2" _
  )
 Dim results = query.List(Of MyDomainClass)

I hope this helps.