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 的导入。