What's the proper way to fix a broken COM registration? - com

We have an .ocx file containing 2 ActiveX controls. One thing led to another and... now we're in "dll hell." One control loads fine within the containing application, and the other control (the one that was modified) fails to load completely. Without a doubt its because we broke compatibility by changing several event and method signatures. (That ought to teach us to unregister old .ocx's before registering new ones...)
In the past I recall having to go into the registry and manually remove important looking entries from HKCR\TypeLib, but that's scary stuff. What is the generally accepted solution to this?

If your COM registration is genuinely broken, the only way I know of to fix it is to do a search through the registry for the CLSIDs in question and delete all references to it.
Yeah, it's a little dangerous; that's why an activex control is supposed to safely register itself, and why MS now recommends using an MSI for installs and not doing a selfreg (regsvr32) in the install process. Once something gets messed up on a system, it can be a pain to fix it.
However, if you figure out the relevant CLSIDs (the CLSID for each control and the typelib) and remove only things that reference those you should be okay; to be safe, set a system restore point before you start.
Good luck

Related

Getting the value at the end of a long chain of registry entries

This is my first attempt to use the registry in any depth, so bear with me...
I write small VB.net programs that interact with Excel and other programs to produce complex reports. As part of this we sometimes have to interact with a 3rd party DLL. Since this DLL is generally very rare, we took an "always install" policy, and run their installer on first-start.
Well now we have a customer who already has the 3rd party app, so the install fails (as it should). But this leaves me trying to find where the DLL/app has been installed so I can call it properly. Poking about in regedit (causing it to repeatedly crash) I have found it seems to be like this...
Installing the DLL produces an entry called APLW.WSEngine in HKEY_CLASSES_ROOT, as well as an apparently identical copy in HKEY_LOCAL_MACHINE\SOFTWARE\Classes\. Those entries both contain a CLSID which can be found in HKEY_CLASSES_ROOT\Interface\[the key]\TypeLib as well as entries for a variety of sub-parts of the install. I also find it at HKEY_CLASSES_ROOT\TypeLib\[the key], and here it has a key called \3.0\HELPDIR\ that contains the directory I'm looking for.
So...
What is the minimum number of calls I need to make to find the value in that HELPDIR key, or get Nothing or "" if any of the keys doesn't exist. In the end I either need that path, or nothing if the software has not been installed. Currently I'm making repeated calls into the reg to look up each entry and key in turn, but I suspect there is a way to chain them?
To make things slightly more complex, that \3.0\ in the path may change, the actual value is in the Version key in the original CLSID entry. If there is an easy way to add this I'll use it, but since we only work with 3.0 (AFAIK) it can be ignored.
Maybe 1. Maybe more. Don't know, this was not a very typically laid out question (which is probably why you got down voted), but I'm going to assume that you are more of a numbers / reporting person than a software engineer. So proceeding with that assumption, I will try and be helpful instead of pointing out any breaches of etiquette and provide you a couple resources that may help.
For starters, how to read a registry value:
https://msdn.microsoft.com/en-us/library/xz88758e.aspx
Secondly, the full tech spec on the Registry class in vb.net:
https://msdn.microsoft.com/en-us/library/microsoft.win32.registry(v=vs.110).aspx
Both give plenty of samples that should aid you along your way. But keep in mind that in certain situations and certain computer setups, these sorts of registry calls might require you to elevate your UAC permissions, if you run into such a case, you can try this resource, which covers in some detail how to make your applications UAC aware:
http://www.codeproject.com/Articles/17968/Making-Your-Application-UAC-Aware
I hope this helps, and good luck!

Runtime error 429 in VBA, but class is registered

I'm trying to recreate a program that uses javascript to open a connection to a PLC and then display all sorts of information on a web page. I'd rather have it in a form in MS Access for various reasons, and have spent forever trying to find the right dll to use (Jet32X.dll, if anyone is curious). I finally tracked the CLSID called out in the javascript back to a registered class for the PLC, and I'm trying to create that object in VB code. It won't get any further than the Dim As New line, however, throwing runtime error 429: "Active X Component Cannot Create Object." Really wish I had some more information about why.
I know the class is registered, since that's how I found it in the first place. I know the DLL file isn't corrupted, since the program runs fine from the JS version. I have a sneaky suspicion that there's some sort of incompatibility going on here, since the PLC and supporting software is pretty old, and I'm working in Microsoft Access 2013 (and its associated VBA). I can't really think of a good way to verify this, however. Does anyone have any other ideas? Could anything else be causing this problem?
Figured it out; in case anyone else runs into this sort of issue:
32bit COM dlls will not run in 64bit applications. If you don't want to go back and reinstall 32bit versions of whichever application you're using, one of the easiest workarounds is using dllhost.exe as a surrogate.
You can read a little about it here, but I found this tutorial easier to follow.
I send a new reply just to recap the information and avoid anyone that stumbles in the same problem again after me wasting precious time. All the steps involved assume that you already correctly registered the dll you are trying to use.
How to make a 32bit COM Dll work in a 64bit application
The "easy" solutions involve using the Dll Surrogate method, which runs dllhost.exe and as an intermediary process in 64bit to expose the underlying 32bit dll. When done correctly this works seamlessly without any special measure needing to be taken in neither in the 32bit dll nor in the 64bit application.
There are two main approaches to this:
Using Oleview.exe (i.e. using a GUI)
Oleview can be obtained downloading the Window 10 SDK.
In order to use Oleview it you have to:
Download the Window 10 SDK at the following link:
https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
Go to C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86 to find the 32bit version of oleview.exe
ONLY THE FIRST TIME: run it as administrator to avoid see the message related to the impossibility to load iviewer.dll
In the left pane, Object Classes -> All Objects and find your dll name.
WARNING: you may find many entries for your dll. In particular each class has got its own entry such as YourProjectName.YourClassName
In the right pane, go to Implementation -> Inproc Server, and tick Use Surrogate Process. Leave the Path to Custom Surrogate empty to use the system default surrogate, that is dllhost.exe.
You can check the procedure went correctly by returning to the Registry tab, always in the right pane of the Oleviewer and make sure that now under CLSID = {yourAppIdHere} [DllSurrogate] = is listed among the entries.
Edit manually the Windows Registry
The Oleview method is recommended, but the manual method may be ok if you need to do this only once. The tutorial that NickGlowsinDark mentions was moved to https://techtalk.gfi.com/32bit-object-64bit-environment/ .
In order to avoid problems in the future with the page going offline I copy and paste here the most important steps involved. All credit goes to Emmanuel Carabott that is the original author of the tutorial, I added the first two steps in order to facilitate you in the process.
Open the Registry Editor (Windows+R -> regedit), and follow the following steps:
You first need to find your dll GUIDs. You will probably have many GUIDs, one for each of the classes that your dll exports. I find it's easier to find the GUIDs if you go to HKEY_CLASSES_ROOT\YourProjectName.YouClassName. It is the (Default) String Value you find under the Clsid key.
I recommend you find all the GUIDs first and make a note of them in order to have an easier time with the steps after this one.
Then, as Emmanuel Carabott kindly explains in his article, you have to do the following for each of the GUIDs you found:
Locate your COM object GUID under the HKey_Classes_Root\Wow6432Node\CLSID\[GUID]
Once located add a new REG_SZ (string) Value. Name should be AppID and data should be the same COM object GUID you have just searched for.
Add a new key under HKey_Classes_Root\Wow6432Node\AppID\
The new key should be called the same as the com object GUID
Under the new key you just added, add a new REG_SZ (string) Value, and call it DllSurrogate. Leave the value empty.
Create a new Key under HKey_Local_Machine\Software\Classes\AppID\
Again the new key should be called the same as the COM object’s GUID. No values are necessary to be added under this key.
That’s it, your COM Object should now be accessible from a 64bit environment and can be used like a regular COM Object.

Can't eliminate Access corruption

My firm's Access database has been having some serious problems recently. The errors we're getting seem like they indicate corruption -- here are the most common:
Error accessing file. Network connection may have been lost.
There was an error compiling this function.
No error, Access just crashes completely.
I've noticed that these errors only happen with a compiled database. If I decompile it, it works fine. If I take an uncompiled database and compile it, it works fine -- until the next time I try to open it. It appears that compiling the database into a .ACCDE file solves the problem, which is what I've been doing, but one person has reported that the issue returned for her, which has me very nervous.
I've tried exporting all of the objects in the database to text, starting with a brand new database, and importing them all again, but that doesn't solve the problem. Once I import all of the objects into the clean database, the problem comes back.
One last point that seems be related, but I don't understand how. The problem started right around the time that I added some class modules to the database. These class modules use the VBA Implements keyword, in an effort to clean up my code by introducing some polymorphism. I don't know why this would cause the problem, but the timing seems to indicate a relationship.
I've been searching for an explanation, but haven't found one yet. Does anyone have any suggestions?
EDIT: The database includes a few references in addition to the standard ones:
Microsoft ActiveX Data Objects 2.8
Microsoft Office 12.0
Microsoft Scripting Runtime
Microsoft VBScript Regular Expressions 5.5
Some of the things I do and use when debugging Access:
Test my app in a number of VM. You can use HyperV on Win8, VMWare or VirtualBox to set up various controlled test environments, like testing on WinXP, Win7, Win8, 32bit or 64 bits, just anything that matches the range of OS and bitness of your users.
I use vbWatchDog, a clever utility that only adds a few classes to your application (no external dependency) and allows you to trap errors at high level, and show you exactly where they happen. This is invaluable to catch and record strange errors, especially in the field.
If the issue appears isolated to one or a few users only, I would try to find out what is special about their config. If nothing seems out of place, I would completely unsintall all Office component and re-install it after scrubbing the registry for dangling keys and removing all traces of folders from the old install.
If your users do not need a complete version of Access, just use the free Access Runtime on their machine.
Make sure that you are using consistent versions of Access throughout: if you are using Access 2007, make sure your dev machine is also using that version and that all other users are also only using that version and that no components from Access 2010/2013 are present.
Try to ascertain if the crash is always happening around the same user-actions. I use a simple log file that I write to when a debugging flag is set. The log file is a simple text file that I open/write to/close everytime I log something (I don't keep it open to make sure the data is flushed to the file, otherwise when Access crashes, you may only have old data in the log file as the new one may still be in the buffer). Things I log are, for instance, sensitive function entry/exit, SQL queries that I execute from code, form open/close, etc.
As a generality, make sure your app compiles without issue (I mean when doing Debug > Compile from the IDE). Any issue at this stage must be solved.
Make absolutely sure you close all open recordsets, preferrably followed by setting their variables to Nothing. VBA is not as sensitive as it used to be about dangling references, but I found it good practice, especially when you cannot be absolutely sure that these references will be freed (especially when doing stuff at Module-level or Class-level for instance, where the scope may be longer-lived than expected).
Similarly, make sure you properly destroy any COM object you create in your classes (and subs/functions. The Class_Terminate destructor must explicitly clean up all. This is also valid when closing forms if you created COM objects (you mentioned using ADOX, scripting objects and regex). In general keeping track of created objects is paramount: make sure you explicitly free all your objects by resetting them (for instance using RemoveAll on a dictionary, then assigning their reference to Nothing.
Do not over-use On Error Resume or On Error Goto. I almost never use these except when absolutely necessary to recover from otherwise undetectable errors. Using these error trapping constructs can hide a lot of errors that would otherwise show you that something is wrong with your code. I prefer to program defensively than having to handle exceptions.
For testing, disable your error trapping to see if it isn't hiding the cause of your crashes.
Make sure that the front-end is local to the user machine, You mention they get their individual front-end from the network but I'm not sure if they run it from there or if it it copied on their local machine. At any rate, it should be local not on a remote folder.
You mention using SQL Server as a backend. Try to trace all the queries being executed. It's possible that the issue comes from communication with SQL Server, a corrupt driver, a security issue that prevents some queries from being run, a query returning unexpected data, etc. Watch the log files and event log on the server closely for strange errors, especially if they involve security.
Speaking of event log, look for the trace of the crash in the event log of your users. There may be information there, however cryptic.
If you use custom ribbon actions, make sure thy are not causing issues. I had strange problems over time with the ribbon. Log all all function calls made by the ribbon.

Why is selfregistration bad?

Assuming self-registration is used to install components as part of a larger installer program, why is self-registration bad? E.g. self-registering vb custom controls or capicom or whatever. I acknowledge that self-registration is probably not as safe in the case of a dll you wrote yourself, but I'm not discussing those.
The MSDN lists several reasons why self-registration is bad, reproduced here:
Rollback won't work properly.
OK, this reason makes sense.
Advertisement will not work as well.
Ignoring the fact that advertisement is only important for certain types of software clients, I don't understand why this is an issue. Only the main application needs to be advertised, not its components.
Self-registration does not support per-user keys properly.
So what? Giving every user access to "common" components is not a bad thing unless you have a lot of users on the machine, in which case it's still not world-ending.
Self-registration is more susceptible to coding errors.
I can definitely believe this, except in the case of dlls which were written by Microsoft (they may have errors, but I don't think trusting them is unreasonable). And in the case of tlbs and ocxs which were generated by software, coding errors seem pretty unlikely.
Self-registering dlls may link to other dlls.
In the case of dlls generated by programs, it doesn't seem likely that self-registration will fail due to this but adding the registration keys manually would have worked. I'd rather have my self-registration return an error that I'm missing the dlls.
I'm sure this will draw flames :/
Edit: Crossed out arguments that I think actually matter (based on user responses and my own).
As far as this item:
Self-registration does not support per-user keys properly.
So what? Giving every user access
to "common" components is not a bad
thing unless you have a lot of users
on the machine, in which case it's
still not world-ending.
It's not just a matter of how many users are on a machine, but also what permissions they have. If not an administrator, the user will be unlikely to have permission to update the HKEY_LOCAL_MACHINE portion of the registry.
The item
Self-registering dlls may link to
other dlls
applies when you try to register the dll, but the installer has not yet copied/installed another dll your registering function requires.
I would add one potential "gotcha" which I have run into (with auto-generated self-registration code for MS COM objects):
Self-registration runs the executable, with all that entails/requires. So, for example, if your component directly or indirectly logs the fact that it was activated (perhaps for security logging if the component is only supposed to be running at very specific points or in very specific contexts, or in coordination with other applications), registration will appear to be an activation (unless you're careful about logging). This can also be interesting if your logs record, say, the context in which the component was utilized, in which case you will have whatever inherited context triggered the self-registration.
Not a big deal in most cases, but it can cause some subtle confusion sometimes. I'd add it to the list of reasons why it's probably not preferable.

'Cached' behaviour of 'hotfixed' dlls

Sometimes, a customer (or tester) needs a patch on an installation of the product I work on. Sometimes, I brew him a dll containing the fix, so that he can test it. He overwrites the old dll with the new version and restarts the app.
Every now and then, it appears that the 'new' dll isn't actually used, although I'm absolutely positive that my brand new sparkling bytes were copied over the old dll.
To make things worse, after the first 'hotfixed' run, when restarting the application, the new functionality clearly is loaded..., and the old behaviour is never to be seen again.
Has anyone observed this behaviour? Would it be possible that the XP loader sort of 'caches' recently used dlls?
(Note: this happens on XP SP2)
Yes, this is possible. Windows does not let you delete files which are in use, but strangely enough (for Windows at least) you can rename a DLL while it is still in use. If your tester uses something along
rename dll to dll.orig
copy new dll
restart application
it can happen, that the dll.orig version is still used if it has not been completely unloaded (because some still started app still has a reference to it).
This has bitten me a few times, too. I therefore usually change some behavior (mostly something of the header of the log-file) so that I can be sure that something has not been happened.
To workaround is simply: close all programs that use this DLL. Sysinternals Process Explorer helps to find the applications that use it.