LNK2001:未解析的外部符号__imp__AddEventSource

时间:2015-01-18 06:26:23

标签: c++ dllimport dllexport

我的应用程序似乎表面上看起来很简单,并且类似于给我很少或没有麻烦的类似情况。在调整了我在代码项目中找到的样本后,我开始将顶级例程移动到一个新的控制台应用程序中,剩下的代码将进入传统的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

最后一点说明;错误似乎不是链接器没有看到导入库的结果,因为它是第一个搜索到的库,我可以在构建日志中看到它如此列出。我也确定没有旧版本的导入库可能会干扰,因为我从机器中删除了它的每个实例,验证它们都已消失,并重新开始。

2 个答案:

答案 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)

  • 确保将项目构建为64位或32位,并且可以在x64或x86下的正确bin文件夹中找到该库。

从个人经验来看,我试图将一个新的导出函数添加到我正在维护的dll中,并且无法使编译器链接到新的导出函数。事实证明,在尝试更新64位版本的dll时,我无意中将构建目标更改为x86。