Thursday, November 6, 2008

How to switch on log4net internal logging

If you want log4net to show its own internal logging, which is useful to find out where it is looking for your XML configuration file, you can add the following XML to your app.config file:

<configuration>
<appSettings>
<add key="log4net.Internal.Debug" value="true"/>
</appSettings>
</configuration>

Note that if you are using log4net in a DLL, this goes into the app.config file for the EXE that is calling your DLL.

Monday, November 3, 2008

Log4Net using a named config file

Setting up Log4Net with your configuration in the app.config file is pretty straight-forward to do - a quick Google will lead to a number of posts on how to do this.

But if you decide to move your log4net config settings to an external file (convention seems to be "log4net.config") then there are a few misconceptions floating around. There are only two further steps you need to take to achieve this:

1. Copy the <log4net> ... </log4net> section out of your app.config and save it into a new file called log4net.config. You should totally remove it from the app.config file once you feel confident - it is no longer needed.

2. Modify your AssembyInfo.cs file by removing this:

[assembly: log4net.Config.XmlConfigurator
(Watch = true)]


and replacing it with this:

[assembly: log4net.Config.XmlConfigurator
(ConfigFile = "log4net.config", Watch = true)]


Now if you fire up the IDE and run it, your logging should still work. If you don't see any logging, then check that the log4net.config is in the current application path - if you are running from the IDE then that will be where the EXE lives at "../MyProject/bin/Debug/". If you've placed the log4net.config file in your project home folder then you can get it working through the IDE with this:

[assembly: log4net.Config.XmlConfigurator
(ConfigFile = "../../log4net.config", Watch = true)]


Note that this will need to be reviewed come deployment time!

OR, even better, just select the config file in the IDE and, in the Properties window, change the "Copy to Output Directory" value to "Copy always". Now when you build it through the IDE it will follow your compiled file around. Nice!

Note that you do NOT need to add log4net.Config.XmlConfigurator.Configure() or log4net.Config.XmlConfigurator.Configure({file name}) to the code in your project! This does a manual configuration which overrides what is in the AssemblyInfo.cs file. If you get the AssemblyInfo.cs parameters right then that is all you need.


Tuesday, October 28, 2008

Email problems with CXF

I was having a problem sending emails from my Apache CXF web service. The emails just wouldn't get sent from within an exception handler on the web service.

CXF (2.0.4 in my case) provides it's own mail classes (geronimo-javamail_1.4_spec-1.0-M1.jar and geronimo-activation_1.1_spec-1.0-M1.jar). The solution was to remove references to these libraries from the Classpath, and instead use the Sun libraries (mail.jar and activation.jar).

This led to my second problem. Now my CXF emails would send, but they were displaying "(no subject)" for the subject. A bit of digging revealed that the Geronimo mail jars were still floating around in my deployed webapp library folder. Completely purging the web app and redeploying it fixed this.

Wednesday, October 22, 2008

Viewing what you are about to dcommit

If you have stacked up a bunch of Git commits in the master, and you want to see what changes are going to be pushed to Subversion at the next git svn dcommit, you can use the commands below:

$git svn dcommit --dry-run (shows a list of commits that will be applied)
Committing to http://xyz/svn/Project/trunk ...
diff-tree bb2820965209ff83d016817b46268f289e65ab07~1 bb2820965209ff83d016817b46268f289e65ab07
diff-tree cff6b4575f6f7797652f030cbb8b425f57f4e08a~1 cff6b4575f6f7797652f030cbb8b425f57f4e08a

This reveals that there are two commits that are going to Subversion. You can then view the file names involved in each commit via:

$git diff-tree --stat bb2820965209ff83d016817b46268f289e65ab07~1 bb2820965209ff83d016817b46268f289e65ab07

Or you can view the actual source code changes in each commit, via:

$git diff-tree -p bb2820965209ff83d016817b46268f289e65ab07~1 bb2820965209ff83d016817b46268f289e65ab07

Tuesday, October 21, 2008

Ignoring local changes to tracked files in Git

If you have a file that lives in the Git repository, and you want to change it locally, but don't want to push it back to the repository, you can use the git update-index --assume-unchanged command.

In my case, I have a few properties files that hold machine-specific file paths. My production box is a Windows server, and my dev box is a Mac, so the paths can't be the same. But I like to have the production version of the file in the repository so I can check it out and build it straight into production.

Say the file is called build.properties. Once I've made a change to it, and run a git status, I can see it in my list of changes:

# On branch master
# Changed but not updated:
# (use "git add ..." to update what will be committed)
#
# modified: build.properties
#


I could just not git add the file, but I like to use git add . and I keep forgetting. Using the Git ignore functionality won't work either. If I add the file name to my .gitignore file, it still shows up as modified.

# On branch master
# Changed but not updated:
# (use "git add ..." to update what will be committed)
#
# modified: .gitignore
# modified: build.properties
#

The reason for this is that once a file is tracked in the repository, it won't be ignored. That would have worked if we never wanted the build.properties file in the repository in the first place though.

The solution is to stop Git from tracking changes to the file:
git update-index --assume-unchanged build.properties

Now when I do my git status:

# On branch master
nothing to commit (working directory clean)


Nice! And you can still explicitly add the file if you like. For more info, check the manual.

Edit: I'm not satisfied with this approach. When I do a "git add ." it actually adds my untracked files (even though they don't show up in the list of changes). This has given me a few surprises. I might have to also add the files to my .gitignore. Hmmmm. This is still a WIP!

Friday, October 17, 2008

How to "git svn dcommit" with uncommitted changes

If you committing directly off the master, and have some files modified locally that you do not want to push up to the SVN server, and you have not added them to the ignore list, then you will get a "file.abc: needs update" error message when you try to do your "git svn rebase". The way to get around this is to stash the local updates, rebase and dcommit, and then unstash them:

git svn rebase --> "file.abs: needs update"
git stash (save local changes away)
git stash list (have a look at what is stashed)
git svn rebase
git svn dcommit
git stash apply (back to where we were before)

Alternatively, use a branch for local changes and merge them into the master. This way the master only contains files that are going back to the SVN trunk.

Adding an empty folder to a Git repository

You can't actually add an empty folder to a Git repository - it handles files not folders. In my case though, I like to have the repo holding an empty copy of my "dist" folder so that I can grab the repo and then do a build without having to create the "dist" folder separately.

My solution is to created the folder and add a ".gitignore" file in there. In order to ignore everything in that folder (apart from itself), the .gitignore file has the following contents:

# Ignore everything in here apart from the .gitignore file
*
!.gitignore


The only other thing I need to do is ensure that my ant build script does not delete the .gitignore file everytime I do a build and clean the "dist" folder. So I add this "exclude" code to my build.xml script:


<delete>
<fileset dir="${dist.dir}">
<exclude name=".gitignore" />
<include name="**/*.*"/>
</fileset>
</delete>


In hindsight, I probably should have gotten the build script to create the "dist" folder. Nevermind, but it was fun trying to find a solution!

Thursday, October 16, 2008

Find folder differences

To compare two folders on your Mac, and find the files that differ:
diff -rq dirA/ dirB/ |grep -v -e '\.svn' -e '\.git'|sort

Remove the '-q' option to see line differences:
diff -r dirA/ dirB/ > diff.txt

Friday, October 10, 2008

Error 51: Unable to communicate with VPN subsystem

If you're hitting "Error 51: Unable to communicate with VPN subsystem" when trying to start the VPN Client on your Mac, the solution is:

  • shutdown the VPN Client

  • start a terminal window

  • restart the VPN via:


  • sudo /System/Library/StartupItems/CiscoVPN/CiscoVPN restart


    Thanks to Anders for this one.

    Thursday, September 25, 2008

    What I want from a Web Service Registry

    Coming back to web services after a break has revealed the necessity for a good governance system, and the weaknesses of the one I have now. Mule Galaxy is just not up to it. It is clunky to use, and I randomly get some errors when updating. What I need is:



    • A central repo where I can store all web service related metadata - the name, URL, purpose, dates, owners, version numbers, dependencies, etc.


    • A one page list of the current web service URLs, what they do, and what their status is (live/dev/test etc). This is the one that I am sorely missing right now! One web service per line


    • Keeping the WSDL in source control is useful, but there needs to be an easy way to get it in there, to compare differences, and to link the source code URL into the metadata repo; and there needs to be a clear process for when (at what stage in the dev process) the WSDL in source control gets updated


    • I need to regularly test/ping these services to ensure they are still up and alert if they aren't. This could be done through something like Servers Alive. Ideally it should dynamically look-up my metadata repo and automatically test the services that are at the right status. Otherwise I'll just forget to add the test into Servers Alive. The repo can hold the names of the people to alert. It would be nice if it could update back into our repo the date/time of the last successful test, and the current test status (up/down)


    • Most of all, it needs to flow well and be intuitive to use - buttons/links that lead me through high-level tasks. I don't do this everyday so I don't want to have to refer to a manual every time I want to do something (like upgrade a web service and deploy it).


    • What I don't want is run-time web service look-ups!


    • What I also don't want is some giant heavy-weight system that does all of this, plus 1,000 things more. Well actually, I don't mind if it does 1,000 things more, as long as they are hidden/unobtrusive and don't turn it into a click-coffee-click tool.




    I can see why so many people end up writing their own registries!

    Sunday, September 14, 2008

    Upgrading iPhone firmware with Pwnage

    These are the steps I use to upgrade my firmware on my 2G iPhone:

    1. Use Pwnage to build the custom IPSW file

    2. Put the iPhone into recovery mode

    • Disconnect iPhone

    • Shutdown iPhone

    • Hold down home button

    • Plug iPhone in



    3. Restore custom IPSW file

    • In iTunes, hold down Alt and click restore

    Friday, September 12, 2008

    VMWare Fusion won't shutdown

    I had a problem where I couldn't shutdown my Windows XP under VMWare Fusion - it started shutting down and then just locked up. There doesn't appear to be any way to force a shutdown via Fusion.

    My solution, via Google, was:

    1. Force quit VMWare Fusion and vmware-vmw (password required) via the Activity Monitor.

    2. Goto to the Virtual Machine (eg. Documents/Virtual Machines/XP) and "Show Package Contents"

    3. Find the file ending with ".vmem" and move it to the trash.

    4. Restart the Virtual Machine and all should be good!

    Edit:
    Well, it turns out there is an easy way to do this from the VMWare application itself. In the comments below, Ben from VMWare mentions that holding down the "Option" key while the Virtual Machine menu is open will change the "Shut Down Guest" to "Power Off". Nice!!

    Friday, September 5, 2008

    "could not sync mail accounts to the iphone because the iphone cancelled the sync"

    I got this problem after adding a new mail account to my Mac and then trying to sync it to the iPhone (by selecting it to Sync in iTunes).

    If you have a JailBroken iPhone, you can fix this by:


    ssh root@xxx.xxx.xx.xxx (default password is 'alpine')
    cd /var/mobile/Library
    chmod -R 777 Mail/

    Thursday, August 7, 2008

    Some tricks when deploying your .Net COM Dll to the GAC

    A little trick here:

    Add the Dll manually to the GAC in the Setup project (as a file rather than as a "project Output"); then set the "register" property of the Dll to "vsdraCOM". If you use "Project Output" for the Dll project it seems to (a) register the COM object only sometimes, and (b) keeps trying to stick the TLB into the GAC and giving you an error.

    Wednesday, July 23, 2008

    taskdef class. org.apache.catalina.ant.InstallTask cannot be found

    If you get this error trying to run your ant build, this can fix it:

    Copy the "catalina-ant.jar" file from $TOMCAT_HOME\server\lib to $ANT_HOME\lib

    Updated: 20 Feb 2013

    If you've updated to Tomcat 7 then somewhere along the way, the "InstallTask" has been deprecated and replaced with "DeployTask". If you edit your build script and replace it, it should work.  For more details, read here.

    In order to get some of your other Tomcat 7 Catalina-Ant bits working (eg. StartTask, StopTask), follow Paul Grenyer's useful guide.

    Tuesday, July 22, 2008

    Moving your VB6 COM collections to .Net under Vantive VBA

    A pretty typical VB6 object model was to use a collection class to represent a group of objects. So you'd have a "Car" object with properties like "Colour", "Make", "Model" etc. and you'd store multiple of these in a "Cars" object, which would internally use a Collection and externally provide your "Add", "Count" "Remove" and enumerator methods. You could expose these as method parameters via COM and Vantive VBA would happily be able to enumerate the collection:


    Dim oCars as Object
    Dim oCar as Object
    Set oCars = oComVB6.FetchCarsByColour("blue")
    For Each oCar in oCars
    MsgBox "Make is " & oCar.Make
    Next oCar


    Skip forward a few years: Vantive is still kicking and it is time to convert your VB6 COM component to .Net. Our VB6 Collection object is gone, what will we use instead? What about an array of objects? So in .Net we have:


    public class Car : ICar {...}
    public Car[] FetchCarsByColour(string colour) {...}


    To access these from Vantive VBA, this should do the trick:


    Dim oCars() as Object
    Dim oCar as Object
    oCars = oComNet.FetchCarsByColour("blue")
    For I = 0 to UBound(oCars)
    ...


    This works fine via VB6, but when you try to compile this in Vantive VBA, you'll get a "Cannot assign whole array" error message on the oCars=... line.

    The solution is to use the ArrayList class. Like this:


    public ArrayList FetchCarsByColour(string colour) {...}


    Which you can then access in Vantive VBA exactly as you could with the VB6 collection, including the "Count" method:


    Dim oCars as Object
    Dim oCar as Object
    Set oCars = oComNet.FetchCarsByColour("blue")
    For Each oCar in oCars
    MsgBox "Make is " & oCar.Make
    Next oCar
    MsgBox "You found " & oCars.Count & " cars"

    Wednesday, June 18, 2008

    "TNS-12541: TNS:no listener" error proves no match for Super-useful Oracle commands

    I'm running Oracle on my XP development box. After making a few changes to my network I found that Oracle no longer started. The first step to checking things was to use super-useful Oracle command #1: "tnsping". If you have the Oracle bin in your path, you can run this from anywhere:
    tnsping mybase

    TNS Ping Utility for 32-bit Windows: Version 10.2.0.1.0 - Production on 18-JUN-2
    008 11:14:42

    Copyright (c) 1997, 2005, Oracle. All rights reserved.

    Used parameter files:
    C:\oracle\product\10.2.0\db_1\network\admin\sqlnet.ora

    Used TNSNAMES adapter to resolve the alias
    Attempting to contact (DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 10.0.1.1
    98)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = mybase)))

    TNS-12535: TNS:operation timed out


    So, first problem is that the host name I'm using is hard-coded in there to be "10.0.1.1". A quick check of "ipconfig /all" reveals that, with my network tinkering, my IP address has changed. In fact, I'm running XP on a virtual machine and not using a static IP address so the IP address was going to change sometime anyway. So we'll replace my IP address with my new IP address instead. To modify this setting, find your "tnsnames.ora" file; for me it lives at C:\oracle\product\10.2.0\db_1\network\admin\tnsnames.ora.

    OK, so now a "tnsping" gives me the lovely "TNS-12541: TNS:no listener" message. Checking my Windows Services, I can see that the Oracle TNS Listener service (OracleOraDb10g_home1TNSListener) is stopped. If I try to start it I get a nice generic Windows Service message "Error 1067: The process terminated unexpectedly."

    So, how do I find out what is happening with the listener? By using super-useful Oracle command #2: "lsnrctl". Again you can find this in your Oracle bin directory. You can now check the listener status via "lsnrctl status". Again, this revealed the same "10.0.1.198" IP address hard-coded in the listener config. So again we need to replace that with the new IP address also. The file that controls this is "listener.ora" and it lives in the same place as your "tnsnames.ora".

    I found these needed to be done in a set order:
    1. Shutdown Oracle.
      sqlplus user/pwd as sysdba
      shutdown immediate

    2. Stop the listener.
      lsnrctl stop

    3. Change the IP addresses in *.ora files.


    4. Start the listener (notice that your database is not registered)
      lsnrctl start

    5. Start Oracle.
      sqlplus user/pwd as sysdba
      startup

    6. Check the db is registered to the listener.
      lsnrctl status

    7. Try to login via the tnsname.
      sqlplus user/pwd@mybase

    So, to summarise, replace the hard-coded IP address with your new IP address in your "tnsnames.ora" and "listener.ora" files.

    Note that I did try to use both "localhost" and "127.0.0.1" but couldn't get the listener to pick up the database√.

    Tuesday, June 10, 2008

    Debug into .Net from VB6

    If you've got a VB6 app that is calling a .Net component, and you want to use the VS debugger on the .Net component then you need do the following:


    • Start the VB6 app

    • Set a break point in your .Net app

    • In VS, click on "Tools-Attach to Process..." and select the VB6 application/IDE. This will start your component running in VS.



    Now, back in VB6, click the button (or do the action) that activates your .Net component. It will switch into the VS debugger on your breakpoint. Nice!

    Be careful about which version of your .Net Dll is registered on the machine (release/debug). If you hav ethe relase one activated then it won't give you any debugging - it does warn you though. To get your debug one back, switch VS back to debug build, close your VB6 app/IDE, and rerun your .Net app. This will re-register the debug version.

    For more details, try this.

    Update: Very, very, VERY, VERY IMPORTANT!!!! You must enable "Managed Code" debugging when you attach to the process. So, in the "Attach to Process" window, you might have "Automatic: Native Code" showing. This didn't work for me - presumably because my Dll is managed, not native. So click the "Select..." button next to the "Attach to:" and choose "Managed" and "Native". Now attach to VB6.exe and all should work!

    Thursday, June 5, 2008

    C# Progress/Busy Window

    I've been playing around with using a C# progress window. For testing I setup a project that takes three different approaches. I used a simple XML serialisation project as a base, and added in some sleep commands to make the serialisation command take about 10 seconds. The first approach uses a simple form (which doesn't work very nicely), then second uses a simple form with a background thread, and the third uses a form with a cancel button and a progress bar with a background thread.

    The upshot is, that you need to use a BackgroundWorkerProcess. You need to encapsulate the stuff you want to do in the background into a single function; you need to identify where your bottleneck is. You send your job, that is going to take some time, to a background worker thread, and that keeps the UI responsive; it makes sure that your "I'm busy right now" window stays refreshed. There are two key things you need to do to get this to work:

    1. Make sure that your work to be done in the background, doesn't try to touch the user interface. If you need to show progress then you can use the backgroundWorker.Progress method. Your background process should do its work and then return the data (via e.Result) which can then be populated onto the form if required.

    2. Make sure you lock down the UI when you launch the work. The UI will remain active and you don't want the user to open a new screen or start the same update again etc, so you need to disable these buttons as required.

    One thing you might want to do if you are waiting on a single network request (and therefore can't really determine % complete for a progress bar) is to display an animation - like some whirring cogs or some papers shuffling.

    Wednesday, June 4, 2008

    C# Converting a Class to XML

    Using a class in C# to hold data is the right idea. XML is just a transport mechanism and should only be used at the boundaries of your application. So, as soon as you grab a chunk of XML from somewhere you should turn it into a class. Similarly, when you want to send some data somewhere, encapsulate the data in a class, and then serialise the class to XML. This way, if you decide to get rid of XML and use INI files, or CSV files, or YAML files then you don't need to change your core application - you only change the routines that serialise and deserialise the class.

    Q. How to easily turn a class into XML?

    A. For a simple class setup, you only need about four lines of code. For a user-defined class called "cls":


    XmlSerializer serializer = new XmlSerializer(cls.GetType());
    StringWriter writer = new StringWriter();
    serializer.Serialize(writer, cls);
    string xml = writer.GetStringBuilder().ToString();

    Tuesday, June 3, 2008

    CenterParent for a non-Modal Form in C#

    I'm using a non-modal form to display a progress/busy/working dialog while a form is busy doing something. Unfortunately, the StartPosition=CenterParent, which works fine via Form.ShowDialog(), doesn't seem to work when a form is display via Form.Show(). I get around this by centering the form in the form_load method via:


    private void frmBusy_Load(object sender, EventArgs e)
    {
    // centerparent not workin for a ".Show" form,
    // so set position manually.
    this.Location = new System.Drawing.Point(
    this.Owner.Location.X +
    (this.Owner.Width - this.Width) / 2,
    this.Owner.Location.Y +
    (this.Owner.Height - this.Height) / 2);
    }

    Friday, May 23, 2008

    iPhone Screenshots

    The iPhone has a built in screen shot app.

    Activating the screenshot function is a matter of editing ~/Library/Preferences/com.apple.springboard.plist so that it contains a Boolean value called SBMobileScreenshotr that is set to true (notice the strange r at the end, not a typo!). I used sftp, then the Property List Editor on the Mac, then sftp'ed it back. Then restart the SpringBoard.

    Use the following super-secret key combination: Hold down the Home key and toggle the mute switch. Your screen flashes white, a screen shot appears on your camera roll.

    I pasted some of this info directly from here and here. Very handy!


    Tuesday, May 20, 2008

    Sending an SMS to the WM5 Emulator

    If you need to send a TXT message to your WM5 emulator, then, from the emulator, send a message to the number 0010001. Immediately after the TXT is sent, it will be sent back to your phone as if it came from that number.

    For other handy emulator numbers, check here. Here are some of the special numbers mentioned:


    Voice numbers: 7272470, 7272979, 7272620, 7272917, 7272688, 7272263, 7274390, 7274388, 7274386, and 7274389

    Data numbers: 7272455, 7272931, 727343, and 7273432

    Always busy: 7272024

    Never answer: 7272773

    Emergency: 911, 112, 08, 999

    SMS: 0010001 0010002

    Tuesday, May 13, 2008

    Creating a COM Proxy in .Net

    I've got a VB6 COM component that is being used by lots of other applications: VB6 apps, Word Add-ins, Outlook add-ins, Outlook forms, VBScript libraries etc. I want to upgrade this to .Net so that (a) we are reducing our dependence on VB6, and (b) we can start using some of the cool new .net capabilities and (c) new .Net components can talk to this component via standard .Net methods rather than using COM.

    The idea is to break this component into two separate .Net components. The first will be a proxy component which implements all of the original methods and properties from the VB6 component, and implements the same COM interface, so it can be swapped in for the old component. It won't do any work, but instead will invoke methods on the second component to do the work. The second .Net component will be a .Net assembly written in the proper .Net fashion that does all of the work that the old VB6 DLL used to do. New .Net applications can make calls directly into the second component and by-pass the COM interface.

    So how do we create this COM facade? The way I ended up doing it was to extract the type library from the VB6 component, generate a .Net assembly that defines the interfaces in CLR metadata, and then created a .Net assembly to override the interfaces.

    vb6-to-net-typelibrary-dll.jpg

    The steps for this are:

    1. On a machine which has your VB6 COM component registered and Visual Studio 6.0, fire up the OLEView tool.

    2. Find your VB6 component. I looked under "Automation Objects" for the ProgId which is the "MyComp.ClassA" you use when you do a CreateObject("MyComp.ClassA") in VB6.

    3. Select "Object-View Type Information" to display the "ITypeLib Viewer". Your IDL is in the right-hand pane.

    4. Save the IDL by clicking the save button. You now have a "MyComp.IDL" file.

    5. Fire up the VS2008 command prompt and navigate to your IDL file.

    6. Type "MIDL MyComp.IDL" to generate a type library. I needed to reorder some of the things in the IDL to get it working. You will now have a "MyComp.TLB" file.

    7. Create your .Net assembly by typing "tlbimp MyComp.Tlb /out:MyCompTlbAssembly.dll". This will generate a "MyCompTlbAssembly.dll" file. This is a .Net assembly that defines your interface. Now we need to override it and implement our new code.

    8. Create a new .Net Class project. I called mine "MyCompProxyNET".

    9. Add a reference to your recently create "MyCompTlbAssembly.dll" by using "Project-Add Reference" and then going to the "Browse" tab and finding your dll.

    10. Create a class for the first class from your VB6 component that you want to override, eg ClassA. I called my class "ClassAProxy".

    11. Add an import of the System.Runtime.InteropServices, set your prog id to match the original VB6 prog id, and implement your interface. So you have something like this:


    using System;
    using System.Runtime.InteropServices;

    namespace MyCompProxyNET
    {
    [ProgId("MyComp.ClassA")]
    public class ClassAProxy : MyCompTlbAssembly.ClassA
    {
    }
    }


    12. Now implement the interface for this class. VS2008 will generate stubs for you if you right click the interface ("MyCompTlbAssembly.ClassA" in this case) and choose "Implment Interface". Nice!!

    13. Almost there, now we just have to make this COM visible. Under "project-Properties-Application-Assembly Information", check the "Make assembly COM-Visible" box, and "OK". Under "Project-Properties-Build" check the "Register for COM Interop" box.

    14. Build your solution. Your .Net assembly is now the registered component for that ProgId. You can verify this by looking up the ProgId in the registry under HKEY_CLASSES_ROOT and following the Clsid to the HKEY_CLASSES_ROOT/CLSID/{your clsid}. You can see that the name of the .Net component will be displayed in the "InProcServer32" key. Previously your VB6 dll would have shown up in here.

    Run one of your apps that used the old VB6 component and you should get a nice "The method or operation is not implemented" error message. This error is thrown by the .Net stubs you created in step 12. Just put some real code in your stubs and away you go!

    Monday, April 28, 2008

    Jim Webber = SOA - ESB

    Just listened to a classic talk from Jim Webber about SOA and ESB. Jim Webber is a big name in the SOA space and is coming to Australia shortly to talk at the JAOO conference. In this talk he explains why SOA is good and why ESB is bad. It is very thought-provoking and entertaining. He thinks that your integration should be web services, but you don't want a big proprietary piece of middleware sitting in the middle of your enterprise.

    http://www.infoq.com/presentations/webber-guerilla-soa

    My favourite quote is his response to a question about orchestration at the end:

    Orchestration is just a programming language. So if you want to build a service using a visual orchestration language, you're entirely entitled to do that. Provided you don't pollute the rest of the service ecosystem, that's great. I caution you that probably Java, C# or VB, or for heavens sake Haskell, will be a more productive environment for you to work in than sticks and boxes and lines - but if you're insistent you want to do that, that's great. Just don't think that you can draw a picture and then hit the button and get deployed into production. Because that's crack smoking fantasy time!

    Tuesday, April 15, 2008

    Go To Definition shortcuts

    Across the IDEs I'm using at the moment, the "Go To Definition" short-cut (drill-down on method to see the code) is:


    • Eclipse: F3

    • VB6: Shift-F2

    • Visual Studio: F12 re-mapped to use the VB6 settings because F12 gives the Dashboard on Mac


    Monday, April 14, 2008

    Log4j Root Logger

    I just found out that with log4j I can set-up a "root" logger. This is a catch-all logger that writes everything, regardless of the package. Seems pretty obvious, but I hadn't seen it before. If you use this in conjunction with a package logger, and you are outputting to the same file, then use the additivity="false" attribute on the package logger to stop lines appearing twice. My example log4j.xml file is below. Note that I output everything to stdout when developing, and then switch over to the rolling log file when I deploy it.


    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
    <!-- Print the date in ISO 8601 format -->
    <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
    </layout>
    </appender>
    <appender name="file" class="org.apache.log4j.RollingFileAppender">
    <param name="file" value="/var/tmp/logs/myapp.log" />
    <param name="MaxFileSize" value="10000KB" />
    <param name="MaxBackupIndex" value="3" />
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
    </layout>
    </appender>
    <logger name="org.apache.fop" additivity="false">
    <level value="info" />
    <appender-ref ref="stdout" />
    </logger>
    <logger name="com.example" additivity="false">
    <level value="debug" />
    <appender-ref ref="stdout" />
    </logger>

    <!-- root logger -->
    <root>
    <level value="info" />
    <appender-ref ref="stdout" />
    </root>
    </log4j:configuration>


    Friday, April 11, 2008

    File Transfer between Machines using Remote Desktop over Cisco VPN

    I access my work PC using Remote Desktop over a Cisco VPN. The VPN shuts down internet access on my PC while it is connected. I needed to get a large file from my work PC to my home PC over the VPN. For small files I'll use email with attachments but this doesn't work with bigger files due to size limits. The solution was to set-up a FTP Server on my home PC and use ftp from my work PC to put the file onto my home machine.

    Setting up an FTP server on my home Windows XP machine was pretty straight-forward. It can be done through Add/Remove Programs, selecting Windows Components and then drilling down into IIS. Follow these steps here for more details. I set mine up with anonymous write access to keep it simple.

    Once the ftp server was installed, I connected to my work PC went to the command prompt. The "ipconfig" command revealed the IP address of the VPN. It was then a matter of using "ftp" to connect to that IP address and put the file across. Use a login of "ftp" for anonymous access.

    The only other thing to remember was to shutdown the ftp server (along with anonymous access privileges) at the end of it all. Don't want that running with anonymous write access all the time!

    Thursday, April 10, 2008

    Source Control under JumpBox

    I've been doing .Net and VB6 development under Parallels on my Mac, and using Subversion mounted on a shared Parallels folder for source control (see this post). This gave me regular time machines backups...but the performance sucked! Checking at the trunk would take forever! And, of course, my Mac development work (Java, Cocoa, Grails etc) couldn't use the same repository.

    So I've moved to Plan B - a Trac Jumpbox. This runs under Linux on Parallels and takes pretty much no set-up at all. You download it, fire it up and on the Trac home page you'll see the URL to use for Subversion access. I can now use this repository for both my Mac development and my Windows (parallels) development and it is all stored on the same machine. Plus I get the Trac source control viewer, diff tools etc which I've always loved (and a Wiki and bug management tool which I'm not going to use). It all runs right here on this machine, so I can do my dev work and check stuff in, without internet access.

    There are a few hassles with this solution, so it might not be the final answer:

    1. I need to remember to start-up the Trac Jumbox when I need source control. I could have it auto-start but I'm a big fan of a quick-starting machine. That's why I went to Mac in the first place! And I need to shut it down when I've finished.

    2. When I'm doing Windows dev work I now have two virtual machines running. This performs fine as long as I don't sleep the machine. Waking it up is patchy at best and on several occasions I've had to reboot the Mac to get it working properly again. (Forget Fusion - I've tried it and I didn't like it!)

    3. Time Machine doesn't back up virtual machines well (it backups the whole machine up every time), so it doesn't back my source control up. So I'm only backing up once a week via SuperDuper. This is not ideal!

    4. This doesn't help me to develop on other machines in the house, or off-site. Should I be using a server, or perhaps hosted source control?

    We'll see.

    Monday, March 3, 2008

    Remote Work and Communication

    I've been working remotely, doing primarily development and architecture work, for the past 24 months. During this time I've used quite a few different communication mediums, each of which had their strengths and weaknesses.

    Emails are the primary way of communicating. They are great for keeping track of what was said, clearly thinking out and stating a point, and useful for managing work.

    Instant Messaging (IM) is very useful for a quick back and forth discussion: "how do you do X?", "can I checkout Y?", " I can't access Z, is it down?" Again, you have a full searchable history of what's been said which is very useful. If you find yourself chatting for more than 30 mins then you should probably have made a phone call. You also need to be careful not to slip into idle chatting which can be counter-productive (for both parties!). You can however, answer in your own time and you can flag yourself as busy if you don't want to be disturbed. Also, Skype client offers encryption which keeps everything safe.

    Voice/Skype Sometimes a 3-day email back and forth could be easily solved by a 10 minute phone call, particularly something like talking though a business requirement with a BA. And it is good in the remote development world, to have a regular voice conference call/team catch-up. I used Skype because it is free, and free is good!

    Collaborated Documents I have started using this with my "To Do" spreadsheet via Google Docs. It is not yet proving super-beneficial but I think that over time, once we adjust to think this way, it will offer a lot and save a lot of to-ing and fro-ing of document versions. I've used collaborated docs to good effect with bug lists.

    Wikis These are super-useful. Typically have one per project. Source control, all project documentation, bug lists, project "how-tos" etc. People end up using it because they are just so darned easy to update! All those 1/2 to 2 pages documents end up being written in here. If your team are slack at documenting then try introducing a wiki! My only gripe (as a team member) is that it is not so easy to throw it on a flash drive and take it away with you when you leave.

    Twitter Played with this for status updates but it didn't stick.

    Thursday, February 28, 2008

    Can't find your .Net dll in the VB6 Project References list?

    I had created a new .Net Dll and done everything right, but couldn't for the life of me find the DLL in the VB6 references list. Everything looked fine in the registry with the HKEY_ROOT/CLSID and HKEY_ROOT/TYPELIB stuff there OK with the right Guids etc.

    Normally you find it in the References list under the dll name eg. MyDll.dll would be called "MyDll" in the list. BUT, if you put a description in the .Net project file (Properties-Application-Assembly Information, the "Description" box), then that is the name that will appear in the References list, and also the name that will we shown under the Type Library list in the OLE View tool.

    Moral of the story? Don't use the .Net Assembly Description field for COM compatible DLLs! Damn, that cost me hours!

    Wednesday, February 27, 2008

    Moving your svn repository to a Parallels Shared Network Drive on the Mac

    I'm running svn under windows as a Windows Service on my Parallels virtual machine (not using Apache). I wanted to move the repository from my local c:\ drive to a shared Parallels folder to take advantage of TimeMachine backups of my svn repository. I ended up deleting and recreating the SVN service. These are the steps:

    1. Find the service name. Look in the services console. Find the display name of the service in there. Then, from the command prompt, use the "sc query" command to list all of the services and find the matching display name.

    2. Check the current config, especially the binary path to the snvserve exe.


    C:\Documents and Settings\Administrator>sc query svnrepos
    SERVICE_NAME: svnrepos
    TYPE : 10 WIN32_OWN_PROCESS
    STATE : 4 RUNNING
    (STOPPABLE,NOT_PAUSABLE,IGNORES_SHUTDOWN)
    WIN32_EXIT_CODE : 0 (0x0)
    SERVICE_EXIT_CODE : 0 (0x0)
    CHECKPOINT : 0x0
    WAIT_HINT : 0x0

    C:\Documents and Settings\Administrator>sc qc svnrepos
    [SC] GetServiceConfig SUCCESS
    SERVICE_NAME: svnrepos
    TYPE : 10 WIN32_OWN_PROCESS
    START_TYPE : 2 AUTO_START
    ERROR_CONTROL : 1 NORMAL
    BINARY_PATH_NAME : "C:\program files\subversion\bin\svnserve.exe" --service -r "c:\svn"
    LOAD_ORDER_GROUP :
    TAG : 0
    DISPLAY_NAME : Subversion Repository Local
    DEPENDENCIES : Tcpip
    SERVICE_START_NAME : LocalSystem


    3. Delete the service.


    sc stop svnrepos
    sc delete svnrepos


    4. Create the new service with the new file system path (don't forget the spaces after the '=' signs and the \ before the nested quotes). It is important to use the UNC path to the shared Parallels folder and not the drive letter, because the drive letter will not work! I found the UNC path by clicking "Map Network Drive" under explorer and looking in the drive dropdown. A quick test of "dir \\.psf\osxpshared" will tell if you got it right.

    sc create svnrepos binpath= "\"c:\program files\subversion\bin\svnserve.exe\" --service -r \"\\.psf\osxpshared\svn\"" displayname= "Subversion Repository" depend= Tcpip start= auto

    If you get the mysterious "service is marked for deletion" message then make sure you have closed the services console. This can keep a handle to the old service and stop it from being deleted.

    5. Start the new service.

    net start svnrepos


    6. Check it works.

    svn info svn://localhost

    Thursday, February 21, 2008

    How much can you change your WSDL before you need to recompile your .Net consumer?

    If you have a .Net application that is consuming a web service, via a web reference, how much does the WSDL have to change before your compiled application will break? I set up a simple .Net web service and tried the following:


    • Added a new method. OK

    • Removed a method (not the one we are calling). OK.

    • Modified the method by changing the argument name. Method call still worked but no data was passed in.

    • Modified the method by adding an additional parameter. Method call still worked but new parameter was not set (obviously). Original parameter was still passed through.

    • Modified the method by removing a parameter. Method call still worked!

    • Modified the method changing the parameter type from string to int. Method FAILED with format error.

    • Modified the method changing the return type from string to int. Method call still worked.



    Then I tried nested objects as parameters (we use these a bit):

    • Modified the method by changing the argument name of one of the nested objects parameters. Method call still worked but no data was passed in that parameter; all other params still populated.

    • Modified the nested object by adding an additional parameter. Method call still worked but new parameter was not set (obviously). Original parameters still passed through.

    • Modified the method by removing a parameter. Method call still worked!



    So a web service call is pretty forgiving. We can change most of it without a recompilation being required.

    Tuesday, February 19, 2008

    .Net 2.0 and calling dynamic Web Services

    It is really easy to use a web service in .Net 2.0. You just add a "Web Reference" to you project and point it to your WSDL URI. A proxy class is generated that provides you with methods that line up with the methods available from your web service.

    OK, but I want this to be dynamic: I want to deploy this to my production environment which uses a different web service address, without recompiling it.

    Easy, you just make sure that your web reference has the "dynamic" property set and an app.config will be added to your project. Take a look at it. Now you can change the access point via the app.config file. So you just deploy a different app.config file in production. If you are using DLLs and COM interop you can have a lot of fun trying to get the config files loading (see here). If needs be, you can roll your own config file and set the access point URL manually before invoking the web service via the ".URL" method on the web proxy.

    OK, but I want this to be really dynamic: I don't know the WSDL at compile-time, all I know is the method signature; I want to provide the WSDL at run-time.

    It starts to get a bit ugly now. But if you know the method signature, eg:

        public string HelloWorld(string inArg)


    then you can define an interface class which you can use in your code at compile time. Then, at run-time, you can look up the WSDL, generate assembly source code from it, compile the assembly and have that assembly implement your interface. Google is your friend here. If found a few examples but this implementation of a WebServiceProxyFactory is pretty darned useful. Note that it does take time to build and compile this proxy assembly - you probably won't be wanting to do this every time, so you may have to implement some kind of proxy-caching.

    Hmmm, OK, but I want this to be really, REALLY dynamic: I don't even know the method signature at design time - I want to examine the WSDL and work out which one to use.

    Yeah right! Maybe you can get the user to write the code? But seriously, there are times when you might want to call a web service that hasn't even been designed yet - maybe you are deploying your app to a number of clients and allowing them to hook in their own web services calls? It is time to read your SOAP manual. You're going to have to grab the WSDL, interpret it, find the method you want to call, build a soap envelope with your values inserted in the right places, and make the web service call. A quick google of HttpWebRequest and SOAP should get you on the right track. This is no trivial task!

    Wednesday, February 13, 2008

    How to read an app.config setting for your DLL from your dll.config file

    It turns out you can manually read the app.config settings for your DLL, using the ConfigurationManager.OpenMappedExeConfiguration. This is pretty darned ugly, but it works. It will look in the location of the currently executing assembly (ie. you dll's location) for the file "{dllname}.config". You still need to manually deploy the config file to this location - the setup package only deploys the EXE config file.

    To get the code working, first add a reference to the .Net "ConfigurationManager" to your project, then add "using System.ConfigurationManager" to your class.

    public string getOpenedConfigValue(string keyName)
    {
    string codebase =
    System.Reflection.Assembly.GetExecutingAssembly().
    CodeBase;
    Uri p = new Uri(codebase);
    string localPath = p.LocalPath;
    string executingFilename =
    System.IO.Path.
    GetFileNameWithoutExtension(localPath);

    string sectionGroupName = "applicationSettings";
    string sectionName =
    executingFilename + ".Properties.Settings";

    string configName = localPath + ".config";
    ExeConfigurationFileMap fileMap =
    new ExeConfigurationFileMap();
    fileMap.ExeConfigFilename = configName;
    Configuration config =
    ConfigurationManager.OpenMappedExeConfiguration(
    fileMap,ConfigurationUserLevel.None);
    ConfigurationSectionGroup group =
    config.GetSectionGroup(sectionGroupName);
    ClientSettingsSection section =
    (ClientSettingsSection)group.Sections[sectionName];
    SettingElement elem = section.Settings.Get(keyName);

    if (elem == null)
    return "";
    else
    return elem.Value.ValueXml.InnerText.Trim();
    }

    App.config in C# with VS2005 - Part II: DLL within an EXE

    In part I I looked at using the app.config from an EXE. In part II I'm going to look at using app.config from a DLL that is called by an EXE.

    So, create a DLL project (mine is called "TestAppConfigDll") and add it to your EXE project. Use the Settings designer again to add your app.config file to your DLL (right-clicking on the project, selecting "properties" and then choosing the "settings" tab). I'm going to add an "Application" setting called "MyDllVal". Again we get a pretty similar looking app.config file in our project:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <configSections>
    <sectionGroup name="applicationSettings"
    type="System.Configuration.ApplicationSettingsGroup,
    System, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089" >
    <section
    name="TestAppConfigDll.Properties.Settings"
    type="System.Configuration.ClientSettingsSection,
    System, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089"
    requirePermission="false" />
    </sectionGroup>
    </configSections>
    <applicationSettings>
    <TestAppConfigDll.Properties.Settings>
    <setting name="MyDllVal" serializeAs="String">
    <value>18.2</value>
    </setting>
    </TestAppConfigDll.Properties.Settings>
    </applicationSettings>
    </configuration>


    And again, we can access the value in code using:

    string myDllVal = 
    TestAppConfigDll.Properties.Settings.Default.MyDllVal;


    No surprise to find that the config file in our bin folder (..\TestAppConfigDll\bin\Debug) is prefixed by the dll name, "TestAppConfigDll.dll.config" in my case. BUT looking in the bin for the EXE project (which references the dll and contains a copy of my dll file), there is no dll.config file, just the original exe.config. And the exe.config has no "MyDllVal" property. If I run the EXE it picks up the original value for "MyDllVal" but I have no way to modify it! It knows the original compile-time value because it has been hard-coded into the "Settings.Designer.cs" file that was generated when you entered your value via the Settings designer. Here is the code if you are interested (my original value was "18.2"):


    [global::System.Configuration.
    ApplicationScopedSettingAttribute()]
    [global::System.Diagnostics.
    DebuggerNonUserCodeAttribute()]
    [global::System.Configuration.
    DefaultSettingValueAttribute("18.2")]
    public string MyDllVal {
    get {
    return ((string)(this["MyDllVal"]));
    }
    }


    So, what if I add a dll.config ("TestAppConfigDll.dll.config") under the EXE bin folder - will it read it on start-up? Nope - it doesn't read it.

    So, maybe it is looking in the exe.config for my setting. What if I merge my "MyDllVal" setting into the exe.config file - will it read it on start-up? Wooeee! That works! So I had to take two chunks of xml from the dll.config to the exe.config; (i) the "section", and (ii) the "{dllname}.Properties.Settings". The resulting exe.config looks like this:


    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <configSections>
    <sectionGroup name="applicationSettings"
    type="System.Configuration.ApplicationSettingsGroup,
    System, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089" >
    <section
    name="TestAppConfig.Properties.Settings"
    type="System.Configuration.ClientSettingsSection,
    System, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089"
    requirePermission="false" />
    <section
    name="TestAppConfigDll.Properties.Settings"
    type="System.Configuration.ClientSettingsSection,
    System, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089"
    requirePermission="false" />
    </sectionGroup>
    </configSections>
    <applicationSettings>
    <TestAppConfig.Properties.Settings>
    <setting name="MyVal" serializeAs="String">
    <value>17</value>
    </setting>
    </TestAppConfig.Properties.Settings>
    <TestAppConfigDll.Properties.Settings>
    <setting name="MyDllVal" serializeAs="String">
    <value>18.00 combo</value>
    </setting>
    </TestAppConfigDll.Properties.Settings>
    </applicationSettings>
    </configuration>


    So the upshot is, if the DLL is called from an EXE, then the DLL will try to get its app.config settings from the EXE's app.config file. Even if I remove the EXE's app.config, the DLL does not use the DLL's app.config file.

    Man, this is annoying! Why have they done it this way? Maybe it is so that shared DLLs in the GAC can have different config settings for different EXEs. But even then, couldn't they have used the EXE config settings first, and if none, then use the DLL config settings?

    So where will it look for app.config if I have a .Net DLL in the GAC that is begin called from a VB6 exe via COM interop? Probably nowhere I guess.

    The solution seems to be to use a custom config file for your DLL app settings. However my DLL is using a web-service and the .Net web service tool puts the web service URI into, you guessed it, the app.settings file for the DLL. I want access to the app.config so I can change the web service URI to point to a different web service. Again, I can get around it by setting the web service URI manually in the code, but I'm starting to fight the framework rather than use it!

    Newline in a .Net textbox (C#)

    Always forget this one - to put a new line in a .Net textbox in C#, use the "\r\n" string (after you've set the textbox to multiline of course). "\t" is tab but that is easy to remember!

    App.config in C# with VS2005 - Part I: EXE

    Using the app.config has been giving me a few headaches. And googling it has revealed many others have the same kind of problems! So here is an overview of how it works:

    From a Windows form project (mine is called TestAppConfig), you can add the app.config setting by right-clicking on the project, selecting "properties" and then choosing the "settings" tab. Add an "Application" setting into the settings designer (eg "MyVal") and you will find an "app.config" file has been added to your project. It will contain something like this:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <configSections>
    <sectionGroup name="applicationSettings"
    type="System.Configuration.ApplicationSettingsGroup,
    System, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089" >
    <section
    name="TestAppConfig.Properties.Settings"
    type="System.Configuration.ClientSettingsSection,
    System, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089"
    requirePermission="false" />
    </sectionGroup>
    </configSections>
    <applicationSettings>
    <TestAppConfig.Properties.Settings>
    <setting name="MyVal" serializeAs="String">
    <value>15</value>
    </setting>
    </TestAppConfig.Properties.Settings>
    </applicationSettings>
    </configuration>


    From your code, you can access this setting like this:

    string myval = 
    TestAppConfig.Properties.Settings.Default.MyVal;


    If you take where the code has been built (eg. ..\TestAppConfig\bin\Debug), you'll see that the app.config has been created using the name of your exe, in my case TestAppConfig.exe.config. If you run the exe from this folder it will use the value from this config file. If you change the value in the config it will be picked up the next time that the application is started - it does not pick it up changes immediately!


    If you need to get the changed value picked up at run-time, you can use the reset method:

    TestAppConfig.Properties.Settings.Default.Reset();

    Wednesday, February 6, 2008

    Distributing a .Net DLL for use by VB6

    The trick is getting the .Net assembly installed into the GAC on your 500+ remote machines. You can do this manually by copying the file into c:\windows\assembly but this won't work via remote scripting. You can use the "gacutil /i {assembly.dll}" but the GacUtil.exe is no longer distributed with the .Net framework (since 2.0) so you have the have the SDK or Visual Studio installed on the client machine - no thanks!

    The easiest way is to create an MSI to install the DLL. In fact, this will also copy it and register it, so it saves a few steps; plus you get the uninstall stuff with it. To do this:

    1. In your project, add a new "Setup" wizard. Choose "Windows application".

    2. Select the "Primary Output" for your DLL project.

    3. By default this will install the DLL in your application folder. To install in the GAC, right-click the "File System" in the setup project and choose "Add Special Folder-Global Assembly Cache Folder". Then right click in the new folder and add the primary output of your DLL project.

    4. Make sure you set the "Product name" and "Company name" in the properties of the setup project. These are used to build the path to you application folder where the DLL will be copied:
    [ProgramFilesFolder][Manufacturer]\[ProductName]


    4. Build the Setup project and you'll find a {assemblyname}.msi file sitting in the ".../release/bin" path under your project.

    5. Deploy the MSI to your remote machine and run it. Hopefully you have some kind of application deployment tool that can run MSI installs!

    Monday, February 4, 2008

    Steps to make your .Net DLL useable from VB6

    In order to use a .Net DLL from VB6 I had to take the steps below. This assumes that you have already created a .Net dll with a public method. Before you begin, in VS2005, build your project and then go into the "Project-References" box in your VB6 application. At this stage you will not be able to see your .Net DLL.

    Step 1. Make it COM-visible.

    Right click on the project In VS2005 and select "Properties". Under the "Application Tab" click the "Make assembly COM-Visible" checkbox. Then click on the "Build" tab and check the "Register for COM interop" checkbox in the "Output" section. Save.

    If you try and build the project now you will find that you can reference the project in VB6, and can instantiate it in code, but you cannot view the methods in the Object Browser or get VB6 IntelliSense in the methods. You can call the method and get your result back though.

    Step 2. Implement an interface.

    Your class should implement an interface. Convert your class from this:

        public class MyClass
    {

    public String testInOut(String sIn)
    {
    return ret = "Returning [" + sIn + "] at " + System.DateTime.Now.ToString();
    }
    }


    to this:

        public interface IMyClass
    {
    String testInOut(String sIn);
    }

    public class MyClass : IMyClass
    {

    public String testInOut(String sIn)
    {
    return ret = "Returning [" + sIn + "] at " + System.DateTime.Now.ToString();
    }
    }


    Or, even easier, select the class and method definition and right-lick, choosing "Refactor-Extract Interface..." and check the method in the box that pops up. This will generate an interface into a new file and modify your class to implement it.

    Step 3. Expose your methods.

    Get .Net to expose your methods to VB6. Do this by adding the [ClassInterface(ClassInterfaceType.AutoDual)] attribute to the public class, and importing the InteropServices library:

    ...
    using System.Runtime.InteropServices;

    namespace MyNamespace
    {
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class MyClass : MyProject.IMyClass
    {


    If you rebuild your .Net dll now, you should be able to see the methods in the VB6 Object Browser. Now we need to make sure we can deploy it remotely. Follow these additional steps.

    Step 4. Control your GUIDs.

    Add GUIDs to both the Interface and the public class. To get a GUID, click "Tools-Create GUID" and choose option 4 "Registry Format". Click "New GUID" and then "Copy". Paste as a "Guid" attribute into your interface, stripping out the curly brackets. Then generate a new GUID and paste an attribute into your public class. The GUID against the public class is the key one because it is the one that is looked up in the registry to determine which DLL your program will use. Specifying the GUIDs in these attributes ensure that the same GUID is used when you compile your build.

        [Guid("3A7E8E37-3B6B-4cda-9A47-EBD0D1D11812")]
    interface IMyClass


    and

        [ClassInterface(ClassInterfaceType.AutoDual)]
    [Guid("87E9EBBD-CE79-4336-BB7F-F070483C442C")]
    public class MyClass : MyProject.IMyClass


    Step 5. Sign the assembly with a strong name.

    Go into the "Project-Properties-Signing" tab and select "Sign Assembly" and choose the "string name" combo entry.

    Now we are read to deploy this remotely. Follow the steps below on the remote machine:

    Step 6. Install into the GAC.

    Copy the file to the remote machine (which already has the .Net framework installed) into some directory {myfolder}. Then copy it into the c:\windows\assembly folder. This is the location of the GAC (Global Assembly Cache). .Net applications on this machine will now be able to use it. However VB6 will still not be able to find it.

    Step 7. Register the DLL.

    Register the DLL with COM via the regasm tool:

        regasm c:\{myfolder}\MyProject.dll


    The regasm tool is installed as part of the .Net framework and can be found here:

        C:\WINDOWS\Microsoft.NET\Framework\v2.0.5072


    This will register the type library. If you try to use the COM object via VB6 it will pick up the version from the GAC. The version you have copied to {myfolder} can be deleted if required. And that is it!

    Sunday, February 3, 2008

    Types of web widgets

    There are three basic types of web widgets - javascript, iframe and flash. Each has pros and cons.

    1. Javascript. <script ../>

    Fetched via XHTTP script request, return JSON. Widget is inserted by direct manipulation of the HTML document (DOM). Pros: Allows dynamic sizing and multi-threading, and static monitoring. Possible issues with cross-site scripting. Cons: Uses complex technologies (JavaScript, DOM, JSON/XML, DOJO/pototype/jQuery/DWR/...). Allowed by most (but not all) third party sites.

    2. IFrame. <iframe ... />

    Fetched via HTTPRequest, returns HTML. Widget is inserted into fixed-size window on parent document. Widget is just another HTML page. Pros: Uses simple technologies, cross-site scripting is fine. Cons: fixed size, tricky communicating between documents, distribution difficult

    3. Flash. <embed ... />

    Fetched via HHTP resource request, returns WF/binary file. Pros: powerful graphics, run on sites that don't allow JavaScript (eg. MySpace). Cons: expensive tools, trickier to tweak.

    Sunday, January 27, 2008

    Posting XML in your blog?

    It can be a pain escaping the '<' and '>' characters in an xml fragment so that it looks ok in your blog. You can paste your xml into this screen to get it escaped for you:

    http://michaelhanney.com/tools/escape.html

    Thursday, January 24, 2008

    Add a quartz job to Spring


    There are a few steps to adding a quartz job to a Spring app:

    1. Add the jars: quartz-1.6.0.jar, commons-logging.jar, commons-collections.jar. If also found I needed jta.jar for my cron trigger job.

    2. Create your job class which is a QuartzJobBean, implementing the executeInternal method:

    public class MyJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context)
    throws JobExecutionException {

    System.out.println("my job is running");
    }
    }

    3. Add the quartz config into the spring web context xml. There are three parts:

    3a. Add the job bean:

    <bean name="myJob"
    class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass" value="com.test.MyJob" />
    </bean>

    3b. Add the trigger - I'm using a cron trigger which will run this job every 15 seconds:

    <bean name="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail">
    <ref bean="myJob"/>
    </property>
    <property name="cronExpression">
    <value>0/15 * * * * ?</value>
    </property>
    </bean>

    3c. Add the quartz factory, and name the trigger:

    <bean
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
    <list>
    <ref bean="myTrigger"/>
    </list>
    </property>
    </bean>

    Compile. deploy and run!

    If you want to add Spring injected classes into your job, use the jobDataAsMap property. In the example below myBean and myString have getters and setters in the MyJob class.

    <bean name="myJob"
    class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass" value="com.test.MyJob" />
    <property name="jobDataAsMap">
    <map>
    <entry key="myBean" value-ref="beanName" />
    <entry key="myString" value="some value" />
    </map>
    </property>
    </bean>

    That's it!