C++ 链接器警告“;第二个定义被忽略了“;包含两个具有相同函数名的库时

C++ 链接器警告“;第二个定义被忽略了“;包含两个具有相同函数名的库时,c++,visual-studio,dll,linker-warning,C++,Visual Studio,Dll,Linker Warning,上下文 我正在做一个项目,旨在向设备发送某些命令。每个设备都可以与dll(例如deviceADll.h、deviceBDll.h)连接,并且dll不是由我编程的,我也不能以任何方式修改它们。我负责将DeviceB集成到项目中,对项目的结构进行最小的更改。我知道结构可能不是最优的和/或设计得不好,因此我愿意将有关该问题的建议作为最后的解决方案 由于设备非常相似,所有Dll函数都具有相同的名称,并且通常具有相同的原型 也正因为如此,我创建了一个父类(Device_ts.h),DeviceA_ts.h

上下文

我正在做一个项目,旨在向设备发送某些命令。每个设备都可以与dll(例如deviceADll.h、deviceBDll.h)连接,并且dll不是由我编程的,我也不能以任何方式修改它们。我负责将DeviceB集成到项目中,对项目的结构进行最小的更改。我知道结构可能不是最优的和/或设计得不好,因此我愿意将有关该问题的建议作为最后的解决方案

由于设备非常相似,所有Dll函数都具有相同的名称,并且通常具有相同的原型

也正因为如此,我创建了一个父类(Device_ts.h),DeviceA_ts.h和DeviceB_ts.h从中继承(我也有一个用于设备的工厂类,但我认为这与我的问题无关)

问题

当我尝试同时包含两个DLL时,问题就出现了:项目可以编译,但我得到了一个

警告60警告LNK4006:Connect@12已在DeviceA.lib(DeviceA.dll)中定义;忽略第二个定义C:\project\u path\DeviceB.lib(DeviceB.dll)project\u Name

接着是

警告61警告LNK4006:\uuuu imp__Connect@12已在DeviceA.lib(DeviceA.dll)中定义;忽略第二个定义C:\project\u path\DeviceB.lib(DeviceB.dll)project\u Name

Warning 62 Warning LNK4221:此对象文件未定义任何以前未定义的公共符号,因此任何使用此库的链接操作都不会使用它C:\project\u path\DeviceB.lib(DeviceB.dll)project\u Name

有没有人经历过类似的情况?我应该忽略这些警告,还是因为忽略了它们的定义而无法调用
DeviceB.h
函数?

我正在使用Visual Studio 2010,我正在编写的
设备\u ts.h
库是一个静态库,所有项目的参数(例如/MD,包括目录、依赖项、MFC等)都是根据我对此问题的研究发现正确设置的

代码

include和代码如下所示(我将只显示导致警告的函数之一,因为我在50个函数中得到相同的错误):

DeviceADll.h

#ifndef DEVICEA_H__
#define DEVICEA_H__

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

namespace DeviceA
{
// some struct definition that don't cause the linker warnings
//...

// function definitions
extern "C" HANDLE PASCAL EXPORT Connect( HANDLE h_devA, const char *ip);
// ...
} // namespace DeviceA
#ifndef DEVICEB_H__
#define DEVICEB_H__

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

namespace DeviceB
{
// some struct definition that don't cause the linker warnings
//...

// function definitions
extern "C" HANDLE PASCAL EXPORT Connect( HANDLE h_devB, const char *ip);
// ...
} // namespace DeviceB
#ifndef DEVICE_FCT_H_
#define DEVICE_FCT_H_
#ifndef EXPORT
#define EXPORT
#endif

#if _MSC_VER > 1000
#pragma once
#endif

#include "DeviceADll.h"
#include "DeviceBDll.h"

class CDevice {
public:
    virtual BOOL Connect(char *ip_addr) = 0;
};
#endif DEVICE_FCT_H_
设备bdll.h

#ifndef DEVICEA_H__
#define DEVICEA_H__

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

namespace DeviceA
{
// some struct definition that don't cause the linker warnings
//...

// function definitions
extern "C" HANDLE PASCAL EXPORT Connect( HANDLE h_devA, const char *ip);
// ...
} // namespace DeviceA
#ifndef DEVICEB_H__
#define DEVICEB_H__

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

namespace DeviceB
{
// some struct definition that don't cause the linker warnings
//...

// function definitions
extern "C" HANDLE PASCAL EXPORT Connect( HANDLE h_devB, const char *ip);
// ...
} // namespace DeviceB
#ifndef DEVICE_FCT_H_
#define DEVICE_FCT_H_
#ifndef EXPORT
#define EXPORT
#endif

#if _MSC_VER > 1000
#pragma once
#endif

#include "DeviceADll.h"
#include "DeviceBDll.h"

class CDevice {
public:
    virtual BOOL Connect(char *ip_addr) = 0;
};
#endif DEVICE_FCT_H_
设备\u ts.h

#ifndef DEVICEA_H__
#define DEVICEA_H__

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

namespace DeviceA
{
// some struct definition that don't cause the linker warnings
//...

// function definitions
extern "C" HANDLE PASCAL EXPORT Connect( HANDLE h_devA, const char *ip);
// ...
} // namespace DeviceA
#ifndef DEVICEB_H__
#define DEVICEB_H__

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

namespace DeviceB
{
// some struct definition that don't cause the linker warnings
//...

// function definitions
extern "C" HANDLE PASCAL EXPORT Connect( HANDLE h_devB, const char *ip);
// ...
} // namespace DeviceB
#ifndef DEVICE_FCT_H_
#define DEVICE_FCT_H_
#ifndef EXPORT
#define EXPORT
#endif

#if _MSC_VER > 1000
#pragma once
#endif

#include "DeviceADll.h"
#include "DeviceBDll.h"

class CDevice {
public:
    virtual BOOL Connect(char *ip_addr) = 0;
};
#endif DEVICE_FCT_H_

这是使用
LoadLibrary()
GetProcAddress()
手动加载DLL的一个很好的用例

您必须为以这种方式查找的每个函数管理一个函数指针,这有点麻烦,但是绕过操作系统的dll加载给了您很大的灵活性

还请注意,使用此方法时不需要针对DLL进行链接,DLL绑定是100%运行时的,链接器根本不涉及

下面是一个例子:

typedef void (*connect_fn)(HANDLE, const char*);

connect_fn connect_a;
connect_fn connect_b;

int main()
{
  HINSTANCE dll_a = LoadLibrary("path_to_dll_a.dll");
  HINSTANCE dll_b = LoadLibrary("path_to_dll_b.dll");

  if (!dll_a || !dll_b) {
    return 1;
  }

  connect_a = (connect_fn)GetProcAddress(dll_a , "Connect");
  connect_b = (connect_fn)GetProcAddress(dll_b , "Connect");

  // connect_a and connect_b can now be used.
  return 0;
}

编辑:基本上,我建议您将设备DLL视为插件,而不是动态库。

您是否可以访问静态链接版本的
DeviceADll
和/或
DeviceBDll
?如果是这样,您可以自己创建两个DLL,每个DLL都与一个设备库相链接,并且只导出不冲突的符号。DLL由另一个团队开发,因此它们必须保留为DLL,以便将来的版本不必随每个新版本而重写。通常情况下不会那么糟糕,但这些文件每个都有5000行,所以这并不是一个理想的话题:小心那些双下划线。任何带有双下划线的标识符都保留供库实现使用。谢谢您的回答。我知道加载Dll是在“运行时”而不是编译时完成的。由于库(Device_ts.h)是静态库,它仍然可以加载Dll的吗?另外,我想我应该将每个Dll加载到它们关联的子类(DeviceA_ts.h和DeviceB_ts.h)中,对吗?@Max Dan Laplace在“正常”Dll使用过程中,仍会在编译时执行一些部分链接(生成一个地址表,在Dll加载时由操作系统归档)。这就是我所说的DLL的编译时链接。至于在每个类中加载,这是一个设计决策,取决于很多因素。例如,如果所有DLL都有相同的符号,那么我将创建一个general
DeviceType
类,该类在其构造函数中接收DLL路径,并作为注入依赖项传递给每个设备。