C++ LNK2001:未解析的外部符号_imp__AddEventSource
我有一个表面上看起来很简单的应用程序,类似于几十个给我带来很少或没有麻烦的类似情况。在修改了我在代码项目中找到的一个示例之后,我开始将顶级例程移动到一个新的控制台应用程序中,剩下的代码将进入传统的Win32 DLL。这是我做过几十次的事情,我对declspecdllexport和declspecdllimport都很熟悉。我定义了一对常用的宏和预处理器变量,告诉编译器为调用者发出u declspecdlimport,为被调用者发出u declspecdllexport。这一切都是花园式的Windows编程,但控制台程序不会链接C++ LNK2001:未解析的外部符号_imp__AddEventSource,c++,dllimport,dllexport,C++,Dllimport,Dllexport,我有一个表面上看起来很简单的应用程序,类似于几十个给我带来很少或没有麻烦的类似情况。在修改了我在代码项目中找到的一个示例之后,我开始将顶级例程移动到一个新的控制台应用程序中,剩下的代码将进入传统的Win32 DLL。这是我做过几十次的事情,我对declspecdllexport和declspecdllimport都很熟悉。我定义了一对常用的宏和预处理器变量,告诉编译器为调用者发出u declspecdlimport,为被调用者发出u declspecdllexport。这一切都是花园式的Wind
查找答案,我使用微软Visual C++编译器上的/EP开关获取两个受影响程序的预处理器输出的拷贝。 主例程ProcessTestCase_ELS在单独的源文件ProcessTestCase_ELS.cpp中定义。您可以想象,即使定义了WINDOWS_LEAN_和_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$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
看起来一切正常,但当我尝试构建控制台程序时,链接步骤报告LNK2001:未解析的外部符号_imp__AddEventSource
最后一个注意事项;该错误似乎不是链接器没有看到导入库的结果,因为它是第一个搜索到的库,我可以在生成日志中看到它的列表。我还确定导入库的旧版本不会造成干扰,因为我从计算机中删除了它的每个实例,确认他们都走了,重新开始
确保您正在将项目构建为64位或32位,并且库位于x64或x86下的正确bin文件夹中。
根据个人经验,我试图将一个新的导出函数添加到我正在维护的dll中,但无法使编译器链接到新的导出函数。结果证明,在尝试更新64位版本的dll时,我无意中将生成目标更改为x86
确保您正在将项目构建为64位或32位,并且库位于x64或x86下的正确bin文件夹中。
根据个人经验,我试图将一个新的导出函数添加到我正在维护的dll中,但无法使编译器链接到新的导出函数。结果证明,在尝试更新64位版本的dll时,我无意中将生成目标更改为x86
LNK2001:未解析的外部符号_imp__AddEventSource
链接器错误消息表明您做错了什么。它正在查找_AddEventSource,但这不是导出函数的名称。它是_AddEventSource@20.请注意添加的@20名称装饰。您通过使用DEF文件混淆了问题,但它仍然可以从.lib文件的转储中看到
此链接器错误完全是设计造成的,它可以保护DLL的客户端免受极其严重的问题的影响。您的问题没有提示,但如果信息准确,则您更改了全局编译选项。Project+属性,C/C++,高级,调用约定设置。默认值为/Gd,您将其更改为/Gz。但没有进行s客户端项目中的ame更改。这非常糟糕,因为客户端代码对导出函数的任何调用都会使堆栈不平衡。这可能导致的运行时错误非常难以诊断。@20后缀的设计初衷是不允许出现这种情况
通过将u stdcall属性添加到函数声明中,更改回设置并明确调用约定
您正在犯的其他错误:
非常重要的一点,不仅仅是对于这个问题,就是只有一个.h文件声明导出的函数。适合包含在客户机程序的源代码中。这样,DLL和客户机之间就不会有不匹配的情况。这包括需要在声明中放入u stdcall,现在DLL和客户机总是会重新启动e和/G选项中的不匹配不会伤害任何人
您在声明中使用TCHAR类型(如PCTSTR)的方式也非常糟糕。您现在严重依赖于另一个全局编译选项。Project+Properties,General,Character Set。您还更改了此类型,使其与默认类型不同,因此可以非常轻松地进行编译
在客户项目中忽略这一点。这个错误比/Gz选项更糟糕,因为当您使用extern C时,不会出现链接器错误。运行时出现的错误行为更容易诊断,当您发现字符串只包含一个字符时,您只会损失一两个小时的生命。完全停止使用TCHAR,它在10年前就不再相关了。您编写的代码只能使用char*
DEF文件中的版本语句错误,它只能有两位数字,必须用..分隔。。这会在更高版本的VS上产生链接错误。您应该完全忽略它,它已经过时20多年了,版本号应该在版本资源中设置。不要跳过将资源添加到您的.rc文件,在VS中很容易做到,版本控制是DLL的导入
LNK2001:未解析的外部符号_imp__AddEventSource
链接器错误消息说明您做错了什么。它正在寻找_AddEventSource,但这不是导出函数的名称。是的_AddEventSource@20. 注意添加了@20名称装饰。您通过使用DEF文件使问题变得模糊,但它仍然可以从.lib文件的转储中看到
这个链接器错误在很大程度上是由设计造成的,它可以保护DLL的客户端免受极其严重的问题。您的问题不包含任何提示,但如果信息准确,则更改了全局编译选项。项目+属性,C/C++,高级,调用约定设置。默认值是/Gd,您将其更改为/Gz。但在客户项目中没有进行相同的更改。这非常糟糕,因为客户端代码对导出函数的任何调用都会使堆栈失衡。这可能导致的运行时错误很难诊断。@20后缀的设计就是为了不让它走到这一步
通过将u stdcall属性添加到函数声明中,更改回设置并明确调用约定
您正在犯的其他错误:
不仅对于这个问题,非常重要的是只有一个.h文件声明导出的函数。适合包含在客户端程序的源代码中。这样,DLL和客户机之间就永远不会有不匹配。这包括需要将u stdcall放在声明中,现在DLL和客户端将始终一致,并且/G选项中的不匹配不会伤害任何人
您在声明中使用TCHAR类型(如PCTSTR)的方式也是非常非常糟糕的。您现在严重依赖于另一个全局编译选项。项目+属性、常规、字符集。您也更改了默认设置,因此在客户端项目中很容易忽略这一点。这个错误比/Gz选项更糟糕,因为当您使用extern C时,不会出现链接器错误。运行时出现的错误行为更容易诊断,当您发现字符串只包含一个字符时,您只会损失一两个小时的生命。完全停止使用TCHAR,它在10年前就不再相关了。您编写的代码只能使用char*
DEF文件中的版本语句错误,它只能有两位数字,必须用..分隔。。这会在更高版本的VS上产生链接错误。您应该完全忽略它,它已经过时20多年了,版本号应该在版本资源中设置。不要跳过将资源添加到您的.rc文件,在VS中很容易做到,版本控制是DLL的导入
选择dllexport或模块定义文件。不要两者都做。陈,起初,我只有dllexport,但我一事无成。此外,我的收藏中的其他每个项目都有这两个,我从来没有读过或听过任何不鼓励使用这两个项目的东西。我能看到的唯一不同的是这个项目的所有东西,在边界的两侧,都是C++;因此,prototype.Pick中的extern C可以选择dllexport或模块定义文件。不要两者都做。陈,起初,我只有dllexport,但我一事无成。此外,我的收藏中的其他每个项目都有这两个,我从来没有读过或听过任何不鼓励使用这两个项目的东西。我能看到的唯一不同的是这个项目的所有东西,在边界的两侧,都是C++;因此,prototype.Build目标中的extern C不是问题;所有内容都是32位。尽管我说过构建目标在本例中不是问题,但这个建议值得记住;所有内容都是32位。尽管我说过构建目标在本例中不是问题,但该建议值得记住。您提出的解决方案工作正常;将DLL项目的默认调用约定恢复为_cdecl允许控制台程序链接,我可以在上的dumpbin报告中看到差异
导入库;顺便说一句,我忘了提到我知道装修这个名字是问题的根源,但我无法发现如何解决它。我没有想到默认调用约定中的实验性更改是问题的根源;毕竟,导出的例程有显式的调用约定。关于TCHAR类型,我还没有完成。从中派生此代码的示例将参数强制转换为PCTSTR。最终版本将把它们转换为LPCTSTR,并将同时具有ANSI和Unicode实现。自2005年以来,我一直将TCHAR与TCHAR.H中定义的通用TCHAR映射结合使用,我敏锐地意识到许多缺陷,并且我认为我已经采取了足够的措施来缓解它们;保持同步的事情少了一件。我在几年前开始添加它,当时我在模块定义文件的文档中注意到它,假设它必须涵盖版本资源未涵盖的某些情况。从一开始我就一直在使用它们。关于标题,我只有一个,尽管从我发布的材料中看不明显。我的所有DLL头文件都与DLL具有相同的基名称。函数声明顶部的注释取自此声明的标题。显示的输出是我努力验证声明是否正确生成的结果,如调用例程中的u declspecdlimport和DLL中的u declspecdllexport;将DLL项目的默认调用约定恢复为_cdecl允许控制台程序链接,我可以在导入库的dumpbin报告中看到差异;顺便说一句,我忘了提到我知道装修这个名字是问题的根源,但我无法发现如何解决它。我没有想到默认调用约定中的实验性更改是问题的根源;毕竟,导出的例程有显式的调用约定。关于TCHAR类型,我还没有完成。从中派生此代码的示例将参数强制转换为PCTSTR。最终版本将把它们转换为LPCTSTR,并将同时具有ANSI和Unicode实现。自2005年以来,我一直将TCHAR与TCHAR.H中定义的通用TCHAR映射结合使用,我敏锐地意识到许多缺陷,并且我认为我已经采取了足够的措施来缓解它们;保持同步的事情少了一件。我在几年前开始添加它,当时我在模块定义文件的文档中注意到它,假设它必须涵盖版本资源未涵盖的某些情况。从一开始我就一直在使用它们。关于标题,我只有一个,尽管从我发布的材料中看不明显。我的所有DLL头文件都与DLL具有相同的基名称。函数声明顶部的注释取自此声明的标题。显示的输出是我努力验证声明是否正确生成的结果,如调用例程中的u declspecdlimport和DLL中的u declspecdllexport。