Monday, December 18, 2006

SQL Server Magazine December Reader Challenge Submission

Back in November I did something I had never done before. I submitted an entry for the monthly "Reader Challenge" contest SQL Server Magazine hosts in their "SQL Server Magazine UPDATE" e-mail newsletter.

I was a little disappointed when I did not win, but was happy to see the winning solution was the same as my own submission. Here is the question for December:

Manoj is a database developer for a company that develops business applications that use SQL Server 2000 as a database server. In Manoj's environment, the database that the applications use contains a table that stores sequential numbers used by various features. The table stores the name of the sequence and the next number. The schema of the table is shown in the following code:

USE tempdb
GO
CREATE TABLE dbo.Sequences ( SeqName varchar(30) NOT NULL PRIMARY KEY, NextNum
bigint NOT NULL DEFAULT 0)
INSERT INTO dbo.Sequences (SeqName, NextNum) VALUES( 'Sequence #1', DEFAULT )
INSERT INTO dbo.Sequences (SeqName, NextNum) VALUES( 'Sequence #2', DEFAULT )
INSERT INTO dbo.Sequences (SeqName, NextNum) VALUES( 'Sequence #3', DEFAULT )
GO


The database also contains a GetNextSeqNum stored procedure, which the applications use to get the next number in a particular sequence. The stored procedure is shown in the following code:

USE tempdb
GO
CREATE PROCEDURE dbo.GetNextSeqNum (@SeqName varchar(30), @NextNum bigint =
NULL OUTPUT)
AS
BEGIN

BEGIN TRANSACTION

SET @NextNum = (SELECT NextNum FROM dbo.Sequences WHERE SeqName = @SeqName)

UPDATE dbo.Sequences
SET NextNum = NextNum + 1
WHERE SeqName = @SeqName
COMMIT TRANSACTION

END
GO


When Manoj tests the stored procedure with concurrent SQL Server connections, he notices that some of the calls encounter deadlocks and fail. Modify the stored procedure logic to help Manoj resolve the deadlock problem.


Here is my solution to the problem:

CREATE PROCEDURE dbo.GetNextSeqNum (@SeqName varchar(30),
@NextNum bigint = NULL OUTPUT)
AS
BEGIN
UPDATE dbo.Sequences
SET @NextNum = NextNum, NextNum = NextNum + 1
WHERE SeqName = @SeqName
END
GO


The above stored procedure both fetches the current and updates the next sequence value in one operation. Testing this updated stored procedure with concurrent SQL Server connections verifies this new stored procedure eliminates the deadlock problem seen in the original stored procedure.

Since the updating and fetching occurs in the same statement, it is safe to remove the "BEGIN TRANSACTION" and "COMMIT TRANSACTION" statements from the stored procedure.

You can read about the winning submission at http://www.sqlmag.com/Article/
ArticleID/94572/sql_server_94572.html
.

Wednesday, November 22, 2006

IE 6 ComboBox shows through DIV bug

I discovered an annoying IE 6 bug the other day that had to do with div areas and combo boxes. Fortunately this bug is not present in IE 7 or Firefox, but since most people are still using IE 6 at this time, it is worth noting.

If you have a div area appear in front of a combo box, the combo box will still be visible even though it should be covered by the div area.

Wednesday, October 11, 2006

VB.NET COM Interface Issues

I am a fan of VB.NET, but sometimes, for no apparent reason, it lacks a feature that its brother C# supports. I recently ran across the following issues that affect VB.NET, but not C#.

Both VB.NET and C# allow you to create classes with COM interfaces. This allows unmanaged code (VB 6, C++, etc.) to interact with your VB.NET or C# class. This can be very useful for environments were you have old legacy code that must be still be maintained, but you would like to do new development in a .NET language.

Issue #1 – No COM Help Strings on Interface Properties
One of the things COM allows you to do is specify a help string for any just about anything in a type library. An easy way to view these help strings is to use the Object Browser in VB 6. (You can get to the Object Browser by pressing the F2 key in VB6.) VB.NET and C# support COM help string through the use of the DescriptionAttribute class. The example below shows how you use this class to add help strings to class methods and properties.

Public Property Name() As String
    <Description("This is a sample property.")> _
    Get
        Return mName
    End Get
    <Description("This is a sample property.")> _
    Set(ByVal value As String)
        mName = value
    End Set
End Property

<Description("This is a sample method.")> _
Public Sub SampleMethod()
End Sub


This is how the descriptions look in the VB6 Object Brower.

Property SampleProperty As String
    Member of VbComExample.SimpleSampleClass
    This is a sample property.

Sub SampleMethod()
    Member of VbComExample.SimpleSampleClass
    This is a sample method.


If you take a look at the library in OLE View, you will see a coclass with the name of your class and an interface with the same name as your class preceded by an underscore (_). My class was named SimpleSampleClass, so the automatically generated interface name was _SimpleSampleClass.

This technique works fine, but this technique is discouraged by Microsoft. In the “.NET Framework Developer's Guide” section of the Visual Studio 2005 help on the page titled “Qualifying .NET Types for Interoperation” the following guideline appears:

- Classes should implement interfaces explicitly.

Although COM interop provides a mechanism to automatically generate an interface containing all members of the class and the members of its base class, it is far better to provide explicit interfaces. The automatically generated interface is called the class interface.


Once again in the “.NET Framework Developer's Guide” section of the Visual Studio 2005 help on the page titled “Introducing the Class Interface” it states:

Caution
Using the class interface, instead of explicitly defining your own, can complicate the future versioning of your managed class.


The following example implements the same class shown in the example above, only this time explicitly defining the COM interface separately:

<ComVisible(True)> _
<Guid(SampleClass.InterfaceId)> _
<Description("This is the interface for the SampleClass class.")> _
Public Interface ISampleClass

    <Description("This will not be used.")> _
    Property Name() As String

    <Description("This is a sample method.")> _
    Sub SampleMethod()

End Interface

<Guid(SampleClass.ClassId)> _
<ClassInterface(ClassInterfaceType.None)> _
<Description("This is a sample COM class.")> _
Public Class SampleClass
    Implements ISampleClass

    Public Property Name() As String Implements ISampleClass.Name
        <Description("Sample property description.")> _
        Get
            Return mName
        End Get
        <Description("Sample property description.")> _
        Set(ByVal value As String)
            mName = value
        End Set
    End Property
    Private mName As String

    <Description("This string will not be used.")> _
    Public Sub SampleMethod() Implements ISampleClass.SampleMethod

    End Sub

End Class


This is how the descriptions look in the VB6 Object Brower.

Property Name As String
    Member of VbComExample.SampleClass

Sub SampleMethod()
    Member of VbComExample.SimpleSampleClass
    This is a sample method.


If you take a look at the library in OLE View, you will see a coclass with the name of SampleClass and an interface with the name ISampleClass.

The descriptions on the Name property are lost because there is no way to specify the DescriptionAttribute on the Get and Set declarations in a VB.NET interface. C# does not have this problem, because it includes the get and set keywords in its interface definitions.

The following is the same example in C#:

[ComVisible(true)]
[Description("This is the interface for the SampleClass class.")]
[Guid(SampleClass.InterfaceId)]
public interface ISampleClass
{
    string Name
    {
        [Description("Name property description.")]
        get;
        [Description("Name property description.")]
        set;
    }

    [Description("This is a sample method.")]
    void SampleMethod();
}

[ClassInterface(ClassInterfaceType.None)]
[Description("This is a sample C# COM class.")]
[Guid(SampleClass.ClassId)]
public class SampleClass : ISampleClass
{


    string ISampleClass.Name
    {
        [Description("This won't be used.")]
        get
        {
            return mName;
        }
        [Description("This won't be used.")]
        set
        {
            mName = value;
        }
    }
    private string mName;

    [Description("This won't be used.")]
    void ISampleClass.SampleMethod()
    {
    }
}


Unlike its VB.NET brother the C# version includes the helper string, even when the interface is explicitly defined. This issue is really just annoying, but the next one actually prevents one from explicitly defining an interface properly.

Issue #2 – Broken Currency Support on Interface Properties
Consider the following property declared in a class with an automatically generated COM interface:

Public Property Money() As <MarshalAs(UnmanagedType.Currency)> Decimal
    <Description("Currency Example.")> _
    Get
        Return mMoney
    End Get
    <Description("Currency Example.")> _
    Set(<MarshalAs(UnmanagedType.Currency)> ByVal value As Decimal)
        mMoney = value
    End Set
End Property


If you lookup the propget and propput methods for this property in OLE View, you will find the following definitions:

[id(0x00000003), propget, helpstring("Currency Example.")]
CURRENCY Money();
[id(0x00000003), propput, helpstring("Currency Example.")]
void Money([in] CURRENCY rhs);


As you can see the help string is successfully captured and the .NET Decimal datatype value is marshaled as a VB6 Currency datatype.

However, if you use the recommended approach and explicitly define your COM class interface, the marshaling will be incorrect for the propput method. The following example demonstrates this problem:

<ComVisible(True)> _
<Guid(SampleClass.InterfaceId)> _
<Description("This is the interface for the SampleClass class.")> _
Public Interface ISampleClass

    <Description("This will not appear")> _
    Property Money() As <MarshalAs(UnmanagedType.Currency)> Decimal

End Interface

<Guid(SampleClass.ClassId)> _
<ClassInterface(ClassInterfaceType.None)> _
<Description("This is a sample COM class.")> _
Public Class SampleClass
    Implements ISampleClass

    Public Property Money() As _
        <MarshalAs(UnmanagedType.Currency)> Decimal _
        Implements ISampleClass.Money
        <Description("Currency Example.")> _
        Get
            Return mMoney
        End Get
        <Description("Currency Example.")> _
        Set(<MarshalAs(UnmanagedType.Currency)> ByVal value As Decimal)
            mMoney = value
        End Set
    End Property
    Private mMoney As Decimal

End Class


If you lookup the propget and propput methods for the Money property of the ISampleClass interface in OLE View, you will find the following definitions:

[id(0x60020003), propget]
CURRENCY Money();
[id(0x60020003), propput]
void Money([in] wchar_t rhs);


The propget method is marshaled correctly, but the propput is marshaled as a wchar_t. In this situation not only is the helpstring missing, but the property will not work correctly. If you want to marshal a Decimal property as a Currency, it is impossible to implement your solution using VB.NET using the “recommended” approach.

C#, however, does not have this problem as demonstrated in the code sample below:

[ComVisible(true)]
[Description("This is the interface for the SampleClass class.")]
[Guid(SampleClass.InterfaceId)]
public interface ISampleClass
{
    decimal Money
    {
        [Description("Money property description.")]
        [return: MarshalAs(UnmanagedType.Currency)]
        get;
        [Description("Money property description.")]
        [param: MarshalAs(UnmanagedType.Currency)]
        set;
    }
}


If you lookup the propget and propput methods for the Money property of the ISampleClass interface in OLE View, you will find the following definitions:

[id(0x60020003), propget, helpstring("Money property description.")]
CURRENCY Money();
[id(0x60020003), propput, helpstring("Money property description.")]
void Money([in] CURRENCY rhs);


In the C# version, not only is the helpstring present, but the property is correctly marshaled.

It is issues like these that give VB.NET a bad rap.

Tuesday, October 10, 2006

Improving String Handling Performance in ASP Applications

A must read for anyone who does work in Visual Basic 6 or VB Script.

Improving String Handling Performance in ASP Applications
By James Musson
Developer Services, Microsoft UK
http://msdn2.microsoft.com/en-us/library/aa302329.aspx

This article specifically discusses ASP applications, but it is also true for Visual Basic 6 applications. The problem appears to be magnified if the ASP application is hosted in a VMWare environment.

Sunday, September 24, 2006

Time Keeps On Ticking (or Does It?)

One thing that many server applications (like Windows Services) have in common is some sort of timer loop that causes the application to "wake up", do some processing, and go back to sleep for a certain amount of time. For example, you may have a website that allows users to enter orders into a system. However, in this system order processing is a complex process that takes a fair amount of time, so instead of requiring the users to wait for the order to be processed and placed into the system, the orders go into a processing queue. Another server application pulls the order off of the queue and processes it. Depending on how quickly orders need to be entered into a system, this server application’s order processing logic may run as often as every few seconds or it may only need to run once a day.

A common technique that is used to implement this timer loop is to have a fairly fast Timer control (typically 10 or 20 Hz) raise an event. (There are actually other techniques for doing this same thing, but for this example I am using a Timer control.) The event handler for this fast Timer control will compare the current time with the last time the application executed its processing loop. If the difference between these two times exceeds the application’s timer loop frequency setting, the application will begin its processing logic. If the difference between these two times is less than the application’s timer loop, the application will go back to sleep and wait for the next Timer event to fire.

Here is some sample VB.NET code that implements this:

Private Sub Timer1_Tick( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Timer1.Tick

    Const cRunEverySeconds As Integer = 120
    Static sLastRunDateTime As Date

    If Date.Now >= sLastRunDateTime.AddSeconds(cRunEverySeconds) Then

        ' Remember Last Run Date & Time
        sLastRunDateTime = Date.Now

        ' Perform Processing

    End If

End Sub

This technique works great except for the day daylight saving time starts or ends. In the example above assume sLastRunDateTime contains a time of 1:59:58 AM. Normally the processing would execute two minutes later at 2:01:58 AM, but today is the day daylight saving time ends. When the system clock hits 2:00 AM, it will automatically go back to 1:00 AM. Therefore instead of waiting 2 minutes between processing cycles, the application will wait 1 hour and 2 minutes. Depending on the application this may or may not be a problem, but it is not ideal. (This same problem plagues applications that are running on laptops that may move from one time zone to another.)

The way to solve this problem is actually very easy. In .NET framework languages all you have to do is replace the Date.Now function call with Date.UtcNow. Coordinated Universal Time (UTC) is not affected by silly things like daylight savings time or time zone changes. Time never moves backward or skips an hour in UTC.

The following is the same code listed above with the Date.Now function calls replaced with Date.UtcNow function calls:

Private Sub Timer1_Tick( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Timer1.Tick

    Const cRunEverySeconds As Integer = 120
    Static sLastRunDateTime As Date

    If Date.UtcNow >= sLastRunDateTime.AddSeconds(cRunEverySeconds) Then

        ' Remember Last Run Date & Time
        sLastRunDateTime = Date.UtcNow

        ' Perform Processing

    End If

End Sub

Wednesday, June 14, 2006

The $1 PowerMac 9500/132

I have not done anything interesting on the software front recently, but I have recently obtained an old Power Macintosh 9500/132. Here is an excerpt form a posting I placed on a good website for older Macintoshes that are still running System 7.6.1 called System 7 Today:

My company was getting rid of a bunch of old equipment recently and I was able to acquire an old Power Macintosh 9500/132 they had for a $1 (I was the high bidder).

I had to replace the PRAM Battery (about $10). It came with System 7.5.3 so I upgraded to 7.5.5. I found a website called System 7 Today and read its recommendation to run System 7.6.1 and I also discovered that a lot of the Mac software for older machines requires System 7.6.1. So I bought a copy of System 7.6 on eBay for $15 (including shipping and handling). Then I upgraded that to System 7.6.1.

So I currently have a $26 PowerMac 9500/132 with the following:
48 MB of RAM
2 GB Hard drive
PowerPC 604 - 132 MHz

When I got it up and running, I searched through my old CDs for anything that might run on a Macintosh (I have never had one up to this point). To my surprise I discovered the following applications that would run on my “new” Mac:

Warcraft
Warcraft II (including the expansion set)
Tribond

I was pleased to see Warcraft II would run, because the version I have would not run properly on Windows XP (you need the Battle Net edition for that).

Wednesday, February 22, 2006

Opening the Address Book from .NET

The Problem
In today’s world, applications often to need to send e-mail messages. The .NET framework has a class for sending e-mail messages (System.Web.Mail.SmtpMail) that works pretty well (it has some shortcomings, but that is a topic for another article). In order to send an e-mail to someone, you need an e-mail address. Most people keep their e-mail addresses in a MAPI-compliant address book, like Outlook, Outlook Express, etc. The .NET framework, however, does not have a mechanism for opening and retrieving e-mail addresses from this address book. This is not good.

The Typical Solution
The typical solution to this problem is to use Simple MAPI’s MAPIAddress method to display the address book. This method works for most situations, but has one major drawback. It only works for ASCII text. There is not a Unicode version of Simple MAPI interface. If you display the address book and select a person with a Japanese (Chinese, Thai, etc.) name, your application will see their name as a series of questions marks (????).

The Extended MAPI Solution
The only way to support Unicode names is to use Extended MAPI. Unfortunately, Microsoft does not support calling the Extended MAPI library from .NET (see KB 813349). I have not personally tried to call the IAddrBook::Address method from .NET, but I have read enough newsgroup articles to think that it is probably not worth the effort.

My solution to this dilemma was to create an ATL/COM/Active X dll that wrapped the Extended MAPI call. I then called this dll from my .NET application using COM Interop. This solution worked very well for what I wanted, but it adds an extra two dlls to my application (oh well).

The Code
Here is the ATL code I wrote that makes the Extended MAPI calls.

// EasyMapi.cpp : Implementation of CEasyMapi

#include "stdafx.h"
#include "EasyMapi.h"

#include
#include ".\easymapi.h"

// CEasyMapi

// Constructor
CEasyMapi::CEasyMapi()
{
   MAPIInitialize(NULL);
}

STDMETHODIMP CEasyMapi::AddressBook(VARIANT_BOOL* Cancel,
   BSTR* AddressesSelected)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   // Local Variables
   HRESULT hResult = NULL;
   ULONG ulUIParam = NULL;
   ULONG ulFlags = 0;
   LPMAPISESSION lpSession = NULL;
   LPADRBOOK pAddressBook = NULL;
   ADRPARM adrparm;
   LPTSTR rglpszDestTitles[1];
   ULONG rgulDestComps[1];
   LPADRLIST pReceipients = NULL;
   CString ReceipientsList;

   // Begin
   *Cancel = VARIANT_FALSE;

   // Logon to MAPI
   ulFlags = MAPI_UNICODE | MAPI_LOGON_UI | MAPI_NO_MAIL |
      MAPI_EXTENDED;
   if(FAILED(hResult = MAPILogonEx(
      ulUIParam, NULL, NULL, ulFlags,
      (LPMAPISESSION FAR *) &lpSession )))
   {
      return hResult;
   }

   // Open AddressBook Object
   if(FAILED(hResult = lpSession->OpenAddressBook(
      ulUIParam, NULL, 0, (LPADRBOOK FAR *) &pAddressBook)))
   {
      return hResult;
   }

   // Display Dialog
   ulFlags = DIALOG_MODAL | MAPI_UNICODE;

   rglpszDestTitles[0] = (LPTSTR)("To");
   rgulDestComps[0] = MAPI_TO;

   adrparm.cbABContEntryID = 0;
   adrparm.lpABContEntryID = NULL;
   adrparm.ulFlags = ulFlags;
   adrparm.lpReserved = NULL;
   adrparm.ulHelpContext = 0;
   adrparm.lpszHelpFileName = NULL;
   adrparm.lpfnABSDI = NULL;
   adrparm.lpfnDismiss = NULL;
   adrparm.lpvDismissContext = NULL;
   adrparm.lpszCaption = (LPTSTR)"Address Book";
   adrparm.lpszNewEntryTitle = (LPTSTR)("");
   adrparm.lpszDestWellsTitle = (LPTSTR)("");
   adrparm.cDestFields = 1;
   adrparm.nDestFieldFocus = 0;
   adrparm.lppszDestTitles = rglpszDestTitles;
   adrparm.lpulDestComps = rgulDestComps;
   adrparm.lpContRestriction = NULL;
   adrparm.lpHierRestriction = NULL;

   if(FAILED(hResult = pAddressBook->Address(
      &ulUIParam, &adrparm, (LPADRLIST FAR *) &pReceipients)))
   {
      if (hResult == MAPI_E_USER_CANCEL)
      {
         *Cancel = VARIANT_TRUE;
         return S_OK;
      }
      return hResult;
   }

   // Parse Return Values
   if ((pReceipients != NULL) && (pReceipients->cEntries > 0))
   {
      for(unsigned int ReceipientIndex = 0;
         ReceipientIndex < pReceipients->cEntries;
         ReceipientIndex++)
      {
         if (pReceipients->aEntries[ReceipientIndex].cValues > 0)
         {
            if (ReceipientsList.GetLength() > 0)
            {
               ReceipientsList.Format(_T("%s, "),
                  ReceipientsList);
            }
            ReceipientsList.Format(_T("%s\"%s\" <%s>"),
ReceipientsList,
pReceipients->aEntries[ReceipientIndex].rgPropVals[1].Value.lpszW,
pReceipients->aEntries[ReceipientIndex].rgPropVals[2].Value.lpszW);
         }
      }
      *AddressesSelected = ReceipientsList.AllocSysString();
   }
   else
   {
      *AddressesSelected = NULL;
   }

   // Logoff MAPI (ignor any errors)
   lpSession->Logoff(0,0,0);

   return S_OK;
}


References
Differences between CDO, Simple MAPI, and Extended MAPI
http://support.microsoft.com/kb/q200018/

Microsoft Windows SDK - Simple MAPI
http://windowssdk.msdn.microsoft.com/library/en-us/mapi/
html/_mapi1book_simple_mapi.asp


Microsoft Windows SDK – MAPI – IAddrBook::Address
http://windowssdk.msdn.microsoft.com/library/en-us/mapi/
html/_mapi1book_iaddrbook_address.asp


Support policy for Microsoft Exchange APIs with the .NET Framework applications
http://support.microsoft.com/Default.aspx?id=813349

Saturday, February 18, 2006

Japan Mail Version 1.4

I made some updates to the Japanese e-mail program I wrote for my wife. The biggest update was to support address books containing Unicode (i.e. Japanese) names. The standard simple MAPI object does not support Unicode, so I had to use the extended MAPI library (not very fun). I hope to write up an article that discusses the pain I went through to get this to work properly in the near future.

Updated on 12/22/2010: I no longer have Japan Mail available for download because Office 2003 and above correctly encode Japanese e-mail messages. If for some reason you would like a copy, send me a note and I will e-mail it to you.

Wednesday, February 08, 2006

Duel Network Cards and the Default Gateway Setting

The Problem
We had a new Microsoft Windows Server 2003 Web Edition server from Dell that had a duel Intel(R) PRO/1000 MT Network Card install. We had one of the network card’s ports connected to our LAN and the other network card’s port connected to the DMZ. Occasionally the machine would stop responding to any traffic that came from the Internet. It would still respond to LAN traffic, but WAN traffic was blocked. We put a network sniffer on the network and confirmed the WAN/internet packets were getting to the server, but the server was not responding to the traffic. Disabling and re-enabling the network card would fix the problem.

The Cause
After searching through numerous newsgroup articles and websites, we called Microsoft’s technical support. Through talking with them, we discovered that Windows uses a single routing table rather than each network card having its own routing table. Each of our network cards had a default gateway defined. The DMZ network card had an internet default gateway and the LAN network card had a LAN default gateway. The DMZ network card (being in the DMZ) could not see the LAN default gateway. Since there is only one routing table for Windows, each network card essentially had two default gateways defined (the DMZ one and the LAN one). This was not a problem for the LAN card, because he could see both gateways, but this was a problem for the DMZ card, because he could only see one of the two default gateways. If, for some reason, the DMZ default gateway becomes unavailable, Windows will switch to the next default gateway in the list. In this case it would switch from the DMZ gateway to the LAN gateway, causing the DMZ network card to no longer work because it could not reach the LAN default gateway. It would continue in this state until the network card was reset, re-establishing the DMZ default gateway as the gateway that should be used.

The Solution
The solution to this problem is to only have one default gateway defined, even though there are two network cards on the server. The DMZ network card was set to use the DMZ default gateway and the LAN network card did not have any default gateway specified.

More Information
Multiple Default Gateways Can Cause Connectivity Problems
http://support.microsoft.com/default.aspx/kb/159168
Default Gateway Configuration for Multihomed Computers
http://support.microsoft.com/default.aspx/kb/157025

Tuesday, February 07, 2006

Windows XP with Asian Language Support – Textbox Caret Issue

Here is an issue that I have noticed (and has bugged me) for a long time, but I think I may be the only person who has noticed this because I never read about it on any newsgroups, blogs, or knowledge base articles. I believe there is a problem with textboxes in the English version Windows XP when Asian language support is enabled (specifically Japanese). If you are typing in a standard windows textbox and the caret gets to the end of the textbox, the caret appears to be either in the middle or to the left of the character instead of to the right of the character. This does not happen in the English version Windows XP when Asian language support is not enabled.

For Example:
If you were to type “abcdefghijkl” into a textbox and it completely filled up the textbox, it would look like the following on Windows XP with Asian support: abcdefghijk|l (the | represents the position of the caret). On Windows XP without Asian support it would look like the following: abcdefghijkl| (which is how it should look).


Example of a textbox with this issue. The carat is actually to the left of the "i" even though it appears on the right side.


This is the same textbox on a machine without Asian language support. As you can see, the carat appears on the right-hand side of the "i" character as you would expect.

If the character is wide (w, s, etc.) the caret appears in the middle of the character. If the character is narrow (i, l, etc.) the caret appears to the left of the character.

This only happens at the end of the textbox.

Am I the only person who has noticed this, or are there others of you out there? If you have noticed this and it bugs you, please leave a comment. I want to know how many of us there are out there.

ASCII Control Characters and Web Service Strings

The Problem
We had an interesting situation come up recently with one of the web services we were writing. This web service returned a string value back to its caller, but occasionally the code Visual Studio .NET generated to handle the web service call would raise the following error message: “There is an error in XML document (1, 285).” The underlying error message was: System.Xml.XmlException: ' ', hexadecimal value 0x11, is an invalid character. Line 1, position 351. This caused us to do a little investigation and we discovered that the returned string would sometimes contain a 0x11 (17) control character. We determined that this character should not have been there in the first place, but we were surprised that it should cause an error. We did a little experiment and discovered that any ASCII character in the range of 0x00 to 0x1F (0 to 31), with the exception of 0x09 (TAB), 0x0A (LF), and 0x0D (CR), would cause this error. This error did not occur in the web service itself, but in the code that processed the XML response returned by the web service. This code is generated automatically by Visual Studio, so there is not much we can do to control it.

XML String Limitation
We did a little more digging and discovered that string values in XML documents are not allowed to contain characters in the range 0x00 – 0x1F (0 – 31), except for 0x09 (TAB), 0x0A (LF), and 0x0D (CR). Details about this restriction are documented in the following locations:
http://www.w3.org/TR/xmlschema-2/#string
http://www.w3.org/TR/2000/WD-xml-2e-20000814#dt-character

.NET Inconsistency
After discovering this restriction on XML string values, I was no longer surprised the code that parsed the XML response from the web service raised an error. However, I was surprised that the web service method did not raise an error when we tried to return a string containing an invalid character. I would have though that the web service method would not be allowed to return data that would be invalid when it was converted into XML.

Things to Remember
  • XML strings cannot contain characters in the range 0x00 – 0x1F (0 – 31), except for 0x09 (TAB), 0x0A (LF), and 0x0D (CR).
  • Since XML strings cannot contain ASCII control characters, web service methods cannot send or receive strings that contain ASCII control characters.
  • If you do need to send a string containing ASCII control characters, you can pass it as a Char or Byte array to get around this restriction.

Thursday, January 05, 2006

Security Briefs: Strong Names and Security in the .NET Framework

Here is a good article that explains how strong names and code signing work in .NET applications. The whole concept can be confusing to developers who have never had to “sign code” before, and I think this article does a pretty good job of explaining the process.

http://msdn2.microsoft.com/en-us/library/Aa302416.aspx