Windows Is Lying To You… With DPI Virtualization

DPI Virtualization in Windows

Windows Vista (and subsequently, Windows 7 & 8) introduced a new feature called DPI Virtualization.

DPI Virtualization means Windows will automatically “resize” your forms and form objects for you, with no coding or resizing required on your part.

Sounds great, doesn’t it? Well, not quite. Remember my “ugly” form in an earlier post, running at 144dpi? It was DPI Virtualized by Windows 7, and the result was a blurry window.

DPI Virtualization also requires the code listing from a previous post to fail, every time. Why? Because when the code asks Windows, “Hey, what’s the current setting?”, Windows Vista, 7 and 8 will always answer “your font factor is 1” (no resizing required) if the user has selected to use DPI Virtualization.

“Whoa, whoa, whoa, Kevin! If the user has selected DPI Virtualization?”

Yes. Windows Vista, 7 and 8 have two different modes of DPI scaling: XP Style, and DPI Virtualization.

Setting the DPI scale mode in Windows 7

Setting the DPI scale mode in Windows 7

If the user changes her DPI setting, and checks the “Use Windows XP style DPI scaling” checkbox, Windows Vista, 7 and 8 will return the expected (truthful) result from the previous code listing – just like XP did.

So, a DPI of 144dpi (150%) with this box unchecked, results in the following:

The correct font factor with XP style scaling turned on

The correct font factor with XP style scaling turned on

And, just like I would do with the XP result from earlier, I would want to resize my forms and form objects, making them 50% larger than “normal.”

But if the user has not checked the box, here’s what Windows tells me about the “font factor” (notice the messagebox is also a bit blurry):

Thanks, Windows, for lying and making my forms blurry

Thanks, Windows, for lying to me and making my forms blurry

Great. The user has selected 144dpi, but my app does not know this because Windows has said, “Don’t worry, no need to resize anything. All is good.”

And my user gets to see “somewhat” resized, yet blurry, forms in my app.

What we need is a “Windows Lie Detector” to make sure Windows stops lying to us.

How do we do this?

We need to tell Windows that the app is “DPI Aware”.

And that’s exactly what we’ll look at in tomorrow’s post.

 

Related Links (With much better explanations)

High DPI Settings In Windows
Fixing Windows Programming

 

SET PROCEDURE TO MyDll

I use the Microsoft Web Browser Control as the primary UI for an app. This means I need a ton of support files like cascading stylesheets, javascript files, images, etc.

Typically, I create a subfolder in the Windows “Temp” (SYS(2023)) folder for the HTML files, then subfolders for each of the support file types (CSS, JS, IMG). I don’t like to install the support files with the app, because there’s always someone out there that wants to modify the files or just poke around to see how some things get done.

In the past, I simply included the support files in the EXE and did a series of STRTOFILE()’s to copy the files to the temporary location. But as the app (and the number of support files it uses) grows, it really bloats the EXE. So I created a DLL which contained all the included files, with methods to do the file copying.

During startup, the EXE instantiates the OLEPUBLIC classes in the DLL and fires the methods to copy the files. Works great, and it keeps the size of the EXE a bit smaller.

Yep, works great. Except when the DLL doesn’t get properly registered during the install, and the instantiation fails. I get error reports from time to time where the DLL hasn’t been registered. Though I’ve been unable to recreate it here, it seems the common denominator is a particular anti-virus software solution – I’m looking at you, Kaspersky.

So, I’m stuck with needing to manually register the DLL if it hasn’t been registered. I suppose I could use Reg-Free COM, manifest files, elevation, etc. to register the DLL programmatically, but I worry about going through all of that only to find that it still won’t register for the same reason it didn’t during installation (I’m still looking at you, Kaspersky).

I stumbled on an idea earlier this week and thought I’d give it a try. The idea was to SET PROCEDURE TO MyDll. Why not create a DLL, then, instead of going through registering it, just SET PROCEDURE to it and call the methods?

Duh. Can’t do it. It’s an object method, and I haven’t created the object.

So I did a little refactoring on the DLL. It contains one OLEPUBLIC class, which is nothing more than a class definition – no properties, no methods, no nothing. Then I split the methods out to simple FUNCTIONs.

Recompiled the DLL, then got rid of the registration info that was added to the Windows Registry. Then I tried the SET PROCEDURE TO.

The pseudocode looks like this:

IF MyDll.dll Exists then
   SET PROCEDURE TO MyDll.dll
   MyFunction()
   RELEASE PROCEDURE MyDll.dll
ENDIF

And, it worked! No registration, just a simple check to see if the file exists. If it does, run the method. This assumes, of course, that the method exists (since there’s no PEMSTATUS I can check – remember, no object or class exists).

Now for the disclaimer (before the comments start rolling in):

  1. Yes, I am well aware that SET PROCEDURE TO was not designed to be used this way.
  2. Yes, I know the best way to “fix” the problem is to determine what the problem actually is and find a “real” solution.
  3. Yes, I know this wouldn’t be considered “best practice”.

On the other hand, though, my purpose for this is pretty specific, and was completed very quickly. Plus, with a little bit of coding on the app’s update procedure, I can now update the DLL anytime I need to, without having to install a whole new update. Best of all, it appears that the problem has been solved.  I still try to instantiate the object first, but if it fails I fallback to the new “unregistered” DLL. Not a single error report since the last update.

So far, so good…

I’ve Seen This Before. It’s An Outlier.

UPDATE: Finally figured out this issue. I was encrypting an XML file, which caused some illegal characters to appear from time to time (the “outlier” instances). Took out the header of the XML file, and everything works as expected.

I recently added some functionality to an app which allows the app to login to a web service with a username and password. Since I want to retain the login information (so the user doesn’t have to be prompted to enter their credentials every time), I store the username and password in an XML “settings” file.

I’ve been using Craig Boyd’s excellent VFPEncryption FLL to encrypt/decrypt the password. The user enters her username and password, the password is encrypted, and the username and password are written to the XML file. It’s been working great on thousands of computers.

Except for four computers.

When the app needs to retrieve the username and password, it opens the XML file, decrypts the password, and tries to login to the web service. But on these four computers, the decryption crashes with “API call caused an exception”.

Working with a customer on this particular problem, I pointed him to the XML file and asked him if he would be willing to change his password for the web service – just to see if maybe we could get past the crash.

He changed a couple of characters in his password, and voila! – it worked with no problem.

So he went back to his original password and made note of the encrypted password which was stored in the XML file. Oddly, the encrypted password contained an embedded carriage return:

<PASSWORD>?-arP?­Z?=¤¨?? ;?¥
?8??*ù©y</PASSWORD>

He changed a couple of characters, and made note of the encrypted password again:

<PASSWORD>ðF’1L’|„ žU­MU¨èˆfâ‚?TÀn Çvõ’U</PASSWORD>

Freaky, huh? When I run the code from the app via the Visual FoxPro Command Window, including the STRTOFILE() and FILETOSTR() lines to write and read the XML file, it works with no problem.

The customer, a former software developer, tried 16 more random passwords – just to see what would happen. Every one of them worked flawlessly.

So, after successfully trying 17 passwords he went back to the original. Same problem: an embedded carriage return in the stored encrypted password, and an API exception when decrypting it.

The last thing he said to me was:

Then it hit me, not the first time I’ve seen this. It’s an outlier. There is a very good reason why this is happening. You may close the ticket now. The function is working fine.