Showing posts with label C++. Show all posts
Showing posts with label C++. Show all posts

Sunday, July 31, 2005

Trouble ahead for time computations?

I was just reading an article about the new U.S. Energy Bill that intends to extend Daylight Savings Time an additional month (starting in 2007). I wonder what the impact of this is on the Computer Science front. There has not been any chatter on my normally read blogs, but perhaps there will be soon. I have no facts to back up my theory here, but lets play along with my conspiracy theory and possibly learn something about static and dynamic linking of DLLs.

Think about this from the Microsoft C/C++ developer perspective: the date functions are implemented in the C standard library, MSVCRT.DLL (or its post VC6 brethren MSVC*.dll). Lets assume (although I am guessing) that the "rules" for the current daylight savings time are implemented in this same DLL (NOTE: Please see comments). One of these functions is called mktime(). Let me quote from MSDN:

When specifying a tm structure time, set the tm_isdst field to 0 to indicate that standard time is in effect, or to a value greater than 0 to indicate that daylight savings time is in effect, or to a value less than zero to have the C run-time library code compute whether standard time or daylight savings time is in effect. (The C run-time library assumes the United States's rules for implementing the calculation of Daylight Saving Time).
So if we developers assumed that the runtime would take care of conversions for us, and we statically linked to the library, we may have to recompile against a newer link library (with the revised code) for our applications to continue working if this change actually happens. On the other hand, had the developer dynamically linked to this DLL and not copied the pre-dst-updated MSVCRT.DLL to the application folder or application current directory (Remember that in Windows Server 2003 and possibly Windows XP the rules have changed), the DLL can be updated once in the system32 directory (perhaps through Windows Update) and our preexisting applications will be none the wiser and work just fine with the change.

This is more food for thought on the debate of static vs. dynamic linking that could help sway some developers into a different line of thought - I prefer dynamic linking, but in some cases for some applications I do not practice what I preach. For the installation folks, please read the previous link (and the comments - good stuff there on merge modules) - this is why you need to be sure that you are following proper version replacement and marking shared DLL's as shared - otherwise you can cause grief for other applications or your own.

[UPDATE: 10/4/2005 - fix formatting and errant character]

Monday, April 25, 2005

Installing an Internet Explorer ActiveX Control Part II

In part I of this article, I expressed concern over the various methods of installing an ActiveX control normally distributed and/or updated in a CAB file via Internet Explorer. This follow-up post addresses some concerns of a commenter to that last post, and describes what my solution was.

The comment made by Troy was from the perspective of a repackaging solution. Troy is quite correct - using pre and post installation snapshots will grab the changes to the ModuleUsage subkey. However, this solution fails to achieve the other goals for this particular installation. We still have the issue of a later update coupled with the MSI repair (or self-repair) operation. A point I failed to mention but Troy unintentionally reminded me of is compatibility across versions of IE. With the type of software this is, it may have a shelf life that would include IE7. Are the changes made to the system under IE6 applicable to all versions of IE under all OS's? I don't know. Using the API to install the control alleviates most (but not all) of the potential future incompatibility concerns.

I especially like how he disliked option #3 - changing the IE security settings to assure a successful installation, calling the API, and resetting them. I laughed as I wrote this option, as I could not imagine anyone doing this. In the middle of last week, I came across the "Get In the Zone" section of an issue of "Web Team Talking." They actually suggest and provide code for doing just that - although in a different context for a slightly different reason. I happen to agree with the KISS directive - "Keep it Simple, Stupid" - but sometimes to assure a successful operation you need to handle the corner cases.

The last part of his comment - "[this is] why many enterprise customers choose to repackage installations from ISVs" is a completely different blog post. I cannot tell you how true this statement is - and it is usually caused by setup developers, either intentionally or unintentionally. I'll add this one to the list of future topics for sure.

For the sake of discussion we were using the Crystal Reports Web Viewer which can generically be replaced with any properly signed cab file that works in the normal deployment mechanism of IE using the OBJECT tag. I have not tried this with unsigned controls, but I tested the approach using Macromedia's Flash player. You can play along by visiting the Macromedia's Flash download page and getting the CLSID from the OBJECT tag in the page's source. I then downloaded the swflash.cab file from the codebase attribute of the object tag and stored it on my local disk. The example below is going to use this flash cab file and the flash Class ID. Additionally, I am going to assume the reader is familiar with COM, C++, and MFC. The code is a bit lengthy to post here, but using the steps below a reasonable Windows developer should be able to complete this exercise in an hour or two.

I chose MFC since we need to implement IBindStatusCallback. This is easily accomplished by deriving a class from CCmdTarget. Make sure the implementations of QueryInterface(), AddRef(), and Release() call the External...() base class implementations. GetPriority() and OnLowResource() can simply return E_NOTIMPL. All the other methods can return S_OK. Since this is an async callback we need to have an event handle member variable, initialized in the Constructor, and have the event set in the OnObjectAvailable() method. That's the essential plumbing for the callback class. The callbacks for download progress can be implemented for the progress bars in the MSI.

Before we continue, let's make sure we have all the data we need to install the control.

  • The class id of the control is {D27CDB6E-AE6D-11cf-96B8-444553540000}. It needs to be in this format as a string (with the braces) so we can call CLSIDFromString to convert it to a CLSID type.
  • We are going to use major and minor versions of 0xFFFFFFFF to assure it is always installed.
  • Finally, we need a codebase - or place to "download" the cab file from. Since we are installing the cab file locally, the CA needs to be appropriately place in the sequence and we need to get the local path to the file. The path needs to be in appropriate format - for a local disk where the file is at C:\swflash.cab, we need to make it file://c:\\swflash.cab

The entry point code needs to hook up the plumbing - instantiate the IBindStatusCallback implementation class, call ExternalQueryInterface() on it to get the interface pointer. Then we need to create and register the binding context by calling CreateBindCtx() and RegisterBindStatusCallback().

The meat of what we are doing is a call to CoGetClassObjectFromURL() with the parameters described above. The two common returns from this call will be
MK_S_ASYNCHRONOUS, S_OK, or an error. If S_OK is returned, the object is already installed. If the async value is returned, we need to loop until the event in the callback class is set or some predefined timeout elapses. After the event is set, check to see that the object was created based on what was set in the OnObjectAvailable() method.

Based on testing, this method appears to work all of the time, regardless of the various IE security zone settings. This is likely due to using the file:// protocol. Remember that we are dealing with a signed control - with unsigned controls, your mileage may vary. This method was not tested in IE with Server 2003 in Enhanced Security Configuration, so all bets are off there as well.

If you wish to have the installation always grab the "latest" version of the code from a URL, you will need to implement the code to change and restore the IE security settings from the article linked to above and also implement the download callbacks to move the MSI progress bar. Be sure to alert the user that you will be temporarily altering the IE security settings, and make sure when handling a cancel button press from the MSI that you restore the settings. This particular corollary to the problem can be useful if you want to refresh the clients using a simple repair/maintainence/reinstall option after the server-side control was updated without having Bob the network admin do much extra work (or having to release client side patches).

Sunday, April 17, 2005

Installing an Internet Explorer ActiveX Control

This post is different from my usual posts, mainly because I have no idea what I am talking about! Well, at least I am admitting it this time!

I am tasked with creating an installation for an intranet type application. I need to create a shortcut to a URL and install a client side ActiveX control. For the sake of argument lets make it a fairly common one, such as the Crystal ActiveX viewer control. Sounds pretty simple, eh?

Let's go over a typical usage scenario. Hopefully, when designing applications and their deployment packaging you have some made-up profiles of typical users. Lets make up a few for our purposes. "Bob the system admin," "Mary the power user," and "Joe the restricted user" should be good enough. We can be a bit more specific and cover XP SP2 users, users with severely restricted ActiveX Browser settings, and non-IE browser users as well, but lets ignore these for now.

Essentially, Joe has a problem. We can give him the URL to the web application or even create a shortcut and/or favorite for him. The problem is, the first time he tries to run a Crystal Report, he gets a security warning, and the Crystal ActiveX control does not install. Of course, it is because he has no permissions to install software, even if everything else we ignored above is in good shape. Incidentally, if we can't figure out that Joe's problem is that he is a limited user, you can try the CDLLogViewer application to find out why.

Bob and Mary never have issues with the system. In fact, if Bob and/or Mary perform the same steps as Joe everything works, and Joe logs in later on, even Joe can view and print reports. The problem is that Bob and Mary would never run reports on the system. Heck, Bob doesn't even launch the application. Bob may even deploy the install package using SMS or Group Policy.

In the corporate network world, pretty much everyone is a Joe. There are a few Privileged Mary's, and even less Bob's. To make all this work, Bob needs to install applications for the likes of Joe. This behavior is not only acceptable, but practically expected.

So how would Steve, our installation guru, make all this work? He would not only need it to work, but be practically bulletproof.

Option 1: The easiest approach. 99% of the time, this is synonymous with the wrong approach. We can extract the Crystal Reports ActiveX viewer cab file, read the .inf file to figure out what it is doing, add the files to a MSI project selecting the proper registration options, create the shortcut file, and be done with the project in under 20 minutes.

Why it doesn't work: Since Joe is not the only user of the machine (remember Mary and Bob?), any updates to the control will cause the MSI-based installation to screw up at some point. If the control is updated by Mary, and Bob uninstalls the application, the component/reference count then causes havoc with Mary by possibly removing the file on uninstall. Effectively, we have precluded the normal installation/updating/removal of the ActiveX Viewer by repackaging it in an MSI. More information on the hazards of this approach can be found on MSDN in the "Registry Details" page. Bad stuff.

Option 2: Install the shortcuts as in Option 1. Call the appropriate API's to install the CAB file from the web server. In this case, (after a bit of digging through MSDN I see this process is called "Internet Component Download" or ICD for short) we need to call CoGetClassObjectFromURL.

Why it doesn't work: Referring to the ICD link above, we assume the web server is up and running at installation time - #1 in the "Download and install process". We also assume that WinVerifyTrust returns a value that is compatible with the setting for ActiveX controls for the Zone in which the server we are pulling the cab file from resides - #2 in the "Download and install process". Last, but not least, we are assuming that good old Bob didn't muck with the CODEBASE setting of the Internet Search Path.

Option 3: Completely decimate the user's machine by querying the IE Security Settings, saving them, adding the server to a trusted zone, modifying the zone parameters to set the ActiveX settings appropriately, call CoGetClassObjectFromURL, after it succeeds, set everything back to the way it was.

This seems like a heck of a lot of work, but it appears to be the only way to bulletproof an installation of an ActiveX component. Plus, we ignored several issues. If the default browser was (or will be) Firefox or even Lynx, a shortcut to this URL will always fail to display the reports. To solve this problem, an EXE wrapper would need to be written to host the IE control to assure that IE is actually the browser engine used to access the page. Joel, the API war is far from over - it just made things more complicated!

Of course, I'll get to play around with these API's and see if installing the cab file from a file:// UNC path changes anything. I'll report back what I find. If anyone else has already "solved" this problem using an approach I haven't considered, please let me know. In all likelihood, I'll end up calling CoGetClassObjectFromURL, and if it fails, try to provide some meaningful feedback to the user as to why the installation can not succeed and let Bob worry about the corner cases. After all, he ultimately created them.

UPDATE: Please see Part II of this post!

Saturday, March 12, 2005

Application Preloading at System Startup

Sometimes I wonder if we (as software developers) should have some form of a code of ethics, where violations can get you barred from writing software in the future. Mainly, this would apply to the spyware/malware developers (or perhaps the ethical violation is more directable to those who bundle the spyware).

If this ethical code existed, I would add a violation for any application that did not inform and offer a choice to the user at install time that some startup or background task will always be running. Some typical offenders are QuickTime, Real, Adobe, Microsoft Office, Microsoft Messenger, printer driver "status monitors", and more. These startup applications increase boot time and memory/pagefile requirements. Most of the time, I don't use these applications each time I boot the PC.

If the goal is to make your application start up faster (or appear to start up faster) there are ways of accomplishing this. Rebasing your DLL's, background/on demand loading of plugins, delay loading dependent DLL's, etc. are a few common tricks that can be used. The utility "Adobe Reader SpeedUp" accomplishes a magnificent improvement in load times, and this doesn't involve rewriting any of Adobe's code. If you wrote one of these startup applications anyway, at least offer the user the option at install time if they wish to use it.

Thursday, October 21, 2004

Raymond talks about registering shell extensions

As you can tell, I'm catching up on reading some of my favorite blogs... Raymond Chen from Microsoft wrote a really nice article about registering shell extensions. Setup developers should take note of how Windows deals with what you put in those keys.

What is a shell extension (also known as Verbs)? This is what Windows uses to determine what to do (or what right-click options to provide) when clicking on a file with a specific extension. Read this link to find our more. I'll wait...

In the setup world, I believe when you run a program from a setup most vendors use the ShellExecute() API. If this is the case, when you launch a readme at the end of an installation by simply specifying "readme.txt" the default "open" verb (on a fresh Windows installation for a text file this will be Notepad) will be run. If the method of running an application in your authoring tool uses CreateProcess() , it will likely result in an error if you simply give it "readme.txt". For MSI based installations, many people may use the tutorial method described in the Platform SDK. I leave it as an exercise to the reader to see what that one uses! Any guesses based on the specifications for the tutorial mentioned in this document?

If you read the MSDN documentation for these functions - which are linked to above - you will understand this quote from the article: "Consequently, the shell mistakenly believes that the program name is "C:\Program", which it cannot find." This can be a security issue if a malicious program installs itself as "C:\program.exe". Please make sure you are registering extensions correctly - even if the installation design document you are given dictates otherwise.

Tuesday, September 28, 2004

Debugging DLL Registration Issues

Did you know that the HKEY_CLASSES_ROOT registry hive (in Windows 2000 and above) is a figment of your imagination?

I was helping a coworker troubleshoot an application that runs as a service that accesses another homebrewed COM component. Of course, it worked on his machine, but not when deployed on a test machine. Running the normal Filemon/Regmon tag team, we traced it down to an issue instantiating the COM component. Regmon reported a registry key in HKEY_CLASSES_ROOT could not be found. Double clicking the line in Regmon (I really love that feature) opens up Regedit to the key that couldn't be found. I immediately guessed the root cause of the problem, and asked the developer the question above: Did you know that the HKEY_CLASSES_ROOT registry hive (in Windows 2000 and above) is a figment of your imagination?

He responded, "Huh? Its right there," as he pointed to the topmost hive in Regedit. A quick pointer to MSDN followed by browsing to HKEY_CURRENT_USER\Software\Classes and HKEY_LOCAL_MACHINE\Software\Classes showed the obvious - the key that Regmon showed as "not found" was really in the HKEY_CURRENT_USER section. As you may know, an application that runs as a service is run under the LocalSystem account (assuming no impersonation is done in the service, a user is not specified in the Service Control Manager, etc.). This account does not share the HKEY_CURRENT_USER hive with the user that installed the software, nor the currently logged in user, and was therefore unable to read the registry key necessary to instantiate the COM component since it did not exist for LocalSystem.

So what caused the problem? In an MSI created by most authoring tools, an option is given to the user to install the application for the "current user" or "all users". This ultimately sets the ALLUSERS property. In addition, the Windows Installer engine will add the registration information to either the HKCU or HKLM sections as defined by the ALLUSERS setting. The only documentation related to this behavior is buried in the SelfReg table, but not in the documentation for the property, tables, or actions that are truly affected by this. Bear in mind that for items in the SelfReg table, the DllRegisterServer() function is called in the context of the current user if ALLUSERS is set to a "this user only" setting, and "elevated" if it is a per-machine installation.

It is tempting to skip the bland and usually useless "User Information" dialog when creating your installations so that your installation doesn't get on people's nerves, but remember that while setting this property is important, you surely do not want to violate rule #20 while trying to appease rule #10.

Oh, and if you are interested in how I actually fixed this issue: since the per-user installation makes no sense for this application, and the "User Information" dialog was never shown, a custom action of type 51 came in handy to set ALLUSERS to 1 at the start of both the UI and InstallExecute sequences. Some ICE type validators may flag the installation if ALLUSERS is set in the property table. If you forcibly set ALLUSERS to 1, make sure that the LaunchCondition table checks that the user is Privileged and that Version9X is not set.

Sunday, September 19, 2004

Detecting .NET Framework Version (Update)

Aaron Stebner from Microsoft comes through with some source code (in C++) to determine the installed version of the .NET framework, including service packs. Great job, Aaron!