开发者

C++ wrapper DLLs to static LIBs

开发者 https://www.devze.com 2023-03-31 15:44 出处:网络
I have some statically compiled libraries (.lib) that I use in my project, which is written in C++ and built on both Windows and Linux. At my project\'s entry-point to these libraries, I use just one

I have some statically compiled libraries (.lib) that I use in my project, which is written in C++ and built on both Windows and Linux. At my project's entry-point to these libraries, I use just one or two functions from the 'main' library in the static library suite, really (but I'm sure that these functions call many others in the other libraries in the suite).

I would ideally like to instead have a suite of dynamically linked libraries (DLLs) that wraps around each of the libs in the static lib suite; I've read/heard that the way to do this on Windows (e.g., Visual Studio 2005/2008/2010) is to "create a wrapper DLL" with some exposed functions calling the underlying static library functions. I would very much appreciate if someone can give me some detailed step-by-step including possibly some snippets, of how to go about doing this in MS Visual Studio 2005/2008/2010. I am sure some of you may already be doing this on a day-to-day basis; your experience is very much appreciated.

Edit:

For the benefit of others like myself, I am posting the first 'useful' link I found: http://tom-shelton.net/index.php/200开发者_C百科8/12/11/creating-a-managed-wrapper-for-a-lib-file/


"Convert a library to another library type" seems easy, but it is not. There is no straight-forward step-by-step way to do this because C++ and DLLs do not play well together at all, and your code will need to be adapted to support a DLL interface.

A concise way to describe the problem is this:

  • A .lib's interface is C++
  • A .dll's interface is C

Thus, a DLL's interface simply doesn't support C++ and you need to be clever to make it work - this is why the ambiguous existing answers.

One standard way is via COM, which means building an entire COM wrapper for the library, complete with class factory, interfaces, objects, and using BSTR instead of std::string. I would guess is not practical.

Another solution is to create a C interface for your C++ library which is DLL-safe. That means basically creating a winapi-style interface, which again is probably not practical or defeats the purpose of using your library at all. This is what @David Heffernan suggests. But what he doesn't address is how you must change your code to be DLL-compatible.

An important but subtle problem is you cannot pass ANY templated C++ objects across DLL boundaries. This means passing std::string in or out of a DLL function is considered unsafe. Each binary gets its own copy of the std::string code, and there's no guarantee that they will happen to play nicely with each other. Each binary (potentially) also gets its own copy of the CRT and you will mess up internal state of one module by manipulating objects from another.

Edit: You can export C++ objects in MSVC using __declspec(dllexport) and importing them using __declspec(dllimport). But there are a lot of restrictions on this and subtleties that cause problems. Basically this is a shortcut for getting the compiler to create a cheap C-style interface for your exported class or function. The problem is it doesn't warn you about how much unsafe stuff is happening. To reiterate:

  1. If there are ANY templated symbols crossing DLL bounds, it is not safe (std::* for example).
  2. Any objects with CRT-managed state should not cross DLL bounds (FILE* for example).


This was a little big to add as a comment to tenfour's response...

If you want to still maintain a C++ API when using the DLL wrapper, you can put C++ to C conversion functions in the header file. This ensures that only C compatible data types ever cross the DLL boundary.

As an example

//MyDLL.h

class MyDLL {
 public:
  ...
  int Add2ToValues(std::vector<int>& someValues) {
   int* cValues = new int[someValues.size()];
   memcpy(cValues, &someValues[0], someValues.size() * sizeof(int));
   int retVal = Add2ToValues_Internal(cValues, someValues.size());
   someValues.assign(std::begin(cValues), std::end(cValues));
   delete [] cValues;
   return retVal;
  }

private:
  int Add2ToValues_Internal(int* valuesOut, const int numValues);
};

//MyDLL.cpp

 int MyDLL::Add2ToValues_Internal(int* values, const int numValues)
 {
   for(int i = 0; i < numValues; ++i) {
     values[i] += 2;
   }

   return 0;
 }

One catch I ran into when doing these wrappers is that you must allocate and deallocate any memory within the header file. Since the header file will be compiled by the application that is using your library, it will use the CRT for whatever compiler you are using to build your application. All interaction with the DLL uses C, so you won't run into any runtime mismatches and all memory is allocated and freed either entirely within the DLL or entirely within the application so you don't have any cross DLL memory management issues either. In the example, I both allocated and deallocated in the header. If you need to allocate data in the _Internal function, you'll need to also add a function that allows you to free that memory within the DLL. Once inside of the _Internal functions, you are free to use as much C++ as you want.


If you do not care about interface adaptation at all, you can export symbols from a static .lib to a .dll fairly easily. The trick is, you do not use Visual Studio GUI or projects at all, but just the linker (link.exe).

With this method, C symbols will remain C symbols and C++ symbols will remain C++ symbols. If you need to change that, you need to write wrapper code (e.g. extern C interfaces). This method simply presents existing symbols from the .objs in the .lib as official exports from the DLL.

Assume we have a .lib compiled from a source TestLib.c

#include <stdio.h>


void print(char* str)
{
    printf("%s\n", str);
}

int add(int a, int b)
{
    return a + b;
}

We compiled this into a static lib TestLib.lib. Now we wish to convert TestLib.lib to TestLibDll.dll (the base name should not be the same or you will get issues with the link output since the linker also creates DLL link .lib). To do this, we use link.exe outside Visual Studio GUI. Launch the "x64 Native Tools Command Prompt for Visual Studio xx" to get a cmd with the toolchain in path. (If you need 32 bit version, use x86 Native Tools instead). Change to the folder with TestLib.lib (e.g x64\Release). Then run:

link /DLL /EXPORT:add /EXPORT:print /OUT:TestLibDll.dll TestLib.lib

This should produce TestLibDll.dll. (The linker may complain a bit about there being no .obj, but you can ignore this.) The exports are:

dumpbin /exports TestLibDll.dll
Microsoft (R) COFF/PE Dumper Version 14.29.30040.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file TestLibDll.dll

File Type: DLL

  Section contains the following exports for TestLibDll.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 00001080 add
          2    1 00001070 print

We have successfully exported the functions.

In the case there are many functions, using /EXPORT is tedious. Instead make a .def file. For our example, here is TestLibDll.def:

LIBRARY TestLibDll
EXPORTS
    print  @1
    add    @2

The linker is then run as

link /DLL /DEF:TestLibDll.def /OUT:TestLibDll.dll TestLib.lib

This example uses x64 C symbols, which makes it straightforward. If you have C++ symbols, you need to provide the mangled version of the symbol in the /EXPORT argument or in the def file. For more complex situations than a single static lib, you may need to provide more link libraries on the command line and/or /LIBPATH args to point to link library folders.

Again, this method is only for exporting symbols verbatim from a static library. I personally used it to create a DLL to be loaded in Python with ctypes for a closed source static library. The advantage is you don't need to write any wrapper code or create any additional VS projects at all.

Note: the accepted answer provides a good discussion of pitfalls regarding C++ DLL interface and why C wrappers are a good idea. I did not focus on that here, only on the mechanics of getting the symbols to be exported to the DLL. Using a C interface to DLL if possible remains good advice.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号