LNK2001: 未解析的外部符号 __imp__AddEventSource
LNK2001: unresolved external symbol __imp__AddEventSource
我有一个应用程序,表面上看起来很简单,并且与许多类似的情况相似,但很少或没有给我带来麻烦。在改编了我在 The Code Project 上找到的示例后,我着手将顶层例程移动到一个新的控制台应用程序中,将其余代码留在传统的 Win32 DLL 中。这是我做过几十次的事情,我对 __declspec(dllexport)
和 __declspec(dllimport)
相当了解。我定义了通常的一对宏和预处理器变量来告诉编译器为调用者发出 __declspec(dllimport)
并为被调用者发出 __declspec(dllexport)
。这一切都是 "garden variety" Windows 编程,但控制台程序不会 link.
为了寻找答案,我使用 Microsoft Visual C++ 编译器上的 /EP
开关来获取两个受影响程序的预处理器输出的副本。
主例程 ProcessTestCase_ELS
在单独的源文件 ProcessTestCase_ELS.cpp
中定义。正如您所想象的那样,即使定义了 WINDOWS_LEAN_AND_MEAN
,该清单也相当冗长,但相关位只是以下几行。
// 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
) ;
DLL 导出的例程的预处理器输出同样长,但有问题的例程的定义很短。整个套路如下。
// ============================================================================
// 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 •
DLL 项目有一个模块定义文件。除去内部文档,如下
LIBRARY EventLoggingForAll
VERSION 1, 0, 0, 1
EXPORTS
AddEventSource @2
RemoveEventSource @3
关于DLL,dumpbin.exe
对DLL文件及其导入库给出如下报告。
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
14 .idata
4 .idata
4 .idata
18 .idata
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
一切似乎都井井有条,但是当我尝试构建控制台程序时 link 步骤报告 LNK2001: unresolved external symbol __imp__AddEventSource
。
最后一个音符;该错误似乎不是 linker 没有看到导入库的结果,因为它是搜索的第一个库,我可以在构建日志中看到它如此列出。我也确定没有旧版本的导入库可能会干扰,因为我从机器上删除了它的每个实例,验证它们都消失了,然后重新开始。
- 确保您将项目构建为 64 位或 32 位,并且该库位于 x64 或 x86 下正确的 bin 文件夹中。
根据个人经验,我试图向我正在维护的 dll 添加一个新的导出函数,但无法让编译器针对新的导出函数 link。原来我在尝试更新 64 位版本的 dll 时无意中将构建目标更改为 x86。
LNK2001: unresolved external symbol __imp__AddEventSource
linker 错误消息说明您做错了什么。它正在寻找 _AddEventSource,但那是 而不是 导出函数的名称。它是 _AddEventSource@20。请注意添加的“@20”名称修饰。您使用 DEF 文件混淆了问题,但从 .lib 文件的转储中仍然可以看到它。
这个 linker 错误在很大程度上是设计使然,它可以保护 DLL 的客户端免受极其严重的问题的影响。您的问题不包含任何提示,但如果信息准确,则您更改了全局编译选项。项目 + 属性,C/C++,高级,"Calling Convention" 设置。默认是/Gd,你把它改成了/Gz。但是没有在客户端项目中做同样的改变。这太讨厌了,因为客户端代码对导出函数的任何调用都会使堆栈失衡。这可能导致的运行时错误很难诊断。 @20 后缀旨在防止它走这么远。
通过将 __stdcall
属性添加到函数声明来更改设置并明确调用约定。
您犯的其他错误:
非常重要,不仅仅是为了这个问题,就是只有 一个 .h 文件来声明导出的函数。适合#included 到客户端程序的源代码中。这样 DLL 和客户端之间永远不会不匹配。这包括需要在声明中放置 __stdcall,现在 DLL 和客户端将始终同意并且 /G 选项中的不匹配不会伤害任何人。
您使用 TCHAR 类型(如声明中的 PCTSTR)的方式也非常非常讨厌。您现在严重依赖另一个全局编译选项。项目 + 属性,常规,"Character Set"。您也更改了默认值,因此在客户端项目中很容易忽略它。这个错误比 /Gz 选项更严重,因为当您使用 extern "C" 时 not 会得到一个 linker 错误。运行时的不当行为更容易诊断,当您发现字符串只包含一个字符时,您只会损失一两个小时的生命。完全停止使用 TCHAR,它在 10 年前就不再重要了。您编写的代码只能与 char*
.
一起使用
DEF文件中的VERSION语句是错误的,它只能有2个数字,必须用.
分隔。这会在以后的 VS 版本中产生 link 错误。你应该完全省略它,它已经过时了 20 多年,版本号应该在 VERSION 资源中设置。不要跳过将资源添加到 .rc 文件,在 VS 中很容易做到,版本控制是 DLL 的导入。
我有一个应用程序,表面上看起来很简单,并且与许多类似的情况相似,但很少或没有给我带来麻烦。在改编了我在 The Code Project 上找到的示例后,我着手将顶层例程移动到一个新的控制台应用程序中,将其余代码留在传统的 Win32 DLL 中。这是我做过几十次的事情,我对 __declspec(dllexport)
和 __declspec(dllimport)
相当了解。我定义了通常的一对宏和预处理器变量来告诉编译器为调用者发出 __declspec(dllimport)
并为被调用者发出 __declspec(dllexport)
。这一切都是 "garden variety" Windows 编程,但控制台程序不会 link.
为了寻找答案,我使用 Microsoft Visual C++ 编译器上的 /EP
开关来获取两个受影响程序的预处理器输出的副本。
主例程 ProcessTestCase_ELS
在单独的源文件 ProcessTestCase_ELS.cpp
中定义。正如您所想象的那样,即使定义了 WINDOWS_LEAN_AND_MEAN
,该清单也相当冗长,但相关位只是以下几行。
// 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
) ;
DLL 导出的例程的预处理器输出同样长,但有问题的例程的定义很短。整个套路如下。
// ============================================================================
// 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 •
DLL 项目有一个模块定义文件。除去内部文档,如下
LIBRARY EventLoggingForAll
VERSION 1, 0, 0, 1
EXPORTS
AddEventSource @2
RemoveEventSource @3
关于DLL,dumpbin.exe
对DLL文件及其导入库给出如下报告。
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
14 .idata
4 .idata
4 .idata
18 .idata
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
一切似乎都井井有条,但是当我尝试构建控制台程序时 link 步骤报告 LNK2001: unresolved external symbol __imp__AddEventSource
。
最后一个音符;该错误似乎不是 linker 没有看到导入库的结果,因为它是搜索的第一个库,我可以在构建日志中看到它如此列出。我也确定没有旧版本的导入库可能会干扰,因为我从机器上删除了它的每个实例,验证它们都消失了,然后重新开始。
- 确保您将项目构建为 64 位或 32 位,并且该库位于 x64 或 x86 下正确的 bin 文件夹中。
根据个人经验,我试图向我正在维护的 dll 添加一个新的导出函数,但无法让编译器针对新的导出函数 link。原来我在尝试更新 64 位版本的 dll 时无意中将构建目标更改为 x86。
LNK2001: unresolved external symbol __imp__AddEventSource
linker 错误消息说明您做错了什么。它正在寻找 _AddEventSource,但那是 而不是 导出函数的名称。它是 _AddEventSource@20。请注意添加的“@20”名称修饰。您使用 DEF 文件混淆了问题,但从 .lib 文件的转储中仍然可以看到它。
这个 linker 错误在很大程度上是设计使然,它可以保护 DLL 的客户端免受极其严重的问题的影响。您的问题不包含任何提示,但如果信息准确,则您更改了全局编译选项。项目 + 属性,C/C++,高级,"Calling Convention" 设置。默认是/Gd,你把它改成了/Gz。但是没有在客户端项目中做同样的改变。这太讨厌了,因为客户端代码对导出函数的任何调用都会使堆栈失衡。这可能导致的运行时错误很难诊断。 @20 后缀旨在防止它走这么远。
通过将 __stdcall
属性添加到函数声明来更改设置并明确调用约定。
您犯的其他错误:
非常重要,不仅仅是为了这个问题,就是只有 一个 .h 文件来声明导出的函数。适合#included 到客户端程序的源代码中。这样 DLL 和客户端之间永远不会不匹配。这包括需要在声明中放置 __stdcall,现在 DLL 和客户端将始终同意并且 /G 选项中的不匹配不会伤害任何人。
您使用 TCHAR 类型(如声明中的 PCTSTR)的方式也非常非常讨厌。您现在严重依赖另一个全局编译选项。项目 + 属性,常规,"Character Set"。您也更改了默认值,因此在客户端项目中很容易忽略它。这个错误比 /Gz 选项更严重,因为当您使用 extern "C" 时 not 会得到一个 linker 错误。运行时的不当行为更容易诊断,当您发现字符串只包含一个字符时,您只会损失一两个小时的生命。完全停止使用 TCHAR,它在 10 年前就不再重要了。您编写的代码只能与
char*
. 一起使用
DEF文件中的VERSION语句是错误的,它只能有2个数字,必须用
.
分隔。这会在以后的 VS 版本中产生 link 错误。你应该完全省略它,它已经过时了 20 多年,版本号应该在 VERSION 资源中设置。不要跳过将资源添加到 .rc 文件,在 VS 中很容易做到,版本控制是 DLL 的导入。