I have an application that seems on the surface to be straightforward, and is similar to scores of similar situations that have given me little or no trouble. After adapting a sample that I found on The Code Project, I set about to move the top level routine into a new console application, leaving the rest of the code to go into a conventional Win32 DLL. This is something that I've done dozens of times, and I know my way around __declspec(dllexport)
and __declspec(dllimport)
fairly well. I defined the usual pair of macros and preprocessor variables to tell the compiler to emit __declspec(dllimport)
for the caller and __declspec(dllexport)
for the callee. Every bit of this is "garden variety" Windows programming, yet the console program won't link.
Searching for answers, I used the /EP
switch on the Microsoft Visual C++ compiler to obtain copies of the preprocessor output of both affected programs.
The main routine, ProcessTestCase_ELS
, is defined in a separate source file, ProcessTestCase_ELS.cpp
. As you can imagine, the listing is rather lengthy, even with WINDOWS_LEAN_AND_MEAN
defined, but the relevant bit is only the following handful of lines.
// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the EVENTLOGGINGFORALL_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// EVENTLOGGINGFORALL_API functions as being imported from a DLL, wheras this DLL sees symbols
// defined with this macro as being exported.
// ============================================================================
// Install an app as a source of events under the name pszName into the Windows
// Registry.
// ============================================================================
extern "C" __declspec(dllimport) DWORD AddEventSource
(
PCTSTR pszName , // Pointer to string containing event source ID
PCTSTR pszMessages , // Optional (default = NULL) pointer to string containing name of associated message file
PCTSTR pszLogName , // Optional (default = NULL) pointer to string containing name of event log
PCTSTR pszCategories , // Optional (default = NULL) pointer to string containing name of category message file
DWORD dwCategoryCount // Optional (default = 0) category count
) ;
The preprocessor output of the routine exported by the DLL is equally long, but the definition of the routine at issue is blessedly short. The entire routine follows.
// ============================================================================
// Install an app as a source of events under the name pszName into the Windows
// Registry.
// ============================================================================
extern "C" __declspec(dllexport) DWORD AddEventSource
(
PCTSTR pszName , // Pointer to string containing event source ID
PCTSTR pszMessages , // Optional (default = NULL) pointer to string containing name of associated message file
PCTSTR pszLogName , // Optional (default = NULL) pointer to string containing name of event log
PCTSTR pszCategories , // Optional (default = NULL) pointer to string containing name of category message file
DWORD dwCategoryCount // Optional (default = 0) category count
) { TCHAR szPath [ 260 ] ;
TCHAR * lpszPath = ( TCHAR * ) &szPath ;
HKEY hRegKey = 0 ;
DWORD dwError = 0L ;
sprintf ( szPath , // Output buffer
"%s\\%s\\%s" , // Format string
"SYSTEM\\CurrentControlSet\\Services\\EventLog" , // Substitute for token 1
pszLogName
? pszLogName
: "Application" , // Substitute for token 2
pszName ) ; // Substitute for token 3
// ------------------------------------------------------------------------
// Create the event source registry key.
// ------------------------------------------------------------------------
dwError = RegCreateKeyA ( (( HKEY ) (ULONG_PTR)((LONG)0x80000002) ) , // Hive Name
szPath , // Key Name
&hRegKey ) ; // Pointer to place to store handle to key
// ------------------------------------------------------------------------
// If pszMessages is NULL, assume that this module contains the messages,
// and get its absolute (fully qualfied) name.
// ------------------------------------------------------------------------
if ( !(pszMessages) )
{
if ( !(( HMODULE ) GetModuleFileNameA ( m_hinstDLL , szPath , 260 )) ) // Sze of buffer, in TCHARs.
{
return util::ReportErrorOnConsole ( ) ;
} // Unless ( ( HMODULE ) GetModuleFileName ( m_hinstDLL , szPath , MAX_PATH ) )
} // Unless ( pszMessages )
// ------------------------------------------------------------------------
// Register EventMessageFile.
// ------------------------------------------------------------------------
dwError = RegSetValueExA ( hRegKey , // Handle to key
"EventMessageFile" , // Value Name
0x00000000L , // Reserved - pass NULL
( 2 ) , // Value type
( PBYTE ) szPath , // Value data
( ( ( strlen ( ( LPCTSTR ) szPath ) + 1 ) * sizeof ( TCHAR ) ) ) ) ; // Size of value data - Macro TCharBufSizeP6C encapsulates all of this: ( _tcslen ( szPath ) + 1 ) * sizeof TCHAR )
// ------------------------------------------------------------------------
// Register supported event types.
// ------------------------------------------------------------------------
DWORD dwTypes = 0x0001
| 0x0002
| 0x0004 ;
dwError = RegSetValueExA ( hRegKey , // Handle to key
"TypesSupported" , // Value Name
0x00000000L , // Reserved - pass NULL
( 4 ) , // Value type
( LPBYTE ) &dwTypes , // Value data
sizeof dwTypes ) ; // Size of value data
if ( dwError )
{
return util::ReportErrorOnConsole ( dwError ) ;
} // if ( dwError )
// ------------------------------------------------------------------------
// If we want to support event categories, we have also to register the
// CategoryMessageFile, and set CategoryCount. Note that categories need to
// have the message ids 1 to CategoryCount!
// ------------------------------------------------------------------------
if ( dwCategoryCount > 0x00000000 )
{
if ( !(pszCategories && pszMessages) )
{
if ( !(( HMODULE ) GetModuleFileNameA ( m_hinstDLL , szPath , 260 )) )
{
return util::ReportErrorOnConsole ( ) ;
} // Unless ( ( HMODULE ) GetModuleFileName ( m_hinstDLL , szPath , MAX_PATH ) )
} // Unless ( pszCategories && pszMessages )
dwError = RegSetValueExA ( hRegKey , // Handle to key
"CategoryMessageFile" , // Value name
0x00000000L , // Reserved - pass NULL
( 2 ) , // Value type
MsgFileNameString ( pszMessages ,
pszCategories ,
lpszPath ) , // Value data
MsgFileNameLen ( pszMessages ,
pszCategories ,
lpszPath ) ) ; // Size of value data
if ( dwError )
{
return util::ReportErrorOnConsole ( dwError ) ;
} // if ( dwError )
dwError = RegSetValueExA ( hRegKey , // handle to key
"CategoryCount" , // value name
0x00000000L , // reserved
( 4 ) , // value type
( PBYTE ) &dwCategoryCount , // value data
sizeof dwCategoryCount ) ; // size of value data
if ( dwError )
{
return util::ReportErrorOnConsole ( dwError ) ;
} // if ( dwError )
} // if ( dwCategoryCount > 0 )
dwError = RegCloseKey ( hRegKey ) ;
if ( dwError )
{
return util::ReportErrorOnConsole ( dwError ) ;
} // if ( dwError )
else
{
return util::AnnounceChangeToAll ( ) ;
} // FALSE (UNexpected outcome) block, if ( lr )
} // DWORD •
The DLL project has a module definition file. Excluding the internal documentation, it is as follows.
LIBRARY EventLoggingForAll
VERSION 1, 0, 0, 1
EXPORTS
AddEventSource @2
RemoveEventSource @3
With respect to the DLL, dumpbin.exe
gives the following reports on the DLL file and its import library.
Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
Dump of file C:\Documents and Settings\DAG\My Documents\Programming\Visual Studio 6\DLL\EventLogging\Debug\EventLoggingForAll.lib
File Type: LIBRARY
Exports
ordinal name
2 _AddEventSource@20
3 _RemoveEventSource@8
Summary
A8 .debug$S
14 .idata$2
14 .idata$3
4 .idata$4
4 .idata$5
18 .idata$6
Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
Dump of file C:\Documents and Settings\DAG\My Documents\Programming\Visual Studio 6\DLL\EventLogging\Debug\EventLoggingForAll.dll
File Type: DLL
Section contains the following exports for EventLoggingForAll.dll
0 characteristics
54BB1CE9 time date stamp Sat Jan 17 20:39:37 2015
0.00 version
2 ordinal base
2 number of functions
2 number of names
ordinal hint RVA name
2 0 00001014 AddEventSource
3 1 00001019 RemoveEventSource
Summary
1000 .data
1000 .idata
1000 .rdata
1000 .reloc
1000 .rsrc
12000 .text
Everything appears to be in order, yet the link step reports LNK2001: unresolved external symbol __imp__AddEventSource
when I try to build the console program.
One final note; the error doesn't appear to be the result of the linker not seeing the import library, because it is the first library searched, and I can see it listed as such in the build log. I am also certain that there isn't an old version of the import library that could interfere, because I deleted every instance of it from the machine, verified that they were all gone, and started fresh.
LNK2001: unresolved external symbol __imp__AddEventSource
The linker error message says what you are doing wrong. It is looking for _AddEventSource but that's not the name of your exported function. It is _AddEventSource@20. Note the added "@20" name decoration. You obfuscated the problem by using a DEF file, but it is still visible from the dump of the .lib file.
This linker error is very much by design, it protects the client of your DLL from a exceedingly nasty problem. Your question contains no hints, but if the info is accurate, you changed a global compile option. Project + Properties, C/C++, Advanced, "Calling Convention" setting. The default is /Gd, you changed it to /Gz. But did not make the same change in the client project. This is so nasty because any call that the client code makes to an exported function will unbalance the stack. The runtime errors that this can cause are very hard to diagnose. The @20 postfix was designed to not let it come this far.
Change the setting back and make the calling convention explicit by adding the __stdcall
attribute to the function declaration.
Other mistakes you are making:
Very important, not just for this problem, is to have only one .h file that declares the exported functions. Suitable to be #included into the client program's source code. That way there will never be a mismatch between the DLL and the client. This includes the need to put __stdcall in the declaration, now the DLL and client will always agree and a mismatch in the /G option can't hurt anybody.
The way you are using the TCHAR types (like PCTSTR in your declaration) is very, very nasty as well. You now critically depend on another global compile option. Project + Properties, General, "Character Set". You changed this one as well from the default so it is very, very easy to overlook that in the client project. This mistake is way nastier than the /Gz option since you do not get a linker error for that one when you use extern "C". The misbehavior you get a runtime is a bit easier to diagnose, you'll only lose an hour or two of your life when you discover that the strings only contain a single character. Stop using TCHAR completely, it stopped being relevant 10 years ago. Your code as written can only work with char*
.
The VERSION statement in the DEF file is wrong, it can have only 2 digits that must be separated by a .
. That produces a link error on later VS versions. You should omit it completely, it has been outdated for over 20 years, the version number should be set in a VERSION resource. Don't skip adding the resource to your .rc file, very easy to do in VS, versioning is import for DLLs.