Tuesday, December 09, 2008
« Battling SQL User Instance failures to s... | Main | Utah .NET User Group - Silverlight 2 »

Alright, this has been a downright stumper and I'm currently at a loss.  For this reason, therefore, I'm opening up to the community to see if anyone out there has any recommendations to this dilemma.

The issue revolves around running a 32-bit application on a 64-bit version of Windows and the application needs to pull values from the Registry.  One thing that 64-bit Windows will do, to help achieve compatibility with 32-bit applications by transparency, is remap (redirect) registry calls.  In other words, when I request "HKLM\SOFTWARE\Test Company\Application Name\Key Name" it actually redirects this to "HKLM\SOFTWARE\Wow6432Node\Test Company\Application Name\Key Name" for storage and retrieval.  So far so good.

Well, I have an old 32-bit application written in VB 6.0 that writes a value to the registry that my .NET application must read.  For argument sake, let's say that the value is ultimately written to the HKLM\SOFTWARE\Wow6432Node\Company\Application\Settings key with a name of Value01.  This is precisely what I'd expect.  However, when I attempt to read this value in my .NET application (I'm using VS 2008, .NET 3.5) I'm getting erratic results.

This is what I have:

My application is explicitly compiled as a 32-bit application.  To do this, I've set the Platform target project property to x86.  I've also confirmed this using the CorFlags.exe, even going to far as using the /32BIT+ switch to ensure the setting.

I have a few varieties of code that seem like they should successfully read the value:

Take 01:

private static string getValue01() {
   return getCompanySetting("Application", "Settings", "Value01", "Not found");
}

private static string getCompanySetting(string appName, string section, string key, string defaultValue) {
   string subKey = string.Format(@"SOFTWARE\Company\{0}\{1}", appName, section);
   return getRegValue(subKey, key, defaultValue);
}

private static string getRegValue(string subKey, string key, string defaultValue) {
   RegistryKey key = Registry.LocalMachine.OpenSubKey(subKey);
   if ( null != key ) {
      object val = key.GetValue(key);
      return ( string.IsNullOrEmpty(val) ? string.Empty : val.ToString();
   }
   else {
      return new Win32Exception(Marshal.GetLastWin32Error()).Message;
   }
}

Take 02:  This is almost the same except that I use the WinAPI directly to read the value rather than the managed objects.

private static string getValue01() {
   return getCompanySetting("Application", "Settings", "Value01", "Not found");
}

private static string getCompanySetting(string appName, string section, string key, string defaultValue) {
   FieldInfo fi = Registry.LocalMachine.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)[0];
   SafeHandle hKey = ( SafeHandle )fi.GetValue(Registry.LocalMachine);
   string subKey = string.Format(@"SOFTWARE\Company\{0}\{1}", appName, section);
   return getRegValue(hKey, subKey, key, defaultValue);
}

private static string getRegValue(SafeHandle hKey, string lpszSubKey, string szKey, string szDefaultValue) {
   StringBuilder szBuffer = new StringBuilder(255);
   uint lBufferSize = ( uint )szBuffer.Capacity;

   int lResult;
   IntPtr phkResult;
   uint lpType;

   lResult = WinApi.RegOpenKeyEx(hKey, lpszSubKey, 0, 1, out phkResult);
   if ( WinApi.ERROR_SUCCESS != lResult )
      return new Win32Exception(Marshal.GetLastWin32Error()).Message;

   lResult = WinApi.RegQueryValueEx(phkResult, szKey, 0, out lpType, szBuffer, ref lBufferSize);
   WinApi.RegCloseKey(phkResult);

   return ( WinApi.ERROR_SUCCESS == lResult )
               ? szBuffer.ToString()
               : szDefault;
}

internal static class WinApi {
   internal const int ERROR_SUCCESS = 0;
   internal const uint KEY_WOW64_64KEY = 0x0100;
   internal const uint KEY_WOW64_32KEY = 0x0200;

   [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "RegOpenKeyEx")]
   internal static extern int RegOpenKeyEx(SafeHandle hKey, string subKey, uint options, uint sam, out IntPtr phkResult);

   [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "RegQueryValueExW", SetLastError = true)]
   internal static extern int RegQueryValueEx(IntPtr hKey, string lpValueName, int lpReserved, out uint lpType, StringBuilder lpData, ref uint lpcbData);
 
   [DllImport("advapi32.dll", SetLastError = true)]
   internal static extern int RegCloseKey(IntPtr hKey);
}

In both of these cases, the code runs flawlessly when I run it from Visual Studio with the debugger attached (e.g., F5).  However (and here's the rub) it fails to retrieve the value when I run it directly within Windows or in VS without the debugger (e.g., CTRL+F5).

In all my investigations, it appears that the application IS indeed a 32-bit image (Process Explorer confirms it), but it will simply not redirect to the \Wow6432Node unless it's running with the debugger attached.

Any thoughts of how to address it?  It's driving me nuts.

Tuesday, December 09, 2008 8:03:39 PM (Mountain Standard Time, UTC-07:00)
Well, I took your exact code and compiled it in 64-bit, 32-bit, release, and debug on Vista 64 Ultimate. I then created by hand the entries in each branch of the registry.

Running each version produces the correct results both with VS debugger attached and not.

One thing you may want to look into is rights, if you are running VS at admin privs it may have access to registry keys that the app alone wouldn't running at user level privs.

Also, create the same keys in each branch of the registry but make the value of the ultimate key different in each branch, this way you will know if it is redirecting improperly or just not getting a value.
Tuesday, December 09, 2008 9:21:25 PM (Mountain Standard Time, UTC-07:00)
Thanks for the feedback, Josh.

I had considered security to be a factor, but I'm not sure that's the case. I'm not running VS as an admin and even if I attempt to run the application via Explorer via 'Run as Administrator' it will not read the key. The application will always fail with a "The system cannot find the file specified" error.

One interesting thing of note, is that I cannot locate the key created by the VB 6.0 app via regedit. It simply does not exist, yet I can query it and retrieve the value when I run my app in VS with the debugger attached. I've never seen that before either. I don't know where the value is actually being stored.

If I manually create the key and assign an arbitrary value (say, 'Test' where the VB-created value is 'Original'), when I run the app in VS w/debugger I'll retrieve 'Original'. However, when I run the app w/o debugger I get 'Test'.

Maybe it's a CAS issue, perhaps?
Wednesday, December 10, 2008 11:03:49 AM (Mountain Standard Time, UTC-07:00)
That is quite odd. I would grab Process Monitor and put a filter in for your VB6 exe name and see what it is doing when it writes to the "Registry".
Sunday, May 31, 2009 9:49:20 AM (Mountain Standard Time, UTC-07:00)
Greeting. The release of atomic energy has not created a new problem. It has merely made more urgent the necessity of solving an existing one.
I am from Leone and also now teach English, tell me right I wrote the following sentence: "Will allergic conjunctivitis damage my eyesight? If you are allergic to pollen or mold, you can take anti allergic medications for the mean time."

Thanks for the help :(, Rupert.
Comments are closed.