Another customer problem: a product doesn't work with VFP

A customer reported that a product they have doesn’t work with Visual Foxpro. Time to put on the detective hat!

In the command window, type

            o= CREATEOBJECT(“ABCProduct.Application”)

After hitting the Enter key, the FoxPro process crashes.

I tried from Excel, in the Visual Basic Editor and it worked fine:

Dim o As Object

Set o = CreateObject("ABCProduct.Application ")

I could even write a small C++ client program that worked fine:

#include "windows.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){

      LPUNKNOWN pUnknown;

      CLSID sClsid;

      HRESULT hrRet;

      CoInitialize(0);

      hrRet = CLSIDFromProgID(L"ABCProduct.Application", (LPCLSID) &sClsid);

      hrRet = CoCreateInstance((REFCLSID) sClsid, NULL, CLSCTX_SERVER,

                                                IID_IUnknown, (LPVOID FAR *) &pUnknown);

      MessageBox(0,"Didn’t crash","",0);

      return 0;

}

Initial investigation indicated that the failure occurred on Windows XP in VFP versions 8 and 9, but worked fine in VFP7.

I could see that there were about 17 DLLs being loaded by the ABCProduct in each case. Several MSVCRT versions were being loaded. I began to suspect some sort of DLL versioning problem.

Tracing through their code in assembly language, I could see that ABCProduct was creating a window via CreateWindowEx and the window procedure was being called indirectly through a pointer, but that pointer was invalid. When it crashed, it was a value like 0xffff2693 which is an invalid address for program execution. When it works, this value is 0x77d5f05e, which is an address in user32.dll, which makes sense.

I went to ABCProducts web site, and I composed an email to their “Contact-Us” email address including these details and stack traces. The next day, I received email from one of their developers and we tried debugging the problem together.

After he installed VFP8 on his machine, he asked me how FoxPro could create an EXE to repro the problem. I told him that you don’t have to create an EXE when using Fox. I realized that he’s used to most software development tools which let you write code in an Integrated Development Environment (IDE) or shell, build an EXE, then run it. Fox is so interactive that you don’t have to do that. You can execute commands immediately without writing a program.

After restarting VFP, he noticed that the command was already in the command window (due to the persisting of command window history) and thought that the command was already executed. I clarified that it was just a history.

He had a hard time reproducing the problem on his machine. He could reproduce some failure, but only without the debugger attached. As it turns out, he was using Win2000. After switching to a Windows XP box, he could repro readily.

I learned that ABCProduct used to be a standalone windows application with its own Windows UI written using C++ and MFC (Microsoft Foundation Classes), and that the company added a COM layer on top so it could be used as a server via OLE Automation. The Windows were created, but just not shown in their COM server implementation.

He could see that his MFC code was creating a window and then MFC would call the Window procedure for that window. However, his code subclasses the control by getting the Window Procedure for that window and storing it so it can be called later. It calls GetClassInfo to get the Window Procedure address. We examined his code over the phone, single stepped it, and saw that it was indicating success, but returning an invalid result.

I put a breakpoint on {,,user32.dll}_GetClassInfo@12 in Visual Studio and could single step and confirm that the parameters passed in were valid, the return value was success, but the returned Window Procedure was invalid.

(Click here for more advanced debugging techniques)

We were stumped: how do we proceed?

Because I have the source code to VFP I could put the CreateInstance line as the very first executable line in VFP and the problem still reproduced. I then proceeded to strip VFP of all other source code and it still reproduced, even when there were only a couple lines left. I also changed all the compiler flags indicating which CRT (C runtime library) and link flags.

By this time, I have the above standalone C++ code that worked, and a very stripped version of VFP that failed even when they had identical code.

Then I stripped the Windows resources from VFP and it worked!

VFP8 and VFP9 use a Manifest Resource that indicates to the OS VFP dependencies on comctl32.dll (Windows Common Controls)

That manifest indicates to the OS that whenever comctll32 is loaded into the current process, it should be a particular version.

However, ABCProduct uses a different version of comctl32. When I remove the resource from VFP, it works.

Sure enough, if I add the line:

1 RT_MANIFEST "manifest.xml"

and manifest.xml contains this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

<assemblyIdentity

            version="1.0.0.0"

            type="win32"

            name="Microsoft.VisualFoxPro"

            processorArchitecture="x86"

/>

<description>Visual FoxPro</description>

<dependency>

    <dependentAssembly>

        <assemblyIdentity

            type="win32"

            name="Microsoft.Windows.Common-Controls"

            version="6.0.0.0"

            language="*"

            processorArchitecture="x86"

            publicKeyToken="6595b64144ccf1df"

        />

    </dependentAssembly>

</dependency>

</assembly>

then I could cause the failure, even on the small C++ sample above.

Soooo..

A possible solution: put the ABCProduct into a separate COM+ application. (I tried that, but I don't know which DLLs of the several to put into the COM+ application). The COM server will then be in a separate process.

It seems ironic that the manifest file was supposed to help prevent DLL versioning problems, but in this case it actually caused a versioning problem<sigh>

ABCProducts will remove the GetClassInfo call when their product runs as a COM server for their next release.

44994