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

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

问题描述

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

  externCFILE_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-> ;完整路径);
}
}

我使用向量作为参考而不是返回值是一个尝试保持内存分配的理由,因为Windows真的不满意我在c ++返回类型周围有exC(谁知道为什么,我的理解是所有externC都是防止编译器中的名称变形) 。无论如何,与其他c ++一起使用的代码一般如下:

  #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); //只是一个例子,实际名称也是平台定义的。
GetAllFilesType get_all_files_pointer =(GetAllFilesType)GET_METHOD(manager,get_all_files);
vector< string>文件;
(* get_all_files_pointer)(文件);

// ...执行某些文件...

return 0;
}

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



现在程序在Mac和linux中工作得很好。问题是windows。运行时,我收到这个错误:


调试断言失败!



。 ..



表达式:_pFirstBlock == _pHead


发现和种类的理解,是因为可执行文件和加载dll之间的单独的内存堆。我相信这是在一个堆中分配内存并在另一个堆中释放时发生的。问题是,对于我的生活,我无法确定出了什么问题。内存被分配在可执行文件中,并作为dll函数的引用传递,值将通过引用添加,然后被处理并最终在可执行文件中释放。



如果我可以知道我的公司知识产权,我会发现更多的代码,我不能,所以以上所有代码仅仅是例子。



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



我确实考虑使用非stl类型,将c ++中的向量复制到char **,但内存分配是一场噩梦,我正在努力让它在linux上更好地工作,更不用说Windows,这是可怕的多重堆。



编辑:一旦文件向量就会崩溃超出范围。我目前的想法是,放在矢量中的字符串被分配在dll堆上,并在可执行堆上释放。如果是这种情况,任何人都可以启发我更好的解决方案?

解决方案

您的主要问题是跨越C ++类型DLL边界是困难的。
您需要以下


  1. 相同的编译器

  2. 相同的标准库

  3. 异常的相同设置

  4. 在Visual C ++中,您需要相同版本的编译器

  5. 在Visual C ++中,您需要相同的Debug / release配置

  6. 在Visual C ++中,需要相同的迭代器调试级别

/ p>

如果这是你想要的,我写了一个仅标题库cppcomponents https://github.com/jbandela/cppcomponents ,它提供了在C ++中最简单的方法。
您需要一个对C ++ 11有强大支持的编译器。 Gcc 4.7.2或4.8将工作。 Visual C ++ 2013的预览版也可以使用。



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

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


  2. 创建一个名为 interfaces.hpp 。在这个文件中,您将定义可以在编译器之间使用的接口。


输入以下

  #include< cppcomponents / cppcomponents.hpp> 

使用cppcomponents :: define_interface;
使用cppcomponents :: use;
使用cppcomponents :: runtime_class;
使用cppcomponents :: use_runtime_class;
使用cppcomponents :: implement_runtime_class;
使用cppcomponents :: uuid;
使用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(){returnFiles!Files;}
typedef runtime_class< FilesId,object_interfaces< IGetFiles>> Files_t;
typedef use_runtime_class< Files_t>文件;

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



添加以下代码

  #includeinterfaces.h


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

}

实施文件(){}


};

CPPCOMPONENTS_DEFINE_FACTORY();

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



添加以下代码

  #includeinterfaces.h
#include< iostream>

int main(){

文件f;
auto vec_files = f.GetFiles(); (auto& name:vec_files)的
{
std :: cout<<名称<< \\\
;
}

}

现在可以编译。为了显示我们在编译器之间是兼容的,我们将使用Visual C ++编译器 cl UseFiles.cpp code> UseFiles.exe 。我们将使用Mingw Gcc将 Files.cpp 编译成 Files.dll



cl / EHsc UseFiles.cpp / I localgit\cppcomponents



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



g ++ -std = c ++ 11 -shared -o Files.dll Files.cpp -I localgit\cppcomponents



没有链接步骤。只需确保 Files.dll UseFiles.exe 在同一目录。



现在运行 UseFiles



cppcomponents的可执行文件也可以在Linux上工作。主要的变化是编译exe时,需要在标志中添加 -ldl ,编译.so文件时,需要添加 -fPIC 到标志。



如果还有其他问题,请通知我。


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

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

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.

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

Debug Assertion Failed!

...

Expression: _pFirstBlock == _pHead

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.

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.

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.

EDIT: 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?

解决方案

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

  1. Same compiler
  2. Same standard library
  3. Same settings for exceptions
  4. In Visual C++ you need same version of the compiler
  5. In Visual C++ you need same Debug/Release configuration
  6. In Visual C++ you need same Iterator debug level

And so on

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.

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

  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

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

Enter the following

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

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

Add the following code

#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();

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

Add the following code

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

int main(){

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

}

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 localgit\cppcomponents

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 localgit\cppcomponents

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

Now run the executable with UseFiles

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天全站免登陆