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!

Monday, November 5, 2007

Bounce apache

I don't bounce apache enough to remember the command, so here it is:

/usr/local/apache2/bin/./apachectl restart

Tuesday, October 9, 2007

Broken pipes with Tomcat and DBCP connection pooling

Getting "broken pipe" errors showing up in your tomcat logs? Happening every 8 hours or so?I had this problem with MySQL 4.1.21 and I've managed to fix it.

(NB. if you are running 5.0.45 MySQL then you'll get a "com.mysql.jdbc.CommunicationsException: Communications link failure" error which is a symptom of the same problem)


A quick search of the web revealed that lots of people are getting these and there are LOTS of suggested solutions. The most common explanation is that your MySQL connections are getting terminated by MySQL before they have been released by the connection pool. Then the next time you try to get a connection from the pool, you'll get a dead connection which will give a "broken pipe" error.

The first step is to try and reproduce it, without waiting around for 8 hours!

I checked the MySQL timeout setting via "mysql> select @@global.wait_timeout". Mine was set to 28,800 which is, not suprisingly, 8 hours. I then reduced this to one minute by:
  • stopping MySQL ("/usr/local/bin/mysqladmin shutdown"),
  • modifying the /etc/my.inf file and adding a "wait_timeout=60" under the "[mysqld]" heading and saving,
  • restarting MySQL ("/usr/local/mysql/mysqld_safe")
Don't worry if your session timeout hasn't changed ("mysql> select @@session.wait_timeout") - it doesn't seem to affect your terminal session. Your global session will have changed, and new connections will use the global setting.

Next I started my application and saw my connections appear (via "mysql> show processlist;") and then disappear at the 60 second mark. Trying to use the app again resulted in the broken pipe error HOORAY! Retrying the app resulted in one broken pipe error per connection in the pool, then it all works OK.

Now the fix.

Appending ?autoconnect=true to the JDBC driver connection string didn't work.

Killing the idle session before MySQL did was the solution. With the MySQL timeout still set at 60 seconds, I decided to evict idle session after 30 seconds by adding the following to the data source:

minEvictableIdleTimeMillis=30000 (evict after 30 seconds inactivty)
timeBetweenEvictionRunsMillis=10000 (run the evicter every 10 seconds)
numTestsPerEviction=-1 (check every connection)

Now, running the same test above, the connections disappear from the list after about 40 seconds. And retrying the ap just creates a new connection. HOORAY! Fixed!! If you are not happy with your connection pool getting wiped out then you can set "minIdle=n" against the data source. When the items from the pool are evicted, new ones are crested to keep the pool full.

In the end I went for a 6 hour idle eviction, checking very 30 mins.