通过 dll 边界传递对 STL 向量的引用 [英] Passing reference to STL vector over dll boundary

查看:21
本文介绍了通过 dll 边界传递对 STL 向量的引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个很好的库来管理需要返回特定字符串列表的文件.由于我将使用它的唯一代码将是 C++(和 Java,但通过 JNI 使用 C++)我决定使用标准库中的向量.库函数看起来有点像这样(其中 FILE_MANAGER_EXPORT 是平台定义的导出要求):

I have a nice library for managing files that needs to return specific lists of strings. Since the only code I'm ever going to use it with is going to be C++ (and Java but that's using C++ through JNI) I decided to use vector from the standard libraries. The library functions look a little bit like this (where FILE_MANAGER_EXPORT is platform-defined export requirement):

extern "C" FILE_MANAGER_EXPORT void get_all_files(vector<string> &files)
{
    files.clear();
    for (vector<file_struct>::iterator i = file_structs.begin(); i != file_structs.end(); ++i)
    {
        files.push_back(i->full_path);
    }
}

我使用向量作为引用而不是返回值的原因是试图保持内存分配合理,因为 windows 真的不高兴我在 c++ 返回类型周围有 extern "C"(谁知道为什么,我的理解是所有 extern "C" 所做的就是防止编译器中的名称修改).不管怎样,把这个和其他c++一起使用的代码一般是这样的:

The reason I used the vector as a reference instead of return value is an attempt to keep memory allocations sane and because windows was really unhappy me having extern "C" around a c++ return type (who knows why, my understanding is that all extern "C" does is prevent name mangling in the compiler). Anyway, the code for using this with other c++ is generally as follows:

#if defined _WIN32
    #include <Windows.h>
    #define GET_METHOD GetProcAddress
    #define OPEN_LIBRARY(X) LoadLibrary((LPCSTR)X)
    #define LIBRARY_POINTER_TYPE HMODULE
    #define CLOSE_LIBRARY FreeLibrary
#else
    #include <dlfcn.h>
    #define GET_METHOD dlsym
    #define OPEN_LIBRARY(X) dlopen(X, RTLD_NOW)
    #define LIBRARY_POINTER_TYPE void*
    #define CLOSE_LIBRARY dlclose
#endif

typedef void (*GetAllFilesType)(vector<string> &files);

int main(int argc, char **argv)
{
    LIBRARY_POINTER_TYPE manager = LOAD_LIBRARY("library.dll"); //Just an example, actual name is platform-defined too
    GetAllFilesType get_all_files_pointer = (GetAllFilesType) GET_METHOD(manager, "get_all_files");
    vector<string> files;
    (*get_all_files_pointer)(files);

    // ... Do something with files ...

    return 0;
}

库是通过cmake使用add_library(file_manager SHARED file_manager.cpp)编译的.该程序使用 add_executable(file_manager_command_wrapper command_wrapper.cpp) 在单独的 cmake 项目中编译.两者都没有指定编译标志,只有那些命令.

The library is compiled through cmake using add_library(file_manager SHARED file_manager.cpp). The program is compiled in a separate cmake project using add_executable(file_manager_command_wrapper command_wrapper.cpp). There are no compile flags specified for either, just those commands.

现在该程序在 mac 和 linux 中都运行良好.问题是窗户.运行时,我收到此错误:

Now the program works perfectly fine in both mac and linux. The problem is windows. When run, I get this error:

调试断言失败!

...

表达式:_pFirstBlock == _pHead

Expression: _pFirstBlock == _pHead

我发现并有点理解这是因为可执行文件和加载的 dll 之间存在单独的内存堆.我相信当内存在一个堆中分配并在另一个堆中释放时会发生这种情况.问题是,对于我的一生,我无法弄清楚出了什么问题.内存在可执行文件中分配并作为引用传递给 dll 函数,通过引用添加值,然后这些值被处理并最终释放回可执行文件.

This, I have found out and kind of understand, is because of separate memory heaps between executables and loaded dlls. I believe this occurs when memory is allocated in one heap and deallocated in the other. The problem is, for the life of me, I can't figure what is going wrong. The memory is allocated in the executable and passed as a reference to the dll function, values are added via the reference, and then those are processed and finally deallocated back in the executable.

如果可以,我会透露更多代码,但我公司的知识产权规定我不能,所以以上所有代码都只是示例.

I would reveal more code if I could but intellectual property at my company states I can't, so all of the above code is merely examples.

任何对该主题有更多了解的人都能够帮助我理解这个错误,并为我指明调试和修复它的正确方向?不幸的是,我无法使用 Windows 机器进行调试,因为我是在 linux 上开发的,然后将任何更改提交到 gerrit 服务器,该服务器通过 jenkins 触发构建和测试.我可以在编译和测试时访问输出控制台.

Anyone with more knowledge of the subject able to help me understand this error, and point me in the right direction to debug and fix it? I'm unfortunately not able to use a windows machine for debugging since I develop on linux, then commit any changes to a gerrit server which triggers builds and tests through jenkins. I have access to the output console upon compile and test.

我确实考虑过使用非 stl 类型,将 c++ 中的向量复制到一个字符**,但是内存分配是一场噩梦,我正在努力让它在 linux 上很好地工作,更不用说 windows 了,而且它是可怕的多堆.

I did consider using non-stl types, copying the vector in c++ to a char**, but the memory allocation was a nightmare and I was struggling to get it working nicely on linux let alone windows and it's horrible multiple heaps.

一旦文件向量超出范围,它肯定会崩溃.我目前的想法是放入向量中的字符串在 dll 堆上分配并在可执行堆上释放.如果是这种情况,任何人都可以告诉我更好的解决方案吗?

It definitely crashes as soon as the files vector goes out of scope. My current thought is that the strings put into the vector are allocated on the dll heap and deallocated on the executable heap. If this is the case, can anyone enlighten me as to a better solution?

推荐答案

您的主要问题是跨 DLL 边界传递 C++ 类型很困难.您需要以下内容

Your main problem is that passing C++ types across DLL boundaries is difficult. You need the following

  1. 相同的编译器
  2. 相同的标准库
  3. 相同的例外设置
  4. 在 Visual C++ 中,您需要相同版本的编译器
  5. 在 Visual C++ 中,您需要相同的调试/发布配置
  6. 在 Visual C++ 中,您需要相同的迭代器调试级别

等等

如果这就是你想要的,我写了一个名为 cppcomponents 的只有头文件的库 https://github.com/jbandela/cppcomponents 提供了在 C++ 中执行此操作的最简单方法.您需要一个对 C++11 有强大支持的编译器.Gcc 4.7.2 或 4.8 将工作.Visual C++ 2013 预览版也适用.

If that is what you want, I wrote a header-only library called cppcomponents https://github.com/jbandela/cppcomponents that provides the easiest way to do it in C++. You need a compiler with strong support for C++11. Gcc 4.7.2 or 4.8 will work. Visual C++ 2013 preview also works.

我将引导您使用 cppcomponents 来解决您的问题.

I will walk you through using cppcomponents to solve your problem.

  1. git clone https://github.com/jbandela/cppcomponents.git 在您选择的目录中.我们将您运行此命令的目录称为 localgit

  1. git clone https://github.com/jbandela/cppcomponents.git in the directory of your choice. We will refer to the directory where you ran this command as localgit

创建一个名为 interfaces.hpp 的文件.在此文件中,您将定义可跨编译器使用的接口.

Create a file called interfaces.hpp. In this file you will define the interface that can be used across compilers.

输入以下内容

#include <cppcomponents/cppcomponents.hpp>

using cppcomponents::define_interface;
using cppcomponents::use;
using cppcomponents::runtime_class;
using cppcomponents::use_runtime_class;
using cppcomponents::implement_runtime_class;
using cppcomponents::uuid;
using cppcomponents::object_interfaces;

struct IGetFiles:define_interface<uuid<0x633abf15,0x131e,0x4da8,0x933f,0xc13fbd0416cd>>{

    std::vector<std::string> GetFiles();

    CPPCOMPONENTS_CONSTRUCT(IGetFiles,GetFiles);


};

inline std::string FilesId(){return "Files!Files";}
typedef runtime_class<FilesId,object_interfaces<IGetFiles>> Files_t;
typedef use_runtime_class<Files_t> Files;

接下来创建一个实现.为此,请创建 Files.cpp.

Next create an implementation. To do this create Files.cpp.

添加以下代码

#include "interfaces.h"


struct ImplementFiles:implement_runtime_class<ImplementFiles,Files_t>{
  std::vector<std::string> GetFiles(){
    std::vector<std::string> ret = {"samplefile1.h", "samplefile2.cpp"};
    return ret;

  }

  ImplementFiles(){}


};

CPPCOMPONENTS_DEFINE_FACTORY();

最后这里是使用上面的文件.创建 UseFiles.cpp

Finally here is the file to use the above. Create UseFiles.cpp

添加以下代码

#include "interfaces.h"
#include <iostream>

int main(){

  Files f;
  auto vec_files = f.GetFiles();
  for(auto& name:vec_files){
      std::cout << name << "
";
    }

}

现在可以编译了.为了表明我们在编译器之间兼容,我们将使用 cl Visual C++ 编译器将 UseFiles.cpp 编译成 UseFiles.exe.我们将使用 Mingw Gcc 将 Files.cpp 编译成 Files.dll

Now you can compile. Just to show we are compatible across compilers, we will use cl the Visual C++ compiler to compile UseFiles.cpp into UseFiles.exe. We will use Mingw Gcc to compile Files.cpp into Files.dll

cl/EHsc UseFiles.cpp/I localgitcppcomponents

其中 localgit 是您运行上述 git clone 的目录

where localgit is the directory in which you ran git clone as described above

g++ -std=c++11 -shared -o Files.dll Files.cpp -I localgitcppcomponents

没有链接步骤.只需确保 Files.dllUseFiles.exe 在同一目录中.

There is no link step. Just make sure Files.dll and UseFiles.exe are in the same directory.

现在使用 UseFiles

cppcomponents 也适用于 Linux.主要的变化是在编译exe的时候需要在flag中添加-ldl,在编译.so文件的时候需要在flag中添加-fPIC标志.

cppcomponents will also work on Linux. The main change is when you compile the exe, you need to add -ldl to the flag, and when you compile the .so file, you need to add -fPIC to the flags.

如果您还有其他问题,请告诉我.

If you have further questions, let me know.

这篇关于通过 dll 边界传递对 STL 向量的引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆