Recreating a COM DLL, do I need to worry about the GUID? - visual-studio

A change needs to be made in a DLL. The DLL was originally coded in VB6 (not by me), and the source code lost.
It is very simple in its functionality, so I recreated it from scratch, but I only have access to VB Express 2008.
I created it first as a normal DLL then realized it had to be a COM DLL. Fortunately, an excellent article at http://www.codeproject.com/KB/COM/nettocom.aspx tells me how.
But, I don't know anything about GUIDs...
Should I use the same GUID as the original DLL or not? Does it make any difference?
Edit: Does it really matter since it's a COM DLL? It is called into by an Active X control & I can see no reference in the web page to the GUI ... (but I'm just a n00b, so what do I know? ;-)

If you want this library to be a direct replacement of the original and it is fully binary compatible (all interfaces are unchanged) - then yes, you should use the same GUIDs for the class ids and interface ids. If you don't do that users of the original library will not be able to use yours without recompiling their programs.
Beware that binary compatibility is a must for reusing the GUIDs. If you break any interface - change its id and the id of the class implementing it and recompile the client.

The GUIDs acts as a kind of identifier for your COM object and DLL. If you use the same GUID you need to register your new DLL so that the location is updated (i.e. if you don't place it exactly in the same spot and have recreated all interfaces the old DLL previously had registered).
The cleaner approach is to generate a new GUID and modify the caller to use the new GUID/DLL instead.

Related

How to determine the version number of a registered OCX in Delphi XE2

I have an ActiveX component in XE2 that connects to a database to handle transaction processing, and it includes a check of its version against that of the database to determine if they are compatible.
Unlike C#, where the AssemblyVersion attribute can be obtained at runtime using the Application.ProductVersion property, Delphi XE2 has no such built-in property for getting the version information included in the project options, with the preferred method being to use GetFileVersionInfo, passing in the full path to the program, or in this case the OCX.
Originally the OCX was always installed to the Windows System path but we have changed our installation process so that the OCX is installed to the same folder as the executable that uses it, which is a location determined by the user.
What I need is a consistent method that I can use from within the OCX code to obtain the installation folder from the registry across the multitude of Windows environments. I assume it would have something to do with the GUID's defined in the _TLB.pas file.
You don't need to query the Registry at all. The OCX can retreive its own filename by passing Delphi's global HInstance variable as the module handle to the Win32 API GetModuleFileName() function, then it can pass that filename to GetFileVersionInfo().
Although, a better way for the OCX to access its own version, without resorting to GetFileVersionInfo(), is to use Find/Load/LockResource() to access its own version resource directly, then no filename is needed at all. You can copy the version resource data into a temp buffer and pass that to VerQueryValue() to retrieve the resource's VS_FIXEDFILEINFO structure (retrieving anything else from the resource gets a bit trickier because GetFileVersionInfo() prepares certain lookup data that VerQueryValue() then uses).
You certainly don't need to use the registry here. Not least because there could potentially be multiple versions of the DLL on the machine, because you are installing to the executable directory of any program that uses the DLL.
You could certainly read the version resource as Remy describes. But another alternative would be to include a constant in your program that encodes the database version compatibility.
const
DatabaseVersion = 1;
Check this constant against the value read from the database and fail if not compatible.
To me this makes a little more sense as it separates the version of the DLL from the version of the database. The two are not necessarily linked. You could, and probably do, update the DLL without changing the database structure.

"Declare" statement as yet an other way to circumvent the .dll hell?

To my astonishment I found VB6 code that uses Declare statements to define functions in a .dll that lives in the Program Folder without it being registered on Windows. This seems like a supersimple way to avoid the .dll hell without having to resort to using Side by side manifests. Can I read some more about this somewhere? Are there snags?
The Declare statement is used to do "just in time" binding to non-ActiveX DLLs. Until your program "touches" a Declared entrypoint no attempt is made to load the library.
It basically has nothing at all to do with the topic of DLL Hell.
Muddled thinking can even lead people to plop ActiveX DLLs "next to" the EXE which actually can result in DLL Hell because people who tend to do this also use poor techniques for installing and uninstalling applications.
Poorly designed application A deployment plops a commonly shared DLL or OCX next to the EXE.
Poorly designed application is run, the VB6 runtime can't find the classes in the registry, does a DLL Search using Windows heuristics, immediately locates the DLL next to the EXE and calls its self-registration entrypoint.
Innocent, properly designed applications B, C, D are later installed that use the same DLL/OCX and their installers find the library already registered.
Poorly designed application A is uninstalled, typically by simply deleteing its folder in Program Files.
Applications B, C, and D (and any future applications using the library) are now broken - due to orphaned component registration pointing to a non-existant library.
Moral of the story:
Never, never, never put DLLs "next to" your VB6 application on installation. If you have private DLLs that are not shared with other applications even then put them into a libs, etc. folder under the application folder. Possible exception might be non-COM DLLs, such as those you expect to use Declare with.
There is also a great deal of misunderstanding about manifests, of which there are multiple kinds. The ones you are probably thinking of are application and assembly manifests.
These can be used for selecting among different versions of a library installed Side by Side, or they can be used to isolate applications and assemblies which is the part that has bearing on DLL Hell.
Of course application manifests can be used to specify quite a few other things about how Windows should run the application.
Windows searches in a well-documented sequence of folders for LoadLibrary (which VB6 uses behind the scenes to resolve Declare declarations). Since the first location on the list of search folders is the app's own folder, your discovery makes perfect sense.
It doesn't resolve the "DLL hell" issue for the most part, though. It can't work for system DLLs, for instance, because Windows preloads most of them. Also, if a DLL is already loaded into memory, Windows may use that copy of the DLL (not sharing data, but code can be reused).
That's part of the reason that manifests were created; they allow an application to strictly define required versions of system DLLs in order to provide certain functionality. VB6's technique is old fashioned (just like VB6).

Why not embed the typelib into the COM server and only ship it separately?

I'm analyzing a rather old COM server project in order to reuse some stuff from it and noticed a strange thing - in order to expose itself to the registry it implements a separate function different from DllRegisterServer() and that function accepts the path to the typelib and loads the typelib from that path and the typelib is shipped separately and can be located anywhere.
The typical solution is to just use ATL CComModule::RegisterServer() which will happily load the typelib from the same DLL if it is embedded as a resource. The DLL already uses ATL and this should be the most direct way.
I tried to find why the typelib is not embedded as a resource but looks like it was a very old design decision and noone can explain it.
I can see a reason for shipping the typelib separately - this way it's easier for COM server developers to #import it. But why not both ship it as a separate file and embed as a resource into the COM server DLL? I could imagine that the typelib increases the DLL size by several hundred kilobytes but can't find any other serious reason.
What could be the reasons for shipping the typelib as a separate file only?
I finally found a serious reason for shipping a typelib separately only. If two versions - standalone and embedded - are shipped the installer could screw something up and those two typelibs could come out of sync and this would cause all kinds of weird problems for customers. Shipping exactly one version of typelib is safer in this aspect.

Can I safely rename/move a dll that is already loaded by a process?

I have a dll that is loaded and file-locked by a process, and I would like to update it with a newer version. I'm looking for an alternative to terminating the process to release the file lock before updating the dll. It is okay that the existing live processes still uses the old version, as long as newly instantiated ones pick up the new logic.
It seems that I can simply rename/move the dll and the live process still seems to work well. Is it safe to do this? If the dll's code has already been loaded into the process then why does it still need to lock the dll?
It is not always ok to move all dll's used by any random application. Some applications, like asp.net, use a shadow copy concept where they actually copy the dll and use the copy leaving you free to modify the original. In the case of asp.net, if you modify the original asp.net will automatically spool up a new app domain using the new dll and gracefully shut down the old one.
If the application you're referring to has a lock on the dll, then you can't safely change it.
It depends on your dll/application. For example, dll may use shared memory, or implement inter-process communication. New dll version may implement it differently. So once new instance will start, you'll have two conflicting versions in memory.
So it's not safe in general case, though in your particular case it may be OK.

CComPtr CoCreateInstance() fails

I have a COM .dll registered successfully with regsvr32 but somehow CoCreateInstance() fails to create one of its interfaces. Is there a freeware tool which can determine the reason for the failure?
First of all, check the return value of the CoCreateInstance() call. Second, you can use a tool like Regmon or Process Monitor to see what registry lookup fails. This way, you can quickly determine what exactly wasn't registered the way you'd expect it to be.
If your com dll is implemented in C++ & has debug info, you could also try debugging with MSVC to step into CoCreateInstance.
My guess is that you missed associating your class with one of the interfaces properly. I've done that a number of times by mistake. If you're using ATL you need to make sure your implementing class derives from the interface & also you have added COM_INTERFACE_ENTRY(I____) for your interface in the COM_MAP:
BEGIN_COM_MAP(CFileHelper)
COM_INTERFACE_ENTRY(IFileHelper)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IStream)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
Or maybe the GUID differs between the IDL file and what's in the implementing C++ file. MSVC6 has an annoying bug where if the wizard fails to create a new class because of file permissions (e.g. some of the files it wants to change are read-only) but it has already added a GUID to the IDL file, and you fix the file permissions and go to do it again, there will be an inconsistency in the GUIDs and it's a pain to catch this. In an ideal world, the GUID definitions would reside ONLY in one file and then you wouldn't have to worry about this.
If not that, sometimes there are weird errors regarding marshaling & apartments but that only pops up if you've got multiple threads and are sending interface pointers across thread or interface boundaries.

Resources