Saturday, September 15, 2007

Upgrading PEAR on Red Hat Enterprise ES 3

Recently we've upgraded our coding standards for our server-side PHP development to include unit tests, which required that we install PHPUnit for PHP 4. Normally installing a new pear module takes about ten seconds, like this:

pear install PHPUnit

Unfortunately, this time things didn't go so smoothly. I ended up with dozens of errors such as this one:

Warning: xml_parse() [http://www.php.net/function.xml-parse]: Unable to call handler _pkginfo_cdata_2_0() in Common.php on line 758

The list of errors ended with:

Notice: Undefined index: package in Common.php on line 1122
The following errors where found (use force option to install anyway):
missing package name
missing summary
missing description
missing license
missing version
missing release state
missing release date
missing release notes
no maintainer(s)
no files

If you look up these errors on Google, you get very few hits. Justin Patrin correctly identifies the problem: Red Hat ES 3 ships with version 1.1. This version of pear is considered ancient and doesn't support reading the latest packages.

You can check your version of pear with this command:

pear -V

Justin recommends the standard command for upgrading pear:

pear upgrade pear

Unfortunately, this command doesn't work when your current version of pear is so far out of date. In fact, the net result of this command is that the "pear" command is removed and not replaced. If you follow the link, you'll see a bug about this in the pear bug database, where the problem is considered "Bogus" (not a bug) because the author of the bug is running a "museum PEAR version (RHE3, pear 1.1)." Unfortunately, there is no discussion of how to actually perform the upgrade, since the command "pear upgrade pear" is completely broken in RHE3. I find it absurd that anyone who supports enterprise software would think that it's okay to simply orphan customers who are running software four years old.

I found several articles on resolving problems with old versions of pear. All of them recommended reinstalling pear from scratch, similar to this:

lynx -source http://go-pear.org/ php

Unfortunately, this didn't work for me. The Red Hat Package Manager (rpm) has very definite ideas as to where pear should be installed. However, rpm bundles pear with php, so you cannot simply uninstall Red Hat's pear and then install the standard version. In my case, I was unable to determine how to get go-pear to align its directory structure with rpm, and I ended up with a schizophrenic installation.

I removed all of /usr/share/pear and restored the directory from backup. I then double checked the installed packages:

pear list

Pear reported that no packages were installed. Since the Red Hat version of pear actually ships with several packages, it was clear that my installation was now completely broken.

After several hours of searching, I finally found on article by Patrick Berry titled Upgrading PEAR on RHEL 4. Although I was running RHEL 3 and not 4, the solution described gave me enough information to solve the problem.

The problem is that version 1.1 of pear cannot read the package to install the latest version, which is 1.67 as of this blog entry. Therefore, you have to go through some intermediate upgrades before eventually arriving at version 1.67.

I first tried following the steps under Patrick's Solution section. It didn't work for me - I received the error pear is not installed. This error appears to have been caused by my earlier attempt to install from go-pear. After much frustration, I solved this problem by removing the file /root/.pearrc.

However, Patrick's steps under Solution still didn't work for me. If I had bothered to read the Update AND the Comments, I would have seen the latest correct upgrade procedure, as described by Daniel a mere ten days ago:

pear upgrade pear-1.3.3
pear upgrade pear-1.4.11
pear upgrade

However, AGAIN this procedure didn't work for me. (Are you sensing a trend here?) When I tried to go from 1.3.3 to 1.4.11, I received an error about "getopt2 not found." It's not clear what caused this problem, but I saw a similar issue reported by others, again with no solution.

To resolve this problem, I had to manually download an interim version of Console_Getopt and copy it to /usr/share/pear/Console. The latest version was too new and version 1.21 was too old. One of the versions in the middle included getopt2 and didn't rely on any other unsupported functionality.

Once I put in place a newer version of Console_Getopt, I was able to upgrade to pear 1.4.11, and then to the latest version, with these commands:

pear upgrade pear-1.4.11
pear upgrade-all

The upgrade-all command also fixes the pear package manager so it shows the correct version of Console_Getopt.

After all of this effort, my installation of pear finally worked again, after wasting a mere two days figuring out all of the problems. (Begin Sarcasm) A big thank you to both Red Hat and the "pear" package manager maintainers for abandoning users of Red Hat Enterprise 3.(End Sarcasm)

Late-Breaking Information:
I just learned that the PEAR home page says that support for PEAR 1.3.6 and earlier will be dropped on January 1, 2008. Since today is still four months before that date, it seems that they've jumped the gun a little bit. However, they do give the definitive steps on how to upgrade your PEAR installation:

pear upgrade --force PEAR-1.3.6 Archive_Tar-1.3.1 Console_Getopt-1.2
pear upgrade --force PEAR-1.4.11
pear upgrade PEAR

However, it's impossible to find this from any search engine because they don't list the symptoms or the affected operating systems, nor do they refer to this procedure in Bug #5906 that I mentioned earlier.

Even More Late-Breaking Information:
Turns out that my pear installation still had issues because of all of the experimentation, so I had to wipe it and start over. This time I was able to get go-pear to work by following the instructions on this web page:
http://aspn.activestate.com/ASPN/Mail/Message/pear-general/1305645

Sunday, August 26, 2007

How To Create 32-bit Import Libraries Without .OBJs or Source

This article is intentionally titled the same as Microsoft KB article 131313 (was Q131313) because that KB article does not give enough information for you to create a .LIB file that works with Windows system DLLs.

There are some functions in the Win32 API that have no import library. One example is the function named RemoveControlByName() in OCCACHE.DLL. According to the documentation, the only way to use the function is to use LoadLibrary() and GetProcAddress(). This is error-prone, requires a lot of code to maintain, and isn't usable with the Delay-Load feature of VC++.

Obviously it would be preferable to have a .LIB to link against, but creating such a library is challenging. If you use Dependency Walker to look at OCCACHE.DLL, you'll see that the function is named simply RemoveControlByName. While that seems obvious, it shouldn't be possible to have this name because it doesn't include any notation for the calling convention.

If the function were __cdecl, then the function name should have started with an underline, such as _RemoveControlByName. If the function were __stdcall, then the underline should have been added as well as suffix to indicate the number of bytes in its call stack. RemoveControlByName has five 4-byte paramters, so a __stdcall signature should have looked like _RemoveControlByName@20. However, the function name has no decorations at all, which should be impossible according to Microsoft's discussion of name decoration.

The Q131313 article discusses the general case of manually creating a .LIB file for a .DLL file. The discussion under Creating a .DEF File says "The reason for this limitation is based on an assumption made by the LIB utility that all names are automatically exported without a leading underscore." This seems promising because we don't have a leading underscore. However, functions exported from Windows DLLs(almost) always using __stdcall, and the discussion under Creating a .DEF File is only applicable to __cdecl.

In spite of those warnings, I spent quite a bit of time trying to craft a .DEF file that described what I was trying to do. (You can use LIB.EXE to compile a .DEF file to a .LIB without any .OBJ files) Although most .DEF files simply list the undecorated function names under the EXPORTS section, the syntax of the .DEF file allows for creating aliases and other strange constructs. Some of the things I tried included:

; Raw function name
RemoveControlByName

; Alias the undecorated name to __stdcall
RemoveControlByName=RemoveControlByName@20

; Explicit reference to the necessary DLL
RemoveControlByName@20 = OCCACHE.RemoveControlByName

However, none of these generated a .LIB that worked. The first two I couldn't link against and the third would link but fail to run.

I thought I'd learn something by looking at the symbol that the parent program is trying to link against, but that was even worse:

__imp_?RemoveControlByName@20

I knew my header file was correct, so that was definitely the correct name. However, you'll notice the leading "__imp", which indicates that the symbol is being imported from a DLL. This was something else that apparently needed to be included in my hand-crafted .LIB file, and I hadn't seen any discussion anywhere on how to do that.

I've tried to solve this problem two other times in the last several years, and this was the point I gave up in both of those cases. However, this time, failure was not an option. I needed the solution.

I tried the second option in the KB article, described under Stubbing Out Functions. I painstakingly created a dozen functions that mimicked the signatures of the functions listed in the header file. If you are doing this for yourself, here's a tip for creating a Visual Studio project. The final file you need is a .LIB file, so it's tempting to use one of the Visual Studio projects for creating a .LIB. However, that's wrong. If you created a .LIB, then you'll link with the stub functions you created, which is useless. What you really want is a DLL project, which happens to create a .LIB as a by-product.

Anyway, I created the functions as described in the KB article. Since my project used .cpp files, all of the calls in my header file had to be declared extern "C". For example:

extern "C" {
#include "occache.h"
};

If you look in any of the standard Windows include files, such as winbase.h, you'll see this same declaration. Note that this declaration has no relation to _cdecl and therefore has no impact on the calling convention. In other words, it doesn't force all of the functions in occache.h to be called with _cdecl. What this declaration does is to modify all of the linker symbols so that they won't include the C++ name decorations, which encode all of the function's parameters into the function name.

I also updated all of the function signatures in the header file to include __declspec(dllexport).

I compiled it, linked my main application to the new .LIB file, and it linked! I thought I was done, but when I ran the application, I received the error "Entry Point Not Found: The procedure entry point _RemoveControlByName@20 could not be located in the dynamic link library OCCACHE.DLL"

I examined the .LIB with DUMPBIN. Under Public Symbols, I now see two definitions for RemoveControlByName:

_RemoveControlByName@20
__imp__RemoveControlByName@20

It appears that the "__imp" definition was a result of adding __declspec(dllexport), so that explained why the application linked successfully. One problem solved.

Continuing my examination of the DUMPBIN information, I saw that RemoveControlByName was defined as:

Archive member name at FE0: OCCACHE.DLL/
46D12949 time/date Sun Aug 26 00:18:33 2007
uid
gid
0 mode
38 size
correct header end

Version : 0
Machine : 14C (x86)
TimeDateStamp: 46D12949 Sun Aug 26 00:18:33 2007
SizeOfData : 00000024
DLL name : OCCACHE.DLL
Symbol name : _RemoveControlByName@20
Type : code
Name type : name
Hint : 9
Name : _RemoveControlByName@20

To determine whether or not this was correct, I used DUMPBIN to compare against known-good definitions in USER32.LIB, where I found that the "Name type" in the USER32 records was defined as "undecorate" instead of "name". Obviously, there was a magic incantation to set this flag, presumably in the .DEF file.

I added a .DEF file and spent several hours trying various alias combinations, none of which worked. Finally, in desperation, I created a .DEF file that contained just the raw function names. For example:

EXPORTS
RemoveControlByName

I built my application, it linked, and it ran. What happened?

I ran DUMPBIN again on my library. The record describing RemoveControlByName now contained the "undecorate" attribute:

Archive member name at FFC: OCCACHE.DLL/
46D12740 time/date Sun Aug 26 00:09:52 2007
uid
gid
0 mode
38 size
correct header end

Version : 0
Machine : 14C (x86)
TimeDateStamp: 46D12740 Sun Aug 26 00:09:52 2007
SizeOfData : 00000024
DLL name : OCCACHE.DLL
Symbol name : _RemoveControlByName@20
Type : code
Name type : undecorate
Hint : 6
Name : RemoveControlByName

Also, the very last line in the record showed that the "Name" was RemoveControlByName, with no decoration. Exactly what I needed.

It's clear that there's quite a bit of undocumented behavior here. Adding the entry to the .DEF file had a rather dramatic effect on the generated library. I couldn't find any mention of this behavior in the documentation on .DEF files. The only relevant reference I could find was in Microsoft's documentation under __cdecl, where it says "Underscore character (_) is prefixed to names, except when exporting __cdecl functions that use C linkage." This statement is true, but it's not the whole truth. To create a .LIB file that can link against such a construct, you also must have the function declared in a .DEF file.

In summary, to create a .LIB file that will let you link with Windows system DLLs, you need to:
  1. Follow the instructions in Q131313 under Stubbing Out Functions. Make sure you name the project the same as the Windows DLL.
  2. Make sure your dummy functions are defined with __declspec(dllexport) as well as __stdcall.
  3. For the header file used by the parent application, make sure that your function declarations are surrounded with extern "C".
  4. Add a .DEF file to your project that includes the function names with no decoration.

Saturday, August 25, 2007

Media Player Slows Network in Vista

If you've read my earlier blog posts about Gigabit Ethernet, you know that I've had my share of difficulty getting good performance out of my GigE network. I've also posted that I've had problems with Vista that I didn't see in Windows XP or Windows Server 2003. Now I know why. Windows Media Player puts a big throttle on GigE network performance, even if it is paused and not playing anything.

The issue was reported at 2CPU.com and a response from Microsoft was reported at ZDNet. The problem is minor on 10/100 Ethernet, but on a GigE Ethernet the network performance can be throttled back to 100Mps levels. Apparently, there's no registry setting that will resolve the problem, the only solution is to shut down Windows Media Player. There are scattered reports that the problem happens with other media players, such as WinAmp, but I haven't confirmed these reports.

There's another problem I also ran into that caused GigE to throttle back to 100Mbps. This was a self-inflicted problem, but it took several weeks to resolve. I have a LinkSys WRT54G router that handles my gateway/firewall/NAT. All of my servers get their IP address with DHCP and the WRT54G is set to always hand out the same IP address to those servers. This was done so that the servers would get the DHCP settings from our provider. Anyway, one of the servers was migrated to a new motherboard with a different MAC address. The WRT54G started handing out a random DHCP address to that node but still reported that node name as having the other, assigned IP address, all of which made the network become schizophrenic over the IP address assigned to that particular node name. The result was that any traffic that was destined for that node ended up going to the gateway, which didn't have a GigE connection, so traffic was throttled back to 100Mbps speeds.

Update 8/28/2007 - Mark Russinovich has posted a detailed analysis of the network slowdown. It turns out that the more Network Interface Cards (NICs) you have in your system, the worse the problem gets. I have three NICs, including WiFi, which slows my performance to a theoretical maximum of 9MB/second.

Wednesday, June 20, 2007

ReadyBoost Damages USB Thumb Drives

Windows Vista ReadyBoost destroyed a second thumb drive yesterday. For the past three months I've just been leaving my computer on at night instead of trying to make ReadyBoost work with Sleep/Standby. Yesterday I was trying something new and I put the system in Sleep mode. I woke it up two hours later and the thumb drive had stopped working. I unplugged the thumb drive and replugged it and I got a "write protect" error. I tried the thumb drive on another computer and got the same result.

I downloaded the Format utility from the Apacer web site. I successfully reformatted the drive, enabled ReadyBoost, and was working again. For a few minutes. Then the light on the thumb drive turned on steady and the drive completely died. I tried it on three different computers and three different versions of Windows.

This is the second thumb drive that's failed when using ReadyBoost and resuming from Sleep. I've heard reports from readers of this blog that they have had a similar problem. How about you? Leave a comment.

Note that I don't believe that this has anything to do with the number of write cycles, as some journalists have complained about. In both cases the drive died immediately after resuming from Sleep.

Wednesday, June 6, 2007

Dell PowerConnect 2708 Performance

Just a quick note to say that the Dell PowerConnect 2708 is fantastic. I was able to get a sustained 112MB per second (that's 112 megabytes, not megabits) between two Core 2 Duo machines (Vista/Windows 2003 Server R2) using PCATTCP. That means that the connection is completely saturated and that the Dell box is switching at the maximum possible speed. Sweet.

In more real world numbers, I was able to get sustained 2.2GB/minute transfer rate using normal Windows file sharing copying from Vista to the server when the file copy was initiated on the Windows 2003 Server side. That's close to the maximum read speed for the source disk, so presumably the disk was the bottleneck and not the network.

Oddly enough, when the file copy was initiated on the Vista side, I only got about 1.2GB/minute with the same file being copied to the same destination. I can't explain that.

[Edited 8/27/2007 to add information on the read speed of the disk.]

Tuesday, June 5, 2007

Logo Certification - Success!

Last night we received word from Lionbridge that we had passed our Windows Vista Logo Certification test. Thus I've crossed the finish line of a journey that started in May of 2006, bringing to close a year-long project to make it all happen. It was a monumental effort to bring all of the pieces together because all of the vendors we rely on had to make their products Vista compliant before ours could pass certification.

There were several "firsts" to this project. I created an MSI installer for the first time. I installed a 64-bit version of Windows for the first time. We ported our code base to VC2005. I worked with Microsoft Consulting Services for the first time. And, not surprisingly, I did my first serious development work under and for Vista. So it's been quite an adventure.

The most important of these was our MSI install generator, Advanced Installer. You may wonder why we'd go with a relatively unknown install system instead of going with one of the "big boys" like InstallShield. The reason is that, between 1991 and 1998, I had to write several installers in InstallShield and and my company paid a lot of money for the privilege of doing so. I found InstallShield to be buggy and user abusive and The Stirling Group (the manufacturer) to be unresponsive. I stopped recommending them a long time ago.

The developers at Advanced Installer worked closely with all interested customers to make sure their installers could pass Vista certification. In several cases, they gave me information that the Vista Logo Certification tests were wrong and what they were doing was correct. In every one of those cases, Microsoft admitted that the Advanced Installer developers were correct.

Monday, June 4, 2007

Isolated COM

One of the worst documented features of Visual Studio is Isolated COM. Actually, there's lots of documentation on Isolated COM, but none of it is particularly useful. The mechanics of making Isolated COM work under Visual C++ are actually pretty easy, so this short article tells you what to do.

Isolated COM allows your application to use ActiveX components without having to register them. The original vision of this was to allow XCOPY deployment of the application, but Isolated COM has many benefits. You can have a private copy of the DLL without worrying that another application will install an older or newer copy that breaks your application. Isolated COM also allows you to successfully install and run on non-Administrator accounts.

A short history of how activation works: In Windows 9x and NT, ActiveX components were found by looking in HKEY_CLASSES_ROOT, which was really a link to HKEY_LOCAL_MACHINE\Software\Classes. Windows 2000 added support for also defining components in HKEY_CURRENT_USER\Software\Classes and HKEY_CLASSES_ROOT became a merged view of HKLM and HKCU. Finally, Windows XP and Windows Vista added support for looking for COM information in the manifest of the executable. This means that the manifest contains the DLL name, CLSID information, interface information, and type library pointers - most of the information that would normally appear in HKCR.

As you might guess, these definitions can be quite lengthy and creating them by hand is error prone. Visual Studio 2005, which supports manifests, includes support for automatically generating and embedding the correct XML declarations.

To switch from "old fashioned" ActiveX usage to Isolated COM, follow these steps:

1. First, make sure components are created with their CLSID and not the ProgId. In other words, don't use Word.Document, use {F4754C9B-64F5-4B40-8AF4-679732AC0607}. If you are using the #import command in Visual C++, you can use the __uuidof operator instead of a string. For example:

HRESULT hr = pDoc.CreateInstance("Word.Document");
HRESULT hr = pDoc.CreateInstance(__uuidof(Word::Document));

2. If you made any changes for #1, test your code with the DLL registered the normal way and make sure it works. Also make sure that your code checks and handles all HRESULT return values.

3. Unregister the ActiveX DLL using: regsvr32 /u waycool.dll

4. Copy the DLL to be in the same directory as your executable.

5. In Visual Studio 2005:
  • Open the property sheet for the project.
  • Click the plus sign next to "Manifest Tool"
  • Click on "Isolated COM".
  • Next to "Type Library File" enter "waycool.tlb"
  • Next to "Component File Name" enter "waycool.dll"
  • Build the project.
6. Run your application in the debugger and single step through all of the activation code to make sure it is working properly.

Once you've added support for Isolated COM, don't forget to update your installer so that ActiveX components aren't automatically registered under Windows XP and Vista. Also don't forget to retest your software under Win9x and Win2K if you support those versions of Windows.