C++ LNK2001:未解析的外部符号_imp__AddEventSource

C++ LNK2001:未解析的外部符号_imp__AddEventSource,c++,dllimport,dllexport,C++,Dllimport,Dllexport,我有一个表面上看起来很简单的应用程序,类似于几十个给我带来很少或没有麻烦的类似情况。在修改了我在代码项目中找到的一个示例之后,我开始将顶级例程移动到一个新的控制台应用程序中,剩下的代码将进入传统的Win32 DLL。这是我做过几十次的事情,我对declspecdllexport和declspecdllimport都很熟悉。我定义了一对常用的宏和预处理器变量,告诉编译器为调用者发出u declspecdlimport,为被调用者发出u declspecdllexport。这一切都是花园式的Wind

我有一个表面上看起来很简单的应用程序,类似于几十个给我带来很少或没有麻烦的类似情况。在修改了我在代码项目中找到的一个示例之后,我开始将顶级例程移动到一个新的控制台应用程序中,剩下的代码将进入传统的Win32 DLL。这是我做过几十次的事情,我对declspecdllexport和declspecdllimport都很熟悉。我定义了一对常用的宏和预处理器变量,告诉编译器为调用者发出u declspecdlimport,为被调用者发出u declspecdllexport。这一切都是花园式的Windows编程,但控制台程序不会链接

查找答案,我使用微软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。