c++visual-studiodlllinker-warning

Linker warning "second definition ignored" when including two libraries with same function names


Context

Im working on a project designed to send certain commands to a device. Each device can be interfaced with a dll (e.g. deviceADll.h, deviceBDll.h) and the Dll's were not programmed by me, nor can I modify them in any way. I am in charge of integrating DeviceB to the project, with minimal changes to the structure of the project. I know the structure may not be optimal and/or well designed, so I am willing to take suggestion concerning that matter as a last resort solution.

Since the devices are very similar, all Dll functions have the same name, and often the same prototype.

Also because of this, I made a parent class (Device_ts.h), from which DeviceA_ts.h and DeviceB_ts.h inherit (I also have a factory class for the Devices, but I don't think that it's relevant to my problem).

Problem

The problem occurs when I try to include both Dlls: the project compiles, but I get a

Warning 60 warning LNK4006: Connect@12 already defined in DeviceA.lib(DeviceA.dll); second definition ignored C:\project_path\DeviceB.lib(DeviceB.dll) Project_Name

followed by a

Warning 61 warning LNK4006: __imp__Connect@12 already defined in DeviceA.lib(DeviceA.dll); second definition ignored C:\project_path\DeviceB.lib(DeviceB.dll) Project_Name

and a

Warning 62 warning LNK4221: This object file does not define any previously undefined public symbols, so it will not be used by any link operation that consumes this library C:\project_path\DeviceB.lib(DeviceB.dll) Project_Name

Has anyone experienced a similar situation? Should I ignore those warning or will I not be able to call DeviceB.h functions since their definitions are ignored?

I am using Visual Studio 2010, the Device_ts.h library I am writing is a static library and all the project's parameters (e.g. /MD, include directories, dependencies, MFC, etc) are set properly from what I found in my research for this problem.

Code

The include and code looks like this (I will only show one of the functions that cause the warning since I get the same error on 50 functions):

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

DeviceBDll.h

#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

Device_ts.h

#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_

Solution

  • This is a good use-case for manual DLL loading, using LoadLibrary() and GetProcAddress().

    You'll have to manage a function pointer for each function looked up this way, which is a bit of a pain, but bypassing the OS's dll loading gives you a lot of flexibility.

    Also note that you do not need to link against the DLL when using this method, the dll binding is 100% runtime, and the linker is not involved at all.

    Here's an example:

    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;
    }
    

    Edit: Basically, I suggest you treat the device DLLs as plugins, rather than dynamic libraries.