StepOver Revisited

Andy Pennell exposed in 2004 (and I mentioned in 2009) a very useful undocumented VC feature: when you wish to avoid stepping into nagging functions (ctors, refcounts, whatever), you can specify them in the StepOver registry key. It supports RegExp’s and some extra syntax, so it was very convenient to specify entire framework classes and namespaces (ATL, MFC, std etc) and save many clicks during debugging.

As the case often turns out for undocumented goodies, the feature started showing some cracks in VS2010. Here is one very noticeable such crack.

When you have two or more VS instances running, they’re likely to compete over access to system resources. As of VS2010, when instances compete over the registry key -

HKCU\Software\Microsoft\VisualStudio\10.0_Config

…there’s a good chance it would be duplicated, with a suffix containing one of the instances’ process ID:

image

However, since access to the original key had failed – the new copy is not an exact duplicate and probably just contains some defaults (which brings up the question of why duplicate it in the first place, but I assume there’s a valid reason).

So there you have it – once such a race occurs, one of the VS instances would be blind to all the customizations you had put into 10.0_Config\NativeDE\StepOver, and you’d be back wasting dozens of clicks stepping in and out of std::shared_ptr copy ctors.

This seems easily fixable, but since it was an underground feature to start with – testing it was probably never part of any requirement for VS2010. Hope it makes it back some day.

Integrating Matlab with Team Foundation Server 2010

I just managed to pull this integration off, and the process definitely deserves more web presence.

First, as of 2011, Matlab (I never understood the shout-ish MATLAB spelling) consumes only MSSCCI source control connections, which a Team Explorer installation does not supply by default. To correct that, install and run the Team Foundation Server MSSCCI Provider 2010 from the VS code gallery.  Beyond the binaries it installs – which seem to act as thin adapters to the real TFS functionality – it populates some registry keys, but mainly:

1. HKLM\SOFTWARE\SourceCodeControlProvider\InstalledSCCProviders:  a string value with a path to a TFS MSSCCI facade registry key

2. HKLM\SOFTWARE\Microsoft\Team Foundation Server MSSCCI Provider: the provider facade, containing at the very least the SCCServerName and SCCServerPath strings.

Now when source control is set from within Matlab/file/preferences, you can already see the TFS option:

It seems until Matlab R2007a, there were some issues with this long name, and you had to manually shorten it. I didn’t witness any such issues so I suppose they’re long resolved.

However – the work isn’t yet done: when you try and perform any source control operation on a file (or sometimes even just edit it), you get error TF249051:

Obviously Matlab still maintains somewhere the previous connection strings (in my case, to Perforce source control). Now this took a deeper dive with Process Monitor, but the culprit was eventually found.

Matlab places the innocent looking file mw.scc, somewhere deep in your user settings folder (in my case, C:\Users\Matlab\AppData\Roaming\MathWorks\MATLAB\R2010b\).

For P4 bindings, the file contains text like:

#Mathworks source code control preferences.
#Wed Aug 10 13:57:16 IDT 2011
d./work/matlab/dailytests.SccAuxPath=P4SCC\#myP4server\:1666\#\#myP4user\#\#myP4workspace
d./work/matlab/dailytests.SccProjectName=Perforce Project

And you have to empty it manually. After you do, Matlab will fill it with valid TFS connection strings, like:

#Mathworks source code control preferences.
#Thu Nov 24 14:43:44 IST 2011
d./work/matlab/dailytests.SccAuxPath=http\://myTFSserver\:8080/tfs/defaultcollection|yadda|yadda\\yadda
d./work/matlab/dailytests.SccProjectName=$/yadda/yadda/yadda

Hope this helps someone out there.

Reading Monitor Physical Dimensions, or: Getting the EDID, the Right Way

We recently needed to know the physical size of monitors on customer machines. Getting it right was a surprisingly tedious research – and definitely something that deserves more web presence – and so the results are below.

1. GetDeviceCaps

- is the immediate answer. The argument flags HORZSIZE / VERTSIZE are advertised to give the -

Width/Height, in millimeters, of the physical screen.

Alas, as many have discovered, GetDeviceCaps just does not work as advertised with these flags.

2. GetMonitorDisplayAreaSize

- is the next obvious guess. The documentation doesn’t state whether the obtained values are in pixels or physical units – I suspect it’s vendor specific, but didn’t get to check it myself since I kept getting the dreadful LastError 0xc0262582: “An error occurred while transmitting data to the device on the I2C bus.     unsigned long”. Gotta say I didn’t insist too much since the entire Monitor Configuration API set is both new to Vista and already ‘legacy graphics’, which are explicitly described as-

Technologies that are obsolete and should not be used in new applications.

3. WMI

There’s a good chance that this Managed Instrumentation code  gets the job done. I didn’t get to test it, since

(1) It is exceptionally complicated (CoSetProxyBlanket anyone? How about some nice IWbemClassObjects to go with that?),

(2) WMI supports monitor classes only since Vista, which makes it irrelevant to most of the world (40%-50% as of Sep 2011).

4. Spelunking the Registry

Unlike what many, many, say, the physical display information is in fact available to the OS, via Extended Display Identification Data (EDID). A copy of the EDID block is kept in the registry, and bytes 21/22 of it contain the width/height of the monitor, in cm. Some have tried digging into the registry directly, searching for the EDID block, but the code in the link didn’t work for me and worked (I guess) for the poster by pure accident: the exact registry path to the EDID is not only undocumented, but does in practice vary from one vendor to another.

This is, however, a step in the right direction – which turned out to be:

5. SetupAPI !

Finally, here’s some code that works almost perfectly, courtesy of Calvin Guan. Turns out there is a documented way of obtaining the correct registry for a device:

  1. Call SetupDiGetClassDevsEx to get an HDEVINFO handle.
  2. Use this HDEVINFO in a call to SetupDiEnumDeviceInfo to populate an SP_DEVINFO_DATA struct.
  3. Use both HDEVICE and HDEVINFO in a call to SetupDiOpenDevRegKey, to finally get an HKEY to the desired registry key – the one that holds the EDID block.

Below is a (larger than usual) code snippet. Beyond some general cleanup, a few fixes were applied to Calvin’s original code:

(1) the REGSAM argument in SetupDiOpenDevRegKey is set to KEY_READ and not KEY_ALL_ACCESS to allow non-admins to run it, (2) Fix a small memory leak due to a missing SetupDiDestroyDeviceInfoList call (thanks @Anonymous!), (3) the monitor size is extracted from the EDID with millimeter precision, and not cm (thanks other @Anonymous!)

#include <atlstr.h>
#include <SetupApi.h>
#pragma comment(lib, "setupapi.lib")

#define NAME_SIZE 128

const GUID GUID_CLASS_MONITOR = {0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18};

// Assumes hDevRegKey is valid
bool GetMonitorSizeFromEDID(const HKEY hDevRegKey, short& WidthMm, short& HeightMm)
{
	DWORD dwType, AcutalValueNameLength = NAME_SIZE;
	TCHAR valueName[NAME_SIZE];

	BYTE EDIDdata[1024];
	DWORD edidsize=sizeof(EDIDdata);

	for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS; ++i)
	{
		retValue = RegEnumValue ( hDevRegKey, i, &valueName[0],
			&AcutalValueNameLength, NULL, &dwType,
			EDIDdata, // buffer
			&edidsize); // buffer size

		if (retValue != ERROR_SUCCESS || 0 != _tcscmp(valueName,_T("EDID")))
			continue;
		
		WidthMm  = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
		HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];

		return true; // valid EDID found
	}

	return false; // EDID not found
}

bool GetSizeForDevID(const CString& TargetDevID, short& WidthMm, short& HeightMm)
{
	HDEVINFO devInfo = SetupDiGetClassDevsEx(
		&GUID_CLASS_MONITOR, //class GUID
		NULL, //enumerator
		NULL, //HWND
		DIGCF_PRESENT, // Flags //DIGCF_ALLCLASSES|
		NULL, // device info, create a new one.
		NULL, // machine name, local machine
		NULL);// reserved

	if (NULL == devInfo)
		return false;

	bool bRes = false;

	for (ULONG i=0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i)
	{
		SP_DEVINFO_DATA devInfoData;
		memset(&devInfoData,0,sizeof(devInfoData));
		devInfoData.cbSize = sizeof(devInfoData);

		if (SetupDiEnumDeviceInfo(devInfo,i,&devInfoData))
		{
			HKEY hDevRegKey = SetupDiOpenDevRegKey(devInfo,&devInfoData,
				DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);

			if(!hDevRegKey || (hDevRegKey == INVALID_HANDLE_VALUE))
				continue;

			bRes = GetMonitorSizeFromEDID(hDevRegKey, WidthMm, HeightMm);

			RegCloseKey(hDevRegKey);
		}
	}
	SetupDiDestroyDeviceInfoList(devInfo);
	return bRes;
}

int _tmain(int argc, _TCHAR* argv[])
{
	short WidthMm, HeightMm;

	DISPLAY_DEVICE dd;
	dd.cb = sizeof(dd);
	DWORD dev = 0; // device index
	int id = 1; // monitor number, as used by Display Properties > Settings

	CString DeviceID;
	bool bFoundDevice = false;
	while (EnumDisplayDevices(0, dev, &dd, 0) && !bFoundDevice)
	{
		DISPLAY_DEVICE ddMon;
		ZeroMemory(&ddMon, sizeof(ddMon));
		ddMon.cb = sizeof(ddMon);
		DWORD devMon = 0;

		while (EnumDisplayDevices(dd.DeviceName, devMon, &ddMon, 0) && !bFoundDevice)
		{
			if (ddMon.StateFlags & DISPLAY_DEVICE_ACTIVE &&
				!(ddMon.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
			{
				DeviceID.Format (L"%s", ddMon.DeviceID);
				DeviceID = DeviceID.Mid (8, DeviceID.Find (L"\\", 9) - 8);

				bFoundDevice = GetSizeForDevID(DeviceID, WidthMm, HeightMm);
			}
			devMon++;

			ZeroMemory(&ddMon, sizeof(ddMon));
			ddMon.cb = sizeof(ddMon);
		}

		ZeroMemory(&dd, sizeof(dd));
		dd.cb = sizeof(dd);
		dev++;
	}

	return 0;
}

SetupAPI is still not the most pleasant of API sets around, but as MSFT’s Doron Holan replied to a user preferring to dig in the registry himself:

Programming is hard. Plain and simple. Some problems are simple, some are hard. Some APIs you like, some you don’t. Going behind the back of those APIs and getting at the data yourself will only cause problems for you and your customers.

I actually had to query the dimensions of a specific monitor (specified HMONITOR). This was an even nastier problem, and frankly I’m just not confident yet that I got it right. If I ever get to a code worth sharing – I’ll certainly share it here.