Workaround for the "AllowDocumentFunction constraint violated" error with Delphi XE2 apps

I have been reading discussions online about an EOleException error we were getting when calling TransformNode from a Delphi XE2 application: "Operation Aborted: AllowDocumentFunction constraint violated". The problem arises because Delphi XE2 now uses MSXML 6.0, which has made some changes to default settings for security reasons.

This, along with the ProhibitDTD property, has caused some grief. The recommended fix is to make changes to the VCL unit xml.win.msxmldom.pas. I found an alternative which appears to work without side-effects and requires no changes to the VCL source code: set the MSXMLDOMDocumentCreate to your own custom implementation which sets the AllowDocumentFunction (and ProhibitDTD or AllowXsltScript if you wanted) property after creating the object.

unit msxml_transformnode_fix;

interface

implementation

uses
  Winapi.ActiveX, Winapi.Windows, System.Variants, System.Win.ComObj, Winapi.msxml, Xml.xmldom, System.Classes,
  Xml.Win.msxmldom, Xml.XMLConst;

function TryObjectCreate(const GuidList: array of TGuid): IUnknown;
var
  I: Integer;
  Status: HResult;
begin
  Status := S_OK;
  for I := Low(GuidList) to High(GuidList) do
  begin
    Status := CoCreateInstance(GuidList[I], nil, CLSCTX_INPROC_SERVER or
      CLSCTX_LOCAL_SERVER, IDispatch, Result);
    if Status = S_OK then Exit;
  end;
  OleCheck(Status);
end;

function CreateDOMDocument: IXMLDOMDocument;
begin
  Result := TryObjectCreate([CLASS_DOMDocument60, CLASS_DOMDocument40, CLASS_DOMDocument30,
    CLASS_DOMDocument26, Winapi.msxml.CLASS_DOMDocument]) as IXMLDOMDocument;
  if not Assigned(Result) then
    raise DOMException.Create(SMSDOMNotInstalled);

  try
    (Result as IXMLDOMDocument2).SetProperty('AllowDocumentFunction', True);
  except on E: EOleError do
    ;
  end;
end;

initialization
  MSXMLDOMDocumentCreate := msxml_transformnode_fix.CreateDOMDocument;
end.

Delphi’s ongoing problem with "with"

Delphi has long included a scoping construct called with which can be used to increase the readability and efficiency of code.  From Delphi’s documentation:

A with statement is a shorthand for referencing the fields of a record or the fields, properties, and methods of an object. The syntax of a with statement is:
  with obj do statement
or:
  with obj1, …, objn do statement
where obj is an expression yielding a reference to a record, object instance, class instance, interface or class type (metaclass) instance, and statement is any simple or structured statement. Within the statement, you can refer to fields, properties, and methods of obj using their identifiers alone, that is, without qualifiers.

However, with has a really big gotcha: scope ambiguity.  Scope ambiguity came back to bite us yet again today: I am currently upgrading a major project (over 1 million SLoC) from an older version of Delphi to Delphi XE2. One component of this project is a legacy third party component package which is no longer supported.  We have full source and the cost of replacing it would be too high, so we are patching the source where needed.

While tracing a reported window sizing bug, I found the following line of code (on line 12880, yes it’s a big source file):

with vprGetTranspBorderSize(BorderStyle) do
  R := Rect(Left,Top,Width-Right,Height-Bottom);

vprGetTranspBorderSize returns a TRect.  In the earlier version of Delphi, the Width and Height properties were not members of TRect, so were found in the parent scope, being the component itself in this case.  All good.

But now in Delphi XE2, records can now have functions and properties just like a class.  So TRect has a bunch of additional properties, including Width and Height.  Handy to have, until you throw in some code that was written before these new properties existed.  One fix here is simply to qualify the scope:

with vprGetTranspBorderSize(BorderStyle) do
  R := Rect(Left,Top,Self.Width-Right,Self.Height-Bottom);

Or, you could declare a second variable, and eliminate the with statement entirely:

R2 := vprGetTranspBorderSize(BorderStyle);
R := Rect(R2.Left,R2.Top,Width-R2.Right,Height-R2.Bottom);

The problem with the second approach is it makes the code less readable and the introduction of the second variable R2 widens its scope to the whole function, rather than just the block where its needed.  This tends to lead to reuse of variables, which is a frequent source of bugs.

Many developers (including myself) have argued that adding support for aliases to with would avoid these scope problems.  Some others argue that one simply shouldn’t use with, and instead declare variables.  But with does make code much cleaner and easier to read, especially when used with try/finally blocks.

with R2 := vprGetTranspBorderSize(BorderStyle) do
  R := Rect(R2.Left,R2.Top,Width-R2.Right,Height-R2.Bottom);

Given that this issue was reported to Borland as far back as 2002, and since that time many, many complex language constructs have been added to Delphi, I think it’s a real crying shame that Borland/Inprise/CodeGear/Embarcadero have chosen to ignore this problem for so long.

Strava segment statistics site updates

I made some small tweaks to my Strava segment stats website recently. This includes:

  • Fixes for rendering issues in the lists
  • Ability to hide bogus segments, similar to the Strava flagging function
  • Dynamically sortable tables
  • Underpinnings for anyone to be able to have their stats updated automatically (but UI for this not yet complete)

Currently I also have an issue with the Strava API, where segments with more than 50 efforts cannot have all efforts populated. Once I have a resolution for this, I’ll publish another update with the ability for anyone to view their stats.

Loading a Unicode string from a file with Delphi functions

In my previous post, I described differences in saving text with TStringStream and TStringList.  TStringList helpfully adds a preamble.  TStringStream doesn’t.  Now when loading text from a stream, you’ll typically want to strip off the preamble.  But if you want the text to be otherwise unmodified, then TStringList is not safe, and TStringStream doesn’t strip off the preamble.

Here’s a helper function that does strip the preamble.  If you don’t pass an encoding, it will guess on the basis of the preamble (but won’t otherwise sniff the stream content to guess the encoding heuristically). If the content does not have a preamble, it assumes the current code page (TEncoding.Default).  If you do pass an encoding, the preamble will be stripped if it is there but no encoding detection will take place.

function LoadStringFromFile(const filename: string; encoding: TEncoding = nil): string;
var
  FPreambleLength: Integer;
begin
  with TBytesStream.Create do
  try
    LoadFromFile(filename);
    FPreambleLength := TEncoding.GetBufferEncoding(Bytes, encoding);
    Result := encoding.GetString(Bytes, FPreambleLength, Size - FPreambleLength);
  finally
    Free;
  end;
end;

Obviously not fantastic for very large files (you can solve that one yourself) but for your plain old bite sized files, quick and easy solution.

Comparing TStringStream vs TStringList for writing Unicode strings to streams

There are two methods widely used in Delphi code for reading and writing strings to/from streams with Delphi, that initially seem pretty similar in their behaviour.  These are TStrings.SaveToStream and TStringStream.SaveToStream (or SaveToFile in either case):

procedure TForm1.SaveToFile;
const
  AString = 'This is some Unicode text'#13+
            'Test Unicode © Δ א';
begin
  with TStringList.Create do
  try
    Text := AString;
    SaveToFile('TStringList UTF8.txt', TEncoding.UTF8);
  finally
    Free;
  end;

  with TStringStream.Create(AString, TEncoding.UTF8) do
  try
    SaveToFile('TStringStream UTF8.txt');
  finally
    Free;
  end;
end;

But there are several crucial differences in what is written to the stream between these two methods:

  1. TStringList prepends the preamble bytes for the encoding (in this case, #$EF#$BB#$BF)
  2. TStringList appends a new line #$0D#$0A to the file, if your text does not already end in a new line.
  3. TStringList converts any single line breaking characters in the text (e.g. #$0D or #$0A) into #$0D#$0A.

The following hex dumps may show this more clearly:

EF BB BF 54 68 69 73 20 69 73 20 73 6F 6D 65 20
55 6E 69 63 6F 64 65 20 74 65 78 74 0D 0A 54 65
73 74 20 55 6E 69 63 6F 64 65 20 C2 A9 20 CE 94
20 D7 90 0D 0A 

TStringList UTF8.txt

54 68 69 73 20 69 73 20 73 6F 6D 65 20 55 6E 69
63 6F 64 65 20 74 65 78 74 0D 54 65 73 74 20 55
6E 69 63 6F 64 65 20 C2 A9 20 CE 94 20 D7 90

TStringStream UTF8.txt

Make sure you know how your files will be read and whether these differences are important to the target application.

Basically, TStringList is typically not appropriate for streaming strings without modification.  TStringStream is your friend here.  But if you need the preamble, and just the preamble, then you’ll have to do a little more work; you won’t be able to use TStringStream.SaveToFile.

IXMLDocument.SaveToStream does not always use UTF-16 encoding

Delphi’s documentation on IXMLDocument.SaveToStream has the following important caveat:

Regardless of the encoding system of the original XML document, SaveToStream always saves the stream in UTF-16.

It’s helpful to have notes like this.  Mind you, forcing UTF-16 output is definitely horrible; what if we need our document in UTF-8 or (God forbid) some non-Unicode encoding?

Now Kris and I were looking at a Unicode corruption issue with an XML document in a Delphi application and struggling to understand what was going wrong given this statement in the documentation.  Our results didn’t add up, so we wrote a little test app to test that statement:

procedure TForm1.OutputEncodingIsUTF8;
const
  UTF8XMLDoc: string =
    '<!--?xml version="1.0" encoding="utf-8"?-->'#13#10+
    '';
var
  XMLDocument: IXMLDocument;
  InStream: TStringStream;
  OutStream: TFileStream;
begin
  // stream format is UTF8, input string is converted to UTF8
  // and saved to the stream
  InStream := TStringStream.Create(UTF8XMLDoc, TEncoding.UTF8);</blockquote>
  // we'll write to this output file
  OutStream := TFileStream.Create('file_should_be_utf16_but_is_utf8.xml',
    fmCreate);
  try
    XMLDocument := TXMLDocument.Create(nil);
    XMLDocument.LoadFromStream(InStream);
    XMLDocument.SaveToStream(OutStream);
    // IXMLDocument.SaveToStream docs state will always be UTF-16
  finally
    FreeAndNil(InStream);
    FreeAndNil(OutStream);
  end;

  with TStringList.Create do
  try    // we want to load it as a UTF16 doc given the documentation
    LoadFromFile('file_should_be_utf16_but_is_utf8.xml', TEncoding.Unicode);
    ShowMessage('This should be displayed as an XML document '+
                'but instead is corrupted: '+#13#10+Text);
  finally
    Free;
  end;
end;

When I run this, I’m expecting the following dialog:

But instead I get the following dialog:

Note, this is run on Delphi 2010.  Haven’t run this test on Delphi XE2, but the documentation hasn’t changed.

The moral of the story is, the output encoding is the same as the input encoding, unless you change the output encoding with the Encoding property, for example, adding the highlighted line below fixes the code sample:

    XMLDocument := TXMLDocument.Create(nil);
    XMLDocument.LoadFromStream(InStream);
    XMLDocument.Encoding := 'UTF-16';
    XMLDocument.SaveToStream(OutStream);

The same documentation issue exists for TXMLDocument.SaveToStream.  I’ve reported the issue in QualityCentral.

Hell of the South

I’m sitting down on a quiet Sunday evening writing this race report with rather a sore shoulder.  I guess I’m writing this race report for Iain — others can read if they want!  So the race was The Hell of the South, a great race run by Southern Tasmania Veterans Cycling Club, a 65km loop around some lumpy roads and including two decent climbs, starting from the small Huon Valley town of Cygnet.  This is the only race I’ve done with STVCC.

Today the race lived up to its name.  Unlike the gentle and gracious conditions the Orange Army were contending with in Victoria, we had a real race to fight!  The turnout to the race was surprisingly good, given the terrible weather conditions and the Around The Bay challenge happening in Melbourne on the same day.  I was feeling reasonably good about my condition and how prepared I was for the race, so I signed up for B grade.  With the format of the race being graded handicap, no drafting of other grades was allowed, so B grade was sent off last to prevent a mixup with A grade.  That’s a good incentive not to get dropped…

While we waited for our turn to leave, the weather just kept getting worse, until when we finally left, it was raining properly, cold, with a bit of wind just to keep us motivated.  A controlled roll through Cygnet, then the race set off in earnest — well, not really.  I think we were all happy to ride at a decorous pace with the level of water on the road and in our faces.  When we hit the first climb, things amped up a bit but it wasn’t out of this world crazy.  I ended up on the front for the descent, which was nice, because it meant I could descend at a pace that suited me, and the conditions meant that no other rider was going to try and pass.  We swapped turns in the bunch all the way through to Kettering, even getting a little bit of sunlight on the way, and things were starting to look up.  I was happy on the front for my turn, I was able to hang on in the bunch, and I was wondering if I might even be able to challenge for a podium position.  But just after Kettering, disaster struck.

I was following another rider, who I shall leave unnamed, when he suddenly sat up — I’m not sure if he then realised that he should move out of the way before slowing down, or not, but as I started to go around him, he moved right, and we crossed wheels.  I came straight off, and at least one other rider also collided with me and came off.  The other riders came off okay, but my bike was unrideable.  The left hand brake lever was twisted and was no longer working.  Without two good brakes, there was no way I was going to ride down the soaking wet descent.  So my race ended right there, at the base of the big climb.

One of the unofficial race support cars stopped to pick me up (thank you ladies!), and we took off again to follow the riders.  Not 2 minutes later, the skies opened, and started hailing in earnest on the remaining riders.  Now I wasn’t sure if I was so upset about being out of the race any longer.  The remainder of the race saw increasingly sad riders grimly struggling against hail, wind and rain — not a pleasant descent of the hill!  I sat resplendent in the comfort of a nice warm car and watched their pain.

Later, I discovered that the brake lever did still sort-of work, and I could probably have straightened it out and kept riding.  However, as it was damaged I guess it’s probably for the best that I didn’t depend on it for the descent.

Coming back into Cygnet in the comfort of the car…  Hailing outside.

Bike seat a little the worse for wear

And twisted brake lever.  Sort-of working now.

I came off lightly. (Photo by my daughter who was keen to do the photography of my bruised shoulder)

My brother survived an expected side trip onto the gravel on the big descent a couple of years ago.  Lots of fun!  And that’s the only picture I have of the course…

Reviewing my ride afterwards, I don’t think anyone in B grade was riding at threshold today.  At no time during this race did I find myself desperately struggling not to be dropped.  It was certainly very different to Longford – Campbelltown or Grindelwald Challenge, both of which stretched me mightily — and broke me!  My average heart rate this time was only 155, even with a small bunch and the inclement weather.

I didn’t hang around long after the race — I was quite cold, late for family and well, I didn’t win anything 🙂 Next time.  For now I’ll have to live with the three little letters DNF next to my name…  Note — the elevation profile in the Strava map is pretty inaccurate, so everything based on that, i.e. power averages, etc, will also be out of whack.  I reckon the Garmin was so wet its altimeter was on the blink (there’s a hole on the base of the Garmin for sensing air pressure — if it gets blocked, no altimeter).  Lucky I wasn’t in a plane I guess.

The case of the terribly slow PayPal emails

Every now and then I receive a payment via PayPal.  That’s not unusual, right?  PayPal would send me an email notifying me of the payment, and I’d open up Outlook to take a look at it.  All well and good.  In the last week, however, something changed.  When I clicked on any PayPal email, Outlook would take the best part of a minute to open the email, and what’s more, would freeze its user interface entirely while doing this.  But this was only happening with emails from PayPal — everything else was fine.

Not good.  At first I suspected an addin was mucking things up, so I disabled all the Outlook addins and restarted Outlook for good measure.  No difference.  Now I was getting worried — what if this was some unintended side-effect of some malware that had somehow got onto my machine, and it was targeting PayPal emails?

So I decided to do some research.  I fired up SysInternals’ Process Monitor, set it up to show only Outlook in its filtering, and turned on the Process Monitor trace.

Process Monitor filter window – filtering for OUTLOOK.EXE

Then I went and clicked on a PayPal email.  Waited the requisite time for the email to display, then went back to Process Monitor and turned off the trace.  I added the Duration column to make it easier to spot an anomalous entry.  This doesn’t always help but given the long delay, I was expecting some file or network activity to be taking a long time to run.

Adding the Duration column

Then scrolling up the log I quickly spotted the following entry.  It had a duration of nearly 3 seconds which stood out like a sore thumb.

The first offending entry

This entry was a Windows Networking connection to connect to a share on the remote host \\102.112.2o7.net.  This came back, nearly 3 seconds later, with ACCESS DENIED.  Then there were a bunch of follow up entries that related to this, all in all taking over 30 seconds to complete.  A quick web search revealed that this domain with its dubious looking name 102.112.2o7.net belongs to a well known web statistics company called Omniture.  That took some of the load off my mind, but now I was wondering how on earth opening a PayPal email could result in Internet access when I didn’t have automatic downloads of pictures switched on.

One of the emails that caused the problem, redacted of course 🙂

I opened the source of the PayPal HTML email and searched for “102.112“.  And there it was.

The HTML email in notepad

That’s a classic web bug.  Retrieving that image of a 1×1 pixel size, no doubt transparent, with some details encoded in the URL to record my visit to the web page (or in this case, opening of the email):

What was curious about this web bug was the use of the “//” shorthand to imply the same protocol (e.g. HTTP or HTTPS) as the base page.  That’s all well and good in a web browser, but in Outlook, the email is not being retrieved over HTTP.  So Outlook interprets this as a Windows Networking address, and attempts a Windows Networking connection to the host instead, \\102.112.2o7.net\b….

At this point I realised this could be viewed as a security flaw in Outlook.  So I wrote to Microsoft instead of publishing this post immediately.  Microsoft responded that they did not view this as a vulnerability (as it does not result in system compromise), but that they’d pass it on to the Outlook team as a bug.

Nevertheless, this definitely has the potential of being exploited for tracking purposes.  One important reason that external images are not loaded by default is to prevent third parties from being notified that you are reading a particular email.  While this little issue does not actually cause the image to load (it is still classified as an external image), it does cause a network connection to the third party server which could easily be logged for tracking purposes.  This network connection should not be happening.

So what was my fix?  Well, I don’t really care about Omniture or whether PayPal get their statistics, so I added an entry in my hosts file to block this domain.  Using an invalid IP address made it fail faster than the traditional use of 127.0.0.1:

0.0.0.0    102.112.2o7.net

And now my PayPal emails open quickly again.

The Tour of Tasmania Team Time Trial

Update: Photos now included.  All photos bar the last one courtesy of Rob Cumine.

There was quite a bit of buzz leading up to the initial stage of the 2011 Tour of Tasmania, an 18km team time trial (TTT) up the 1200m Mt Wellington climb starting from Cascade Brewery. As various commentators described the stage as “innovative”, “unique”, “ground-breaking”, and even “stupid”, I was keen to see for myself how it would pan out. I was a little dubious as to how it would work, but it turned out to be a spectacular way to shake up the General Classification and inject some spice and suspense early in the race.

So why the controversy? While up-hill individual time trials are common, the tour organisers claimed that this was a world-first for the team time trial format. Some riders suggested that only a team stacked with climbers could win, and this meant it was not a sensible format. But I wondered if that was any different to having a team stacked with time trial specialists for a traditional TTT.

I took the day off work to watch the stage. The day dawned foggy and pretty chilly; together with my mates Ant and Phil, we scouted out the climb early in the morning, then descended back to the start line to meet the rest of the group who would join us to cheer on the pros. The mid-morning descent left all of us shivering and I found myself in that zone where one struggles to care enough to keep focused on the road. Thick fog on the descent kept our speed at about 20km/h, cars looming out of the mist at the last moment to keep us on our toes. Even at that low speed, we still froze. Fortunately, the fog thinned out a bit by the time the race started, but half the climb was still fog shrouded.

After joining the rest of the group at Cascade Brewery, we climbed at a much more leisurely pace for a second time, before selecting a vantage point about 2km from the summit to watch the race unfolding. Our vantage point was chosen mostly on the basis of it being a bit sunny!

The race favourites were definitely Genesys Wealth Advisors, given that they’ve won every National Road Series race this year so far, as well as collecting a hefty swag of stage wins. But they expected a challenge from the Russian National Team and Jayco/2XU, and a challenge they got! In this TTT there was not a time cut, and the top 4 riders for each team all got the time of the 4th rider over the line. For those teams with 4 riders, it was just pure survival. Larger teams could afford to burn up some domestiques in the faster first third of the climb up Strickland Ave.

I think the key image I took away was the pain etched into so many riders’ faces, especially the 4th rider of most teams — I’m sure this would have been a resounding personal best time for many of these riders, while the strongest climbers on each team had it relatively easier. In a typical TTT, stronger time triallers do take longer stints on the front, but in this climb I’m guessing the stronger climbers were on the front the whole way to the top!

Most teams were down to four riders as they passed us, and in a couple of cases the fourth rider was really struggling, with lots of urging and encouragement from team members and director in the team car following.

Jayco/2XU gave us a new call for future group rides. No more “easy on the front” calls: as they rode past us, the leading rider accelerated slightly, provoking a heartrending cry from the fourth rider, “what on earth are you doing?!?” and the team director panicking a little with a “hey hey hey hey hey” yelled out of the car window. Even in 2-3 seconds that little acceleration had opened up a sizeable gap in the group.

Also amusing was local rider Danny Pulbrook of Pure Tasmania tonking past on a green mountain bike borrowed from Scott of Ray Appleby Cycles after apparently destroying his derailleur earlier in the climb. He nearly stopped to swap to one of our road bikes but decided he couldn’t afford to stop again…

After the last Genesys rider passed, we hopped back on our bikes and followed him to the summit. By that point I had put on my winter rain jacket as I was getting pretty cold, so I was surprised by how many riders opted to ride down given the chilly, foggy descent!

 

Heading down afterwards, approaching the fog (R.Cumine)

So he’s not one of the pros, but at least he’ll survive the descent, right?

So what’s the verdict? Jayco/2XU took line honours with a strong group of climbers; Genesys managed 2nd place, 17 seconds behind the leaders, with the Russians a further 17 seconds back. The results were closer than I expected, with all teams finishing within 8 minutes of the leading team, and the last rider crossing the line 20 minutes behind the leader. The stage essentially became a test of each team’s ability to encourage its riders to climb faster, but it was much more dependent on having 4 strong climbers than a traditional TTT where stronger riders do more work — no chance of any real recovery behind the other riders on a climb! The format actually worked a lot better than I expected, and kudos to the tour directors for generating some press and interest in the race this way. That said, however, I don’t reckon we’ll be seeing a TTT hill climb in the major international tours any time soon.

Now forget about the pros; what about our times? I came in at 1:09:24, about 1 minute behind the slowest “pro”; Ant is a great climber, and was 3.5 minutes ahead of me (and looked fresh as a daisy at the summit), and Phil 2 minutes ahead of me — and they waited for me at one point. But at least we did the mountain twice!

The two abreast rule: when is it safe for cars to pass cyclists?

One of the most contentious Tasmanian road rules regarding cyclists is the Two Abreast Rule (quote here from VicRoads):

Bike riders must not ride more than two abreast (two bike riders riding next to each other) unless overtaking. Bike riders riding two abreast must not ride more than 1.5 metres apart.

Whenever this rule is mentioned, you’ll typically also get some free advice:

When riding two abreast please consider other road users and, if necessary, change to single file to allow motor vehicles to overtake safely.

So, my question today is, when is it safe for motor vehicles to overtake? I’m going to look at this in the context of Bonnet Hill in Hobart. Channel Highway on Bonnet Hill is a narrow, 60km/h semi-rural road connecting Hobart and Kingston. It is a minor road, and most traffic uses the 100km/h Southern Outlet. There are no overtaking sections on Bonnet Hill.  It is one of the most-used bicycle routes in Hobart.

In terms of my measurements, here are the basics.  Bonnet Hill is fairly typical of Tasmanian rural roads — in fact it is wider than many roads around Hobart.

Lane width 3m (total roadway 6m)
Typical bicycle width 0.45m
Typical car width 1.75m
Recommended distance from edge of road for cyclist (to tyre) 0.5m
Recommended minimum distance between car and cyclist when passing 1m (urban)/1.5m (rural)
Typical distance between double file cyclists 0.5m (max 1.5m)

When a car driver wishes to pass a cyclist on this road, can they do this both legally and safely?  Let’s look at this graphically.  In this hand-crafted image I have even moved the car closer than recommended for a rural road: only 1 metre from the cyclists who are riding single file.

Is it safe and legal to pass?  The measurements shown are in metres

Well that’s pretty clear. Either the driver has to illegally cross over the double line, or they have to pass at an unsafe distance. Personally, I’d prefer that they pass safely!

This clearly also illustrates that regardless of the legality, on a road such as Channel Highway over Bonnet Hill, it is never safe to pass if there is oncoming traffic.  A minimum of a metre is so important.

Why is a metre important?  Here are several reasons:

  1. A cyclist may be forced to negotiate around debris or damaged road surfaces, especially if they are riding close to the edge of the road.  This means you cannot be sure they’ll take a perfectly straight line while you pass them.
  2. With cross or gusty winds, a cyclist may be abruptly blown sideways with little or no notice.
  3. If you pass too close, the wind of your passing will blow the cyclist about and may even suck them into your vehicle.
  4. Driving past at speed will startle the cyclist and that may cause them to crash.  Consider: if you pass a cyclist riding at 20km/h at 100km/h (as one motorist recently admitted doing), that is equivalent to being overtaken on the Midlands Highway by a car doing 190km/h.  Do you think perhaps you would not be expecting that?  How much time do you think you’d have to become aware of a vehicle approaching from behind at that sort of speed differential?

The next diagram illustrates again why there is not enough space to pass bicycles when there is oncoming traffic. As I have shown, even if the cyclists move onto the very edge of the road, there is insufficient space between the car and the cyclists.

Oncoming traffic: is it safe to pass?  No: that’s considerably less than a metre between the car and the bikes.

OK, so consider what happens if you do try to pass when an oncoming car is approaching, and you run out of space.  What will you do?  That’s right, you’ll instinctively swerve to avoid the oncoming car, and the loser is the cyclist.

Overtaking when there isn’t room.  Hitting the poor cyclist is the likely outcome.

Finally, let’s look at the double file situation.  Here we have two cyclists riding 50cm apart, in the recommended position on the road.  The car is giving the cyclists 1m of space.  Again, this is technically illegal, but it is safe for the cyclists.  The situation for the driver is no different: they must still ensure there is no oncoming traffic.

Safely (but illegally) passing double file cyclists.  The car is only 1m further right.

So, in the end whether the cyclists are single file or double file, the driver always needs cross into the oncoming traffic lane in order to safely pass, at least on typical Tasmanian rural roads.  Also note that you can actually pass double file cyclists in a shorter time than when the cyclists are riding single file!

And to finish off, I wonder if a common sense clause in the road rules could be a help in resolving the conflict between drivers and cyclists on these low-traffic Tasmanian rural roads?  Something like:

When passing slow moving bicycles, horses, or farm vehicles the vehicle is permitted to cross a solid centre line if and only if there is no oncoming traffic and the driver can clearly see that it is safe to do so.