New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heat-ing an ActiveX control #5673

Open
JonathanRowell opened this Issue Aug 30, 2017 · 11 comments

Comments

Projects
None yet
3 participants
@JonathanRowell

JonathanRowell commented Aug 30, 2017

Not certian if this is a bug, but the following wxs code is the result of heat on an ActiveX Dll which was made with Visual Studio C++ 6.0 Enterprise Edition with heat.exe in the WIX Toolset 3.11 (I get an identical result with 3.10). In fact there are several controls in a directory and they are all the same as this one

     <Component Id="cmpB720B28ED07C737BC57413D07D5FC92A" Directory="dirE52911F17F921939DFE8DA3005B8A804" Guid="*">
        <File Id="filCA25F0566B6F46FC3F1DEBBB803857C0" KeyPath="yes" Source="$(var.controlsDir)\Aturis.dll">
           <TypeLib Id="{6C236116-AF88-438B-B382-760FB38C532B}" Description="Aturis 1.0 Type Library" HelpDirectory="dirE52911F17F921939DFE8DA3005B8A804" Language="0" MajorVersion="1" MinorVersion="0">
              <Class Id="{B41BE301-DB10-4973-AAAA-3C2733117CFE}" Context="InprocServer32" Description="AturisReader Class" ThreadingModel="apartment" Programmable="yes">
                 <ProgId Id="Aturis.AturisReader.1" Description="AturisReader Class">
                    <ProgId Id="Aturis.AturisReader" Description="AturisReader Class" />
                 </ProgId>
              </Class>
              <Interface Id="{AD85A49D-8B07-40AF-8AA9-A0ECA08877A8}" Name="IAturisReader" ProxyStubClassId="{00020424-0000-0000-C000-000000000046}" ProxyStubClassId32="{00020424-0000-0000-C000-000000000046}" />
           </TypeLib>
        </File>
     </Component>

I'm not sure that this is correct. It seems to just register a typelib. When I install the resulting .msi file on Windows 7 or Windows 10, the Control cannot be created. One gets an error message "Module not found" or "Invalid Class string". If I deregister the control using regsvr32 /u aturis.dll and reregister with regsvr32 aturis.dll everything works fine.

The heat options are : "c:\program files\wix toolset v3.11\heat.exe" dir controls -ag -cg controls -indent 3 -dr INSTALLDIR -var var.controlsDir -out controls.wxs

@barnson

This comment has been minimized.

Member

barnson commented Aug 30, 2017

Please attach the dll.

@barnson barnson added this to the v4.x milestone Aug 31, 2017

@BMurri

This comment has been minimized.

Collaborator

BMurri commented Sep 7, 2017

I would be interested in debugging this once the example dll has been added.

@JonathanRowell

This comment has been minimized.

JonathanRowell commented Sep 8, 2017

I'm sorry for the delay, but the control is proprietary and I had to ask before posting.
(Had to change the file type to .txt)

aturis-dll.txt

@BMurri

This comment has been minimized.

Collaborator

BMurri commented Sep 8, 2017

Short version: The WiX code produced matches exactly what the control writes to the registry when called by Heat. This is neither a regression nor an unknown scenario. Recommended resolution: External

Long version: When heat extracts registry values, it does so by creating an environment with an (initially empty) virtual registry, then (for this binary) calls the DllRegisterServer export followed by retrieving all values in the virtual registry and transforming them into WiX code. What aturis.dll does in this environment is write the equivalent of the attached reg file (extension changed in order to attach. I hope I don't have three copies of it attached, if I do, they are all the same). As you can see, it does declare the following (connected) entities:

  • ProgId: Aturis.AturisReader (& Aturis.AturisReader.1) (points to CLSID {B41BE301-DB10-4973-AAAA-3C2733117CFE})
  • CLSID: {B41BE301-DB10-4973-AAAA-3C2733117CFE} (Apartment mode InprocServer32 that is marked Programmable) (points back to Aturis.AturisReader & Aturis.AturisReader.1). The CLSID points to the typelib {6C236116-AF88-438B-B382-760FB38C532B}, version 1.0.
  • Interface: IAturisReader ({AD85A49D-8B07-40AF-8AA9-A0ECA08877A8}) with proxystub: {00020424-0000-0000-C000-000000000046} (which was not added to the registry by the control). The interface points to the typelib {6C236116-AF88-438B-B382-760FB38C532B}, version 1.0
  • TypeLib: {6C236116-AF88-438B-B382-760FB38C532B}, version 1.0, using the same binary file as the CLSID.

If that is the complete and expected footprint, then everything "looks fine", without knowing the actual internals. However, if some other CLSID/ProgId/Interface is needed, or if the control itself internally uses others that didn't surface, then it would be expected to fail.

Because some implementations of self registration make use of preexisting values in the registry, they don't register correctly in this kind of environment. If you have access to the source code, you may wish to verify if additional registration values are required (based on OS version or some other criteria that an empty registry prevents correctly calculating). You could then manually add the required information to the created controls.wxs file.

@BMurri

This comment has been minimized.

Collaborator

BMurri commented Sep 8, 2017

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Aturis.AturisReader]
@="AturisReader Class"

[HKEY_CLASSES_ROOT\Aturis.AturisReader\CLSID]
@="{B41BE301-DB10-4973-AAAA-3C2733117CFE}"

[HKEY_CLASSES_ROOT\Aturis.AturisReader\CurVer]
@="Aturis.AturisReader.1"

[HKEY_CLASSES_ROOT\Aturis.AturisReader.1]
@="AturisReader Class"

[HKEY_CLASSES_ROOT\Aturis.AturisReader.1\CLSID]
@="{B41BE301-DB10-4973-AAAA-3C2733117CFE}"

[HKEY_CLASSES_ROOT\CLSID{B41BE301-DB10-4973-AAAA-3C2733117CFE}]
@="AturisReader Class"

[HKEY_CLASSES_ROOT\CLSID{B41BE301-DB10-4973-AAAA-3C2733117CFE}\InprocServer32]
@="$(var.controlsDir)\aturis.dll"
"ThreadingModel"="Apartment"

[HKEY_CLASSES_ROOT\CLSID{B41BE301-DB10-4973-AAAA-3C2733117CFE}\ProgID]
@="Aturis.AturisReader.1"

[HKEY_CLASSES_ROOT\CLSID{B41BE301-DB10-4973-AAAA-3C2733117CFE}\Programmable]
@=""

[HKEY_CLASSES_ROOT\CLSID{B41BE301-DB10-4973-AAAA-3C2733117CFE}\TypeLib]
@="{6C236116-AF88-438B-B382-760FB38C532B}"

[HKEY_CLASSES_ROOT\CLSID{B41BE301-DB10-4973-AAAA-3C2733117CFE}\VersionIndependentProgID]
@="Aturis.AturisReader"

[HKEY_CLASSES_ROOT\Interface{AD85A49D-8B07-40AF-8AA9-A0ECA08877A8}]
@="IAturisReader"

[HKEY_CLASSES_ROOT\Interface{AD85A49D-8B07-40AF-8AA9-A0ECA08877A8}\ProxyStubClsid32]
@="{00020424-0000-0000-C000-000000000046}"

[HKEY_CLASSES_ROOT\Interface{AD85A49D-8B07-40AF-8AA9-A0ECA08877A8}\TypeLib]
@="{6C236116-AF88-438B-B382-760FB38C532B}"
"Version"="1.0"

[HKEY_CLASSES_ROOT\TypeLib{6C236116-AF88-438B-B382-760FB38C532B}\1.0]
@="Aturis 1.0 Type Library"

[HKEY_CLASSES_ROOT\TypeLib{6C236116-AF88-438B-B382-760FB38C532B}\1.0\0\win32]
@="$(var.controlsDir)\aturis.dll"

[HKEY_CLASSES_ROOT\TypeLib{6C236116-AF88-438B-B382-760FB38C532B}\1.0\FLAGS]
@="0"

[HKEY_CLASSES_ROOT\TypeLib{6C236116-AF88-438B-B382-760FB38C532B}\1.0\HELPDIR]
@="$(var.controlsDir)"

@JonathanRowell

This comment has been minimized.

JonathanRowell commented Sep 9, 2017

Thank you for your analysis, which unfortunately I don't understand. I do have access to the source code and if necessary I'll post parts of it. It is a very simple ATL control which in fact wraps another DLL by making the latter's procedure calls into methods and the latter's variables into properties. This DLL is dynamically loaded via a method, so there is, afaik, no additional registry values necessary.

What I do not understand is why registering with regsvr32 causes no problems whatsoever whereas via the installer I get "invalid class string"? If something is missing, what is it?

@BMurri

This comment has been minimized.

Collaborator

BMurri commented Sep 9, 2017

I couldn't tell you without seeing the source code, but I've seen this situation multiple times throughout my career. All too often registration code makes assumptions that don't apply in the wild because "it always worked" on the developer's test and dev boxes.

The registration code is running in a different environment in the harvester than it does in the already installed product. Are you using the ATL Registrar code/templates? Any custom hooks? Something completely custom? Also, RegSvr32 uses two different entry points covering three different methodologies because of at least that many different prescribed methods for self registration emitted over the years of the evolution of COM/OLE. Our harvester only attempts two, and only one of those overlaps with RegSvr32.

I did notice that the registration method appeared to be using an incorrect calling convention, because the debugger warned me about a stack misalignment when the call returned. Both RegSvr32 and our harvester silently ignore that, because it's exceedingly all too common, but it can be a sign of stack corruption and could possibly indicate that the code partially failed (possibly due to some assumption).

@BMurri

This comment has been minimized.

Collaborator

BMurri commented Sep 9, 2017

Also, which version of ATL?

@JonathanRowell

This comment has been minimized.

JonathanRowell commented Sep 9, 2017

Like I said, all of this is made automatically using the relevant tools in VC++ 6.0 Enterprise Edition. I have all the files here, and you can have any of them except perhaps the actual C++ class implementation which contains the proprietary code (which I suspect has nothing to do with registration). The file extensions are Aturis and then
.aps, .cpp, .def, .dsp, .dsw, .h, .idl, .ncb, .opt, .plg, .tlb, _i.c,_p.c, ps.def, .ps.mk

AturisReader.cpp (proprietary), AturisReader.h, AturisReader.rgs, dlldata.c resource,h, StdAfx.cpp,
and StdAfx.h

There are no custom hooks or anything else strange. It is a simple C++ class which exports mostly methods to Automation and which loads a DLL and has methods which just simply call the corresponding (by name0 in the loaded DLL. Nothing could be simpler. It is just that VC++ 6.0 is VERY old.

@BMurri

This comment has been minimized.

Collaborator

BMurri commented Sep 15, 2017

VC++ 6 did not create 64-bit code, unless I don't remember my history very well. However, registering using the 32-bit regsvr32 on a 64-bit test box wrote the registration to both the 32- and 64-bit parts of the registry in the test box I tried using, while MSI will only write to one (32-bit). That violates what little I remember about COM/ActiveX registration using InprocServer32.

Are you somehow using a 64-bit host to try to load your ActiveX?

@JonathanRowell

This comment has been minimized.

JonathanRowell commented Sep 15, 2017

I thought that that would have something to do with it. Using VC++ 6.0 can one only create 32 bit controls. I use the WIX Toolset on Windows XP 32 bit to make an MSI file which contains the control and a 32-bit program which calls it (made with Delphi 5.0). This MSI file gets installed on my Windows 7 and Windows 10 machines for testing, both of which are 64 bit machines. In fact the XP machine is a Virtual Box machine on 64 bit Windows 7. I do all my development in virtual machines and test on real machines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment