我的应用程序似乎表面上看起来很简单,并且类似于给我很少或没有麻烦的类似情况。在调整了我在代码项目中找到的样本后,我开始将顶级例程移动到一个新的控制台应用程序中,剩下的代码将进入传统的Win32 DLL。这是我已经做过几十次的事情,我对__declspec(dllexport)
和__declspec(dllimport)
的了解非常清楚。我定义了通常的一对宏和预处理器变量,告诉编译器为调用者发出__declspec(dllimport)
,为被调用者发出__declspec(dllexport)
。这一切都是"花园品种" Windows编程,但控制台程序不会链接。
在搜索答案时,我使用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$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: unresolved external symbol __imp__AddEventSource
。
最后一点说明;错误似乎不是链接器没有看到导入库的结果,因为它是第一个搜索到的库,我可以在构建日志中看到它如此列出。我也确定没有旧版本的导入库可能会干扰,因为我从机器中删除了它的每个实例,验证它们都已消失,并重新开始。
答案 0 :(得分:2)
LNK2001:未解析的外部符号__imp__AddEventSource
链接器错误消息说明你做错了什么。它正在寻找_AddEventSource但是不是您导出的函数的名称。它是_AddEventSource @ 20。请注意添加的“@ 20”名称装饰。您通过使用DEF文件对问题进行了模糊处理,但是仍然可以从.lib文件的转储中看到它。
这个链接器错误非常符合设计,它可以保护DLL的客户端免受非常讨厌的问题。您的问题不包含任何提示,但如果信息准确无误,则您更改了全局编译选项。项目+属性,C / C ++,高级,“呼叫约定”设置。默认值为/ Gd,您将其更改为/ Gz。但不在客户端项目中做了相同的更改。这是非常讨厌的,因为客户端代码对导出函数的任何调用都会使堆栈失衡。这可能导致的运行时错误很难诊断。 @ 20 postfix旨在让它走到这一步。
更改设置并通过将__stdcall
属性添加到函数声明中来显式调用约定。
您正在犯的其他错误:
非常重要的是,不仅仅是因为这个问题,只有一个 .h文件来声明导出的函数。适合#included到客户端程序的源代码中。这样,DLL和客户端之间永远不会出现不匹配。这包括需要在声明中放置__stdcall,现在DLL和客户端将始终同意,并且/ G选项中的不匹配不会伤害任何人。
您使用TCHAR类型的方式(如声明中的PCTSTR)也是非常非常讨厌的。您现在批判性地依赖于另一个全局编译选项。项目+属性,常规,“字符集”。您也从默认设置更改了此设置,因此在客户端项目中非常容易忽略它。这个错误比/ Gz选项更糟糕,因为当你使用extern“C”时,你不会得到一个链接器错误。您获得运行时的错误行为更容易诊断,当您发现字符串只包含单个字符时,您只会失去一两个小时的生命。完全停止使用TCHAR,它在10年前停止了相关性。您编写的代码只能与char*
一起使用。
DEF文件中的VERSION语句错误,只能有2个数字,必须用.
分隔。这会在以后的VS版本中产生链接错误。您应该完全省略它,它已经过时了20多年,版本号应该在VERSION资源中设置。不要跳过将资源添加到.rc文件中,在VS中很容易做到,版本控制是为DLL导入的。
答案 1 :(得分:1)
从个人经验来看,我试图将一个新的导出函数添加到我正在维护的dll中,并且无法使编译器链接到新的导出函数。事实证明,在尝试更新64位版本的dll时,我无意中将构建目标更改为x86。