3

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_
user0042
  • 7,917
  • 3
  • 24
  • 39
MDL
  • 107
  • 1
  • 10
  • Do you have access to statically linked versions of `DeviceADll` and/or `DeviceBDll`? If so, you can make two DLL yourself, each linking against one of the device library, and only exporting non-conflicting symbols. –  Aug 18 '17 at 20:08
  • The Dlls are developped by another team, so they have to stay as Dlls so future versions don't have to be rewritten with each new release. Normally it would not be that bad, but these files are 5000 lines each so it's really not ideal – MDL Aug 18 '17 at 20:14
  • Off topic: Watch out for those double underscores. Any identifier with double underscore is reserved for use by the library implementation. – user4581301 Aug 18 '17 at 21:48

1 Answers1

5

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.

  • Thanks for your answer. I know that loading Dll is done during "runtime" and not compile time. Since the library (Device_ts.h) is a static one, can it still load the Dll's? Also I guess I should load each Dll's in their associated child classes (DeviceA_ts.h and DeviceB_ts.h), right? – MDL Aug 18 '17 at 20:21
  • @Max Dan Laplace During "normal" DLL usage, there is still some partial linking performed at compile time (an address table is built that will be filed by the OS at dll load time). That's what I refer to by compile-time linking of the DLL. –  Aug 18 '17 at 20:24
  • As far as loading in each class, that's a design decision that depends on a lot of factors. For example, if all dlls have the same symbols, then I'd create a general `DeviceType` class that receives the DLL path in its constructor, and passed to each device as an injected dependency. –  Aug 18 '17 at 20:27