TimeSpan.FromMilliseconds rounding!

Today’s fairly brutal gotcha: TimeSpan.FromMilliseconds accepts a double but internally rounds the value to a long before converting to ticks (multiplying by 10000).

For example, using C# interactive in VS2017:

> TimeSpan.FromMilliseconds(1.5)
[00:00:00.0020000]

> TimeSpan.FromMilliseconds(1234.5678)
[00:00:01.2350000]

Using .FromTicks works as expected:


> TimeSpan.FromTicks(15000)
[00:00:00.0015000]

To be fair this is the documented behavior:

The value parameter is converted to ticks, and that number of ticks is used to initialize the new TimeSpan. Therefore, value will only be considered accurate to the nearest millisecond.

But really, it isn’t expected since the input is a double!

This all came to light because a camera system I’m involved with started overexposing –  the integration time was programmed as 2ms instead of the desired 1.5ms. Hmmph!

So a little alternative:

> TimeSpan TimeSpanFromMillisecondsEx(double ms) =>
    TimeSpan.FromTicks((long)(ms * 10000.0))

> TimeSpanFromMillisecondsEx(1.5)
[00:00:00.0015000]

 

Note: the FromMilliseconds method delegates to an internal Interval method, passing the milliseconds value and 1 as the scale:


private static TimeSpan Interval(double value, int scale)
{
    if (double.IsNaN(value))
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_CannotBeNaN"));
    }
    double num = value * scale;
    double num2 = num + ((value >= 0.0) ? 0.5 : -0.5);
    if ((num2 > 922337203685477) || (num2 = 0.0) ? 0.5 : -0.5);
    if ((num2 > 922337203685477) || (num2 < -922337203685477))
    {
        throw new OverflowException(Environment.GetResourceString("Overflow_TimeSpanTooLong"));
    }
    return new TimeSpan(((long) num2) * 0x2710L);
}

 

 

Advertisements

VS2017 and NuGet for C++/CLI

At the time of writing it still isn’t possible to use the NuGet package manager for C++/CLI projects. My workaround is to:

  1. Add a new C# class library project to the solution.
  2. Add any NuGet packages to this new project.
  3. Configure the C# project so it always builds in Release configuration.
  4. Use the Build Dependencies dialog to ensure that the new C# project is built before the C++/CLI project.
  5. Add to the C++/CLI project a reference to the NuGet packages by using the output folder of the C# project.

Example

Create a new solution with a C++/CLI class library…

Add a C# class library (.Net Framework), delete Class1.cs, then go to the solution’s NuGet package manager:

2018-09-05 12_19_13-.png

Install the Newtonsoft.Json package for the C# project:2018-09-05 12_29_01-Solution3 - Microsoft Visual Studio.png

Change the C# build configuration so that the Release configuration builds for both Debug and Release:2018-09-05 12_31_24-Configuration Manager.png

Then delete the unused Debug configuration:2018-09-05 12_31_42-Configuration Manager.png

2018-09-05 12_32_14-Configuration Manager.png

Make C++/CLI project dependent on the C# project:2018-09-05 12_34_09-Solution3 - Microsoft Visual Studio.png

(Note: I use the above for this dependency rather than adding a reference to the project to avoid copying the unused C# project to the C++/CLI’s output folders.)

Build the solution.

Add a reference to the Newtonsoft library by using the Browse option in the Add References dialog and locating the C# project’s bin/Release folder:

2018-09-05 12_38_57-Select the files to reference....png

Build the solution again. The Newtonsoft library will now be copied to the C++/CLI build folder:

2018-09-05 12_40_59-Debug.png

First test: add some code to the C++/CLI class to demonstrate basic JSON serialisation:

#pragma once

using namespace System;

namespace CppCliDemo {

	using namespace Newtonsoft::Json;

	public ref class Class1
	{
	private:

		String^ test = "I am the walrus";

	public:

		property String^ Test
		{
			String^ get() { return this->test; }
			void set(String^ value) { this->test = value; }
		}

		String^ SerialiseToJson()
		{
			auto json = JsonConvert::SerializeObject(this, Formatting::Indented);
			return json;
		}
	};
}

Then add a simple C# console app, reference just the C++/CLI project, and test the class:2018-09-05 12_50_22-Reference Manager - CSharpConsoleTest.png

static void Main(string[] args)
{
var test = new CppCliDemo.Class1();
var json = test.SerialiseToJson();
Console.Write(json);
}

 

The output – nicely formatted JSON 🙂

2018-09-05 12_51_48-C__Users_Jon_Source_Repos_Solution3_CSharpConsoleTest_bin_Debug_CSharpConsoleTes.png

Second test, make sure a clean rebuild works as expected:

  1. Close the solution
  2. Manually delete all binaries and downloaded packages
  3. Re-open solution and build
  4. Verify that the build order is:
    1. CSharpNuGetHelper
    2. CppCliDemo
    3. CSharpConsoleTest (my console test demo app)
  5. Run the console app and verify the serialisation works as before

 

 

C# TimeSpan TypeConverter and UITypeEditor

Code for this post is on GitHub.

I have an application that presents various TimeSpan properties to a user. The default string conversion isn’t great, in fact for anything other than hh:mm:ss it isn’t intuitive.

A TimeSpan of 1 day, 2 minutes, 3 hours, 4 seconds, and 5 milliseconds is shown in the example below:

Dev10.png

After a little noodling I found some articles that helped me put together something better (at least for me!).

The first feature is the presentation of a TimeSpan instance as a string:

dev10

The second feature is the ability to convert back from a string. For example, entering a value of 1h, 5s:

Dev10.png

… becomes…

Dev10.png

And finally, the property can present an interactive editor via a dropdown:

Dev10.png

Here’s an example of how the new classes are used as attributes on a TimeSpan:

[TypeConverter(typeof(TimeSpanStringConverter))]
[Editor(typeof(TimeSpanUIEditor), typeof(UITypeEditor))]
[DefaultValue(typeof(TimeSpan), "1.02:03:04.005")]
[DisplayName("Custom 1")]
public TimeSpan A { get; set; } = new TimeSpan(1, 2, 3, 4, 5);

WCF HttpListenerException

A problem I’ve had for a while:

Running an application from Visual Studio 2013 without administrator privileges and trying to start a WCF service results in a HttpListenerException.

My service host’s URI was http://localhost:8000.

W8x64_2014

The problem goes away when Studio is started with Administrator:

W8x64_2014

But that’s a pain for me for a variety of reasons.

I googled and found lots of information on stackoverflow. I tried to use the developer-reserved Design_Time_Addresses solution on port 8732, and then on 8731, but to no avail.

So then I figured how to look for this URL on my PC. From a command shell run:

netsh http show urlacl

Then I spotted the design time addresses URL:

W8x64_2014

Port 8733 !

So I changed my URI to http://localhost:8733/Design_Time_Addresses and everything worked.

This new URI is only for use when running the service in a debugged session via Visual Studio. For normal runtime use I still use the original URI of http://localhost:8000.

Update: I’m now using the following property to get the URI at runtime:

static public string ServerPath
{
    get
    {
        string serverPath = &amp;quot;http://localhost:8000&amp;quot;;

        if (System.Diagnostics.Debugger.IsAttached == true)
        {
            serverPath = &amp;quot;http://localhost:8733/Design_Time_Addresses&amp;quot;;
        }

        return serverPath;
    }
}