实战C与C++符号差异导致的连接错误
为了测试一个与系统电源有关的问题,我写了一个小程序(MFC)使用了如下API:
BOOLEAN GetPwrCapabilities(
PSYSTEM_POWER_CAPABILITIES lpSystemPowerCapabilities
);
MSDN对该函数的文档中有如下提示:
Requirements
Client: Included in Windows XP, Windows 2000 Professional, Windows Me, and Windows 98.
Server: Included in Windows Server 2003 and Windows 2000 Server.
Header: Declared in Powrprof.h.
Library: Use Powrprof.lib.
也就是需要包含Powrprof.h头文件,并在连接时使用Powrprof.lib。
但是以上两步都做了以后,编译没有问题,连接时却遇到以下错误:
BatViewDlg.obj : error LNK2001: unresolved external symbol "unsigned char __stdcall GetPwrCapabilities(struct SYSTEM_POWER_CAPABILITIES *)" (?GetPwrCapabilities@@YGEPAUSYSTEM_POWER_CAPABILITIES@@@Z)
BatViewDlg.obj : error LNK2001: unresolved external symbol "long __stdcall CallNtPowerInformation(enum POWER_INFORMATION_LEVEL,void *,unsigned long,void *,unsigned long)" (?CallNtPowerInformation@@YGJW4POWER_INFORMATION_LEVEL@@PAXK1K@Z)
Release/BatView.exe : fatal error LNK1120: 2 unresolved externals
开始怀疑是VC6的路径设置有问题,但检查并调整后也不见效,GOOGLE一下也没见什么有用信息。于是只好回过头来仔细分析错误信息。
首先看找不到函数符号(Symbol)是如下形式的。
unsigned char __stdcall GetPwrCapabilities(struct SYSTEM_POWER_CAPABILITIES *)" (?GetPwrCapabilities@@YGEPAUSYSTEM_POWER_CAPABILITIES@@@Z)
然后使用dumpbin工具观察powrprof.lib的输出:
c:\Program Files\Microsoft SDK\Lib>dumpbin /exports powrprof.lib
Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
Dump of file powrprof.lib
File Type: LIBRARY
Exports
ordinal name
_CallNtPowerInformation@20
_CanUserWritePwrScheme@0
_DeletePwrScheme@4
_EnumPwrSchemes@8
_GetActivePwrScheme@4
_GetCurrentPowerPolicies@8
_GetPwrCapabilities@4
_GetPwrDiskSpindownRange@8
_IsAdminOverrideActive@4
_IsPwrHibernateAllowed@0
_IsPwrShutdownAllowed@0
_IsPwrSuspendAllowed@0
_LoadCurrentPwrScheme@16
_MergeLegacyPwrScheme@16
_ReadGlobalPwrPolicy@4
_ReadProcessorPwrScheme@8
_ReadPwrScheme@8
_SetActivePwrScheme@12
_SetSuspendState@12
_ValidatePowerPolicies@8
_WriteGlobalPwrPolicy@4
_WriteProcessorPwrScheme@8
_WritePwrScheme@16
Summary
C9 .debug$S
14 .idata$2
14 .idata$3
4 .idata$4
4 .idata$5
E .idata$6
c:\Program Files\Microsoft SDK\Lib>
看了以后,恍然大悟。二者的符号形式大不相同,显然VC6需要的是C++格式的符号(有所谓的微软的装饰符 decoration),而powerprof.lib导出的是C语言的形式,不待任何修饰。
那么为什么会出现这个差异呢?一定是头文件出问题了。于是打开powerprof.h看,其前面的部分如下:
/*****************************************************************************\
* *
* powrprof.h - - Interface for powrprof.dll, the power policy applicator *
* *
* Version 1.0 *
* *
* Copyright (c) Microsoft Corporation. All rights reserved. *
* *
\*****************************************************************************/
// Registry storage structures for the GLOBAL_POWER_POLICY data. There are two
// structures, GLOBAL_MACHINE_POWER_POLICY and GLOBAL_USER_POWER_POLICY. the
// GLOBAL_MACHINE_POWER_POLICY stores per machine data for which there is no UI.
// GLOBAL_USER_POWER_POLICY stores the per user data.
typedef struct _GLOBAL_MACHINE_POWER_POLICY{
ULONG Revision;
SYSTEM_POWER_STATE LidOpenWakeAc;
SYSTEM_POWER_STATE LidOpenWakeDc;
ULONG BroadcastCapacityResolution;
} GLOBAL_MACHINE_POWER_POLICY, *PGLOBAL_MACHINE_POWER_POLICY;
。。。。。。
果然缺少一般API头文件所常见的编译声明,比如:
/*++
Copyright (c) Microsoft Corporation. All rights reserved.
Module Name:
setupapi.h
Abstract:
Public header file for Windows NT Setup and Device Installer services Dll.
--*/
#ifndef _INC_SETUPAPI
#define _INC_SETUPAPI
#if _MSC_VER > 1000
#pragma once
#endif
//
// Define API decoration for direct importing of DLL references.
//
#if !defined(_SETUPAPI_)
#define WINSETUPAPI DECLSPEC_IMPORT
#else
#define WINSETUPAPI
#endif
//
// determine version of setupapi based on _WIN32_WINDOWS and _WIN32_WINNT
//
// NT4 version of setupapi (0x0400) is earliest, and installed onto Win95 by IE.
// Win2k version of setupapi (0x0500) also shipped in WinME
// we'll use "0x0410" to indicate version of setupapi shipped with Win98
//
#ifndef _SETUPAPI_VER
#if defined(_WIN32_WINNT) && (!defined(_WIN32_WINDOWS) || (_WIN32_WINNT < _WIN32_WINDOWS))
#define _SETUPAPI_VER _WIN32_WINNT // SetupAPI version follows Windows NT version
#elif defined(_WIN32_WINDOWS)
#if _WIN32_WINDOWS >= 0x0490
#define _SETUPAPI_VER 0x0500 // WinME uses same version of SetupAPI as Win2k
#elif _WIN32_WINDOWS >= 0x0410
#define _SETUPAPI_VER 0x0410 // Indicates version of SetupAPI shipped with Win98
#else
#define _SETUPAPI_VER 0x0400 // Earliest SetupAPI version
#endif // _WIN32_WINDOWS
#else // _WIN32_WINNT/_WIN32_WINDOWS
#define _SETUPAPI_VER 0x0501
#endif // _WIN32_WINNT/_WIN32_WINDOWS
#endif // !_SETUPAPI_VER
#ifndef __LPGUID_DEFINED__
#define __LPGUID_DEFINED__
typedef GUID *LPGUID;
#endif
//
// Include commctrl.h for our use of HIMAGELIST and wizard support.
//
#include <commctrl.h>
#ifdef _WIN64
#include <pshpack8.h> // Assume 8-byte (64-bit) packing throughout
#else
#include <pshpack1.h> // Assume byte packing throughout (32-bit processor)
#endif
#ifdef __cplusplus
extern "C" {
#endif
//
其实最关键就是
#ifdef __cplusplus
extern "C" {
#endif
也就是告诉C++编译器后面的声明是C规范的。
于是将本来的直接包含头文件#include <Powrprof.h>修改为如下形式:
#ifdef __cplusplus
extern "C" {
#endif
#include <Powrprof.h>
#ifdef __cplusplus
}
#endif
问题果然消失。像这样的问题能不能说是微软头文件的BUG呢?如果不是,那么文档也应该有比较明显的说明吧?或许最新的SDK已经有了。但是无论如何我都感觉到SDK在变得越来越大的同时,问题也在增加。