I upgraded an OCX library from VS2010/Win7 to VS2019/Win10. The project builds, however when I try and use RegSvr32.exe
from an elevated command prompt, I receive error 0x0040200. I did a bit of debugging and the offending call is the call to AfxOleRegisterTypeLib
.
Yes, I saw this SO article, which states that "absence of a tlb file near the dll". Other searches state to run from an administrative command prompt.
I do NOT have a TLB near the OCX control. If I try and create one using tlbexp.exe
, I get the following error:
TlbExp : error TX0000 : Could not load file or assembly 'file:///C:\pathto.ocx' or one of its dependencies. The module was expected to contain an assembly manifest.
TlbExp command line (used Run as Administrator for all cmd.exe):
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.2 Tools\x64\tlbexp.exe" /VERBOSE "<path to OCX file>" /out:"<path to .tlb output file>"
I downloaded Resource Tuner
and that shows the manifest nicely. The manifest does not have any TLB information.
I am thinking that maybe the OCX manifest needs something more that helps TlbExp
get at the information that it wants, just a thought.
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="DriveOps.ocx"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
</assembly>
I did use Depends64
(aka Dependency Walker 64-bit
) and there are no missing components DLLs. It finds all of them nicely, as does RegSvr32.exe.
Placing the OCX file in C:\Windows\System32 does not help.
To anyone looking at the DLLs, these same DLLs work fine on the Win7 box. Here is some more information on the non-Windows DLLs
Ipp
prefix are the DLLs used by the Intel Code Composer Studio redistribution (x64) files, here version 2011, an older one that needs to be updated to the latest and greatest, not to mention now free API. These are all in the System32
folder.Here is the code:
// DllRegisterServer - Adds entries to the system registry
STDAPI DllRegisterServer(void)
{
AFX_MANAGE_STATE(_afxModuleAddrThis);
if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid))
return ResultFromScode(SELFREG_E_TYPELIB); // <- failure line, through debugging
if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE))
return ResultFromScode(SELFREG_E_CLASS);
return NOERROR;
}
The Intel Code Composer Studio 2011
files are in the C:\Windows\System32
directory, just like on the Win7 box.
For what it is worth, TlbExp
fails on the Win7 box too, just it registers, which is probably why the UI can add the control. As I recall, I once replaced the OCX on the Win7 project and VS2010 automatically created the TLB and prefixed Ax in front, but that was a couple of years ago, so my memory may not be the most accurate. Attempting to add the OCX to the UI (.Net WinForms) fails miserably and just says could not be added.
The OCX does use the latest platform tool set (Visual Studio 2019 (v142)
).
A comment for developer newbies, regasm.exe
is for .Net Assemblies. RegSvr32.exe
is for ActiveX Controls (OCX/DLL)
, which is what I have. RegSvr32 is for dynamically loaded modules, hence the DllRegister entry point.
Thoughts?
Notes From Further Testing
(Saturday 9/21/2019) When I upgraded, I created an empty C++ DLL project and then added all the files, changing the target extension to OCX going through the old project settings and when reasonable aligning them to the new project file wanting to keep things the same. I wanted to do a test and see what happens with a brand new OCX project. I saw that there was such a thing as "MFC ActiveX Control" for a project type in VS2019. I created that and saw that I got different base files, but more importantly RegSvr32.exe works. That means that either the mistake was the initial project file, so I need to import to a clean project or import piece by piece, if that is possible, and see where things break.
(Saturday 9/21/2019) The new test project did not come with a manifest file and TlbExp.exe
failed with the same error message like my real project. I went to add new item and saw "Package Manifest". That manifest file, though still produces the same TlbExp.exe error, looks quite different from the application manifest file from above. I created yet another new MFC ActiveX Control
project and added in the manifest from above just changing names and saw that the project refused to build throwing 1) Error c1010001 Values of attribute "level" not equal in different manifest snippets. and 2) LNK1327 failure during running mt.exe. That tells me that the original Win7 project and my Win10 project files probably have some error in it, otherwise VS should have thrown these errors to me. That does not answer why even on the test project TlbExp.exe
fails. Maybe some attributes in the manifest are required. I just left the defaults.
Package Manifest (It is the first time that I ever saw one of these. I always saw the app.manifest kind.)
<?xml version="1.0" encoding="utf-8"?>
<!-- TODO: Make sure to set the Package attributes -->
<Package xmlns="urn:Microsoft.WindowsPhone/PackageSchema.v8.00"
Owner=""
OwnerType="OEM"
Platform=""
Component=""
SubComponent="Package"
ReleaseType="Test" >
<Components>
<Driver InfSource="$(_RELEASEDIR)$(TARGETNAME).inf">
<Reference Source="$(_RELEASEDIR)$(TARGETNAME)$(TARGETEXT)" />
<Files>
<!-- For kernel mode drivers, $(DRIVER_DEST) evaluates to "drivers" by default -->
<!-- For user mode drivers, $(DRIVER_DEST) evaluates to "drivers\umdf" by default -->
<File Source="$(_RELEASEDIR)$(TARGETNAME)$(TARGETEXT)" DestinationDir="$(runtime.system32)\$(DRIVER_DEST)" />
</Files>
</Driver>
</Components>
</Package>
LoadLibrary(dll)
and after that GetProcAddress(module, "DllRegisterServer")
to see which one fails. Well, in my case the both functions succeed. That means that the author missed one other failure branch and these two API calls are not the only thing that RegSvr32.exe
does.Though I am still not at the end of the road, as I have aximp.exe
/ tlbimp.exe
issues on the OCX, I found the problem that prevented me from registering the ActiveX control, which was the question here.
The answer is the GUID
in the main CPP file:
(I am giving my research, as I could not find anyone explaining how RegSvr32.exe
works and what it does. I wanted to share in hopes that it helps others.)
const GUID CDECL _tlid = { 0xFE5C7D88,0xD53C,0x4977,{0xBA,0x56,0x4B,0xF3,0x02,0x0A,0x5D,0x8A} };
which gets used in the main registration function STDAPI DllRegisterServer(void)
must match the GUID
present in the IDL:
[uuid(FE5C7D88-D53C-4977-BA56-4BF3020A5D8A), version(1.0),
helpfile("DriveOps.hlp"),
helpstring("DriveOps ActiveX Control module"),
control]
library DriveOpsLib
{
...
}
I had 2 different values, hence the failure.
Here is the methodology and research that I used to find the problem, but first I will state the registration function, as that is key again.
STDAPI DllRegisterServer(void)
{
AFX_MANAGE_STATE(_afxModuleAddrThis);
HINSTANCE hiTypeLib = AfxGetInstanceHandle();
if (!AfxOleRegisterTypeLib(hiTypeLib, _tlid))
return ResultFromScode(SELFREG_E_TYPELIB);
if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE))
return ResultFromScode(SELFREG_E_CLASS);
return NOERROR;
}
The failure was at this line, as I believe was mentioned in the question.
if (!AfxOleRegisterTypeLib(hiTypeLib, _tlid))
I had already found the source code for RegSvr32.exe
on the internet. It was part of Microsoft GitHub
sources located in VCSamples-master
.
Direct Link to RegSvr32.exe
: here
Direct Link to download zip: here
The code was a sort of dead end, as it told me what should have been obvious, namely that utility called the DllRegisterServer
entry point of a DLL to do all the work. I should have known that, but, well, I had to see it to make sense.
Using procmon.exe
, did not shed any light and the various calls to the registry was like reading a foreign language, not helpful.
Here I drew a blank until it occurred to me to get the source code for AfxOleRegisterTypeLib
, as that what was failing. I wanted to see what the heck that thing did and what was at line 113 of source file ctlreg.cpp
.
I was still thinking on the comment of procmon
and a registry issue, but I figured that the code will tell me which one. It took me a bit of research, but I found the code. I love Microsoft sharing code. Their error messages are not helpful, but being able to actually see what they were trying to do so totally helps.
Here is the code:
BOOL AFXAPI AfxOleRegisterTypeLib(HINSTANCE hInstance, REFGUID tlid,
LPCTSTR pszFileName, LPCTSTR pszHelpDir)
{
USES_CONVERSION;
BOOL bSuccess = FALSE;
CString strPathName;
TCHAR *szPathName = strPathName.GetBuffer(_MAX_PATH);
::GetModuleFileName(hInstance, szPathName, _MAX_PATH);
strPathName.ReleaseBuffer();
LPTYPELIB ptlib = NULL;
// If a filename was specified, replace final component of path with it.
if (pszFileName != NULL)
{
int iBackslash = strPathName.ReverseFind('\\');
if (iBackslash != -1)
strPathName = strPathName.Left(iBackslash+1);
strPathName += pszFileName;
}
if (SUCCEEDED(LoadTypeLib(T2COLE(strPathName), &ptlib)))
{
ASSERT_POINTER(ptlib, ITypeLib);
LPTLIBATTR pAttr;
GUID tlidActual = GUID_NULL;
if (SUCCEEDED(ptlib->GetLibAttr(&pAttr)))
{
ASSERT_POINTER(pAttr, TLIBATTR);
tlidActual = pAttr->guid;
ptlib->ReleaseTLibAttr(pAttr);
}
// Check that the guid of the loaded type library matches
// the tlid parameter.
ASSERT(IsEqualGUID(tlid, tlidActual));
if (IsEqualGUID(tlid, tlidActual))
{
// Register the type library.
if (SUCCEEDED(RegisterTypeLib(ptlib, T2OLE((LPTSTR)(LPCTSTR)strPathName), T2OLE((LPTSTR)pszHelpDir))))
bSuccess = TRUE;
}
RELEASE(ptlib);
}
else
{
TRACE1("Warning: Could not load type library from %s\n", (LPCTSTR)strPathName);
}
return bSuccess;
}
I kept receiving an ASSERT
, so although line 113 did was on a non-code line, the actual failure was obvious. I knew that I did not fail out on the ASSERT_POINTER
, as that error message is different, which meant that I failed at:
ASSERT(IsEqualGUID(tlid, tlidActual));
I looked at the code in detail as well as the entry arguments. I decided to copy and paste this function contents into the real registration code in my OCX to gain further visibility while debugging. I wanted to see the values.
Sure enough, I saw 2 different GUID
values, one from the top, my _tlid
, and the one returned from the instance handle. I took out my handy-dandy TextPad
text editor, though Visual Studio
has a Find in Files
, but TextPad is so much easier to use. That led to one other instance in the entire solution, namely in DriveOps.idl
. That file up until that moment meant nothing to me, but suddenly I saw that the GUID
here was the one that RegSvr32.exe
pulled from the instance handle.
I unified the IDs, rebuilt, and now RegSvr32.exe
no longer complains. Yeah, since I got the code, it had no choice but to register. That it does not modify the registry is a different story and problem from what I can tell, but that is another question. RegSvr32.exe
now registers without complaint.
(Yes, I still have tlbimp.exe
, aximp.exe
, and adding my OCX project to my WinForms
project problems, but I got this thing licked and learned something in the process. I guess the difference in line numbers may be some changes Microsoft made in the header, either way, functionality appears the same.)
User contributions licensed under CC BY-SA 3.0