ClassIDs in Visual FoxPro COM Projects
November 26, 2011 •
As you probably know Visual FoxPro projects can be compiled into COM servers. It's as easy as creating a new FoxPro class and marking it with the OLEPUBLIC keyword to allow it to be compiled into a COM Server.
In a PRG file it's as easy as creating a class like this:
DEFINE CLASS SimpleClass as Session OLEPUBLIC FUNCTION HelloWorld(lcName) RETURN "Hello " + lcName ENDDEFINE
Now add this class to a project called SimpleServer and compile the class either into an EXE (which becomes a DCOM out of process server) or a DLL (MTDLL or Regular DLL).
BUILD MTDLL SimpleServer FROM simpleserver
and voila you have a COM server that you can now instantiate from other environments or even from Visual FoxPro.
o = CREATEOBJECTEX("SimpleServer.SimpleClass","") ? o.HelloWorld("Rick")
The name of the COM server by default becomes:
ProjectName.ClassName
so here the project is SimpleServer and the class is SimpleClass.
Note I'm using CreateObjectEx here to force VFP to use the object as a COM object in VFP - in some situations VFP treats in process (DLL) FoxPro COM objects like Fox objects and CREATEOBJECTEX() ensures that the object is always loaded as a true COM object. Of course you can do something similar in VB classic, or .NET, Delphi, C++ etc. where use a FoxPro COM object makes a lot more sense than inside of FoxPro code where you could (and should if possible) use the object natively.
When you use a DLL server as above the COM instance will be loaded into the host process - it's an InProcess component and it runs in the hosts environment. This means that the environment settings (Startup Path, memory setup, OS Paths etc) are all inherited from the host process inside of the DLL server. In effect the DLL server becomes part of the host process just like any other DLL loaded into it.
To create an EXE server you can just compile the same code into an EXE:
BUILD EXE SimpleServer FROM simpleserver
Once you've compiled an EXE you can use the same code used previously to instantiate the server - nothing changes. However, now the server is an out of process server and runs in its own process - SimpleServer.exe if you look it up in Task Manager - that is completely separate from the host process. SimpleServer.exe gets instantiated by the Visual FoxPro runtime and it gets its own brand new environment and any interaction between the host application and your COM server occurs over an RPC Remoting interface that performs interprocess communication.
ClassIDs in Visual FoxPro
Typically you interact with COM objects via ProgIds like SimpleServer.SimpleClass, but internally all COM access is routed through unique GUID identifiers called ClassIDs. To find more info about COM servers you can use the COMCLASSINFO function in FoxPro. One of the options of COMCLASSINFO is option 4:
o = CREATEOBJECT("SimpleServer.SimpleClass") ? COMCLASSINFO(o,4)
which shows you the ClassId for the COM object which looks like this (but will be different if you were following along - GUIDs are unique):
{6C552462-832E-432A-9059-351145E7090B}
Visual FoxPro creates these unique CLASSIDs inside of the Project file. When you build your project VFP scans for any COM objects - those classes marked with OLEPUBLIC - and keeps track of them. The first time a given class is encountered VFP stores its GUID inside of the project file. Subsequent builds retrieve this GUID and reuse it for registration and creation of the type library and DllRegister/DllUnregister functions that compile into VFP.
Specifically it stores this data - ProgId and ClassIds in the Reserved 2 field of the PJX header record. To demonstrate I added a second COM class (SimpleServerNo2) to the project and compiled. When I then open USE SimpleServer.pjx and BROWSE I can see the Reserved2 field which looks like this:
4 103 4 2 30c:\wwapps\wc3\SimpleServer.DLL 12simpleserver 12simpleserver 25simpleserver Type Library 4 116 38{F10346E2-C9D9-47F7-81D1-74059CC15C3C} 10 24simpleserver.SimpleClass 24simpleserver.SimpleClass 0 11SimpleClass 30c:\wwapps\wc3\simpleserver.prg 12 38{6C552462-832E-432A-9059-351145E7090B} 38{3753AF24-8DCA-4D00-B59B-3CA256C07091} 10 27simpleserver.SimpleClassNo2 27simpleserver.SimpleClassNo2 0 14SimpleClassNo2 30c:\wwapps\wc3\simpleserver.prg 12 38{353EB458-7DDE-4987-8BC0-42AD9ECCB9E9} 38{EDBC795C-62AB-42F6-863B-45708C680C72}
What you see here is a fixed length format record of project information. Most of this info comes from the Project Info dialog in FoxPro. But some information - namely the COM Server configuration additional 'records' are added to the Reserved2 field. I marked the ClassIds for the two COM servers in italic in the string above. Each COM server has both a CLASSID as well as an (IID) Interface ID which is the second classID you see following the ClassID.
Why does this matter?
If you corrupt a project in some way you CAN, if you're really desperate, fix things by going in here and fixing up the ProgIds or ClassIds to match known values. I've had many occasions where customers have copied around projects multiple times and have renamed classes without paying attention. Going into this field I've been able to 'save' the project and existing ClassIDs and ProgIds. Basically the COM objects are at the end
COM Information Corruption in FoxPro Projects
As a vendor who sells several FoxPro products that utilize COM to interact with other host environments I've seen many problems with FoxPro COM servers. One of the things I've seen a lot of is that COM CLASSIDs get corrupted.
Wrong Server loading
The most common reason for this corruption that I've seen comes from projects being copied. Since VFP stores COM information as part of the project file, copying the project (ie. COPY FILES SimpleServer.pj* to SimpleServer2.pj*) copies all the COM information as well. Sometimes this is what you want, other times not. If you want to make an exact copy of a project for testing or backup then project copying is totally fine. Just keep in mind that if you build the copied project it will share the same ProgIds and ClassIds as the original project and when you build this second project all COM references will point at this DLL.
Often times, however, people copy projects because they want a new starting point for a new project. They copy the project and various files not realizing they are essentially copying the COM functionality as well. What ends up happening is that two different objects now exist in two separate projects that are registered with the same ClassIDs. The result of this is that one registration overwrites the other and if the two versions are not kept in sync unpredictable behavior ensues because you're never sure which type of object gets loaded.
Moral of this story is: When you copy projects with COM objects for creation of a new project: Always create a new project and import all files from scratch. This ensures that each project gets their own set of CLASSIDs.
Which server is which?
When in doubt which server holds what COM object it's best to manually register projects. To be sure always register the component you're interested in manually with:
regsvr32 SimpleServer.dll
or for an EXE:
SimpleServer.exe /regserver
This always makes sure you get the right server loaded.
ClassID Corruption
When VFP builds a project it checks the ClassID in the project and tries to find existing classids in the registry. VFP checks the validity of the entries in the registry and tries to update the registry with the latest build information. VFP is pretty smart about this and corruption of these IDs is very, very rare, but it does happen. Usually it happens when you have two projects where each was created on its own (ie. you didn't copy the projects). What happens in this scenario is that both projects reference the same ProgIds but different ClassIDs. In effect each project builds different registry entries. Aside from expected problems that each time the project is compiled in a different project it will end up pointing to a different binary on disk, this scenario can also cause corruption as VFP tries to update the registry. The error that comes up in this case is: "Invalid subscript reference" while trying to build the project when it's trying to register the COM server.
The first thing to try in this case is to see if you can find an old copy of your COM binary (DLL or EXE) and try to register it.
regsvr32 simpleserver.dll
or for an EXE server:
SimpleServer.exe /regserver
Re-registering might clean up the registry and point at the right locations for things to start working again. Once registered try loading the COM object. If that works, unload it and then try to rebuild your project, chances are good that now it will build.
If that fails, you can try and fix your project using the Reserved2 field mentioned before. It's all text so you can change values in this field, but be really careful - the field data is fixed length, so any character you change needs to be replaced with another (ie. spaces are important!). You can fix ProgIDs and ClassIds and paths if necessary.
If that fails another - more laborious option - is to clean the registry of your COM object.
- Find all instances of your ProgId (s) HKCR\simpleserver.SimpleClass HKCR\simpleServer.SimpleClassNo2
- Find all instances of your TypeLib HKCR\TypeLib\{guid}
- Find all instances of your servers executable name (ie. SimpleServer.dll)
In the case of my SimpleServer example I'd just search the registry for "SimpleServer". Delete all the root entries that you find for matches (ie. walk them back up to the GUID entry or the ProgId entry). This will be the ProgId key in the root and the various ClassId entries in the All key.
If your name is not clearly delineated you have to search more specifically on the ProgId and the ClassId to find all entries - both ways work, but the latter is more tedious.
Once the registry is clean make sure you only compile the same COM objects from a single project OR if you need to have multiple projects with same COM objects, make 100% sure that the second project is a copy of the original project that contains all the same ClassID entries!
When in doubt: Create a new Project with a New Name
Your last resort option that should always work is to create a brand new project with a new name. Don't copy it, just create a new project and give it a new name and then add all your project files back to the project. When you create a new project with a new name the ProgIds will change as will the ClassIds. Essentially creating a new project with a new name ensures that you're starting from scratch and it's almost guaranteed to work. The downside of course is that all your COM ProgIds change and any client code that's already using your COM objects has to change.
I've had to do this in the past with a few projects that were corrupted and also ended up corrupting a customer's installations. Starting over was really the only option to make this all work out properly. However, when you're down to your last resort this is the most reliable solution to get to a known good starting point.
Not as bad as it sounds
If all this sounds scary, don't worry. A lot of what I'm describing here is really, really rare. But it does happen occasionally. If you are careful with copying projects then you are almost sure to never run into a problem. Just make sure you understand what happens when you copy a project that has existing COM objects in it. Safest route is to create a new project, with a new name and start fresh - this is guaranteed to avoid COM object corruption.
Derek Kalweit
December 03, 2012