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

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

问题描述

我有一个很好的库来管理需要返回字符串列表的文件。因为唯一的代码我要使用它将是C ++(和Java,但是使用C ++通过JNI)我决定使用来自标准库的向量。库函数看起来有点像这样(其中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 ; full_path);
}
}

我使用向量作为参考而不是返回值是一个尝试保持内存分配sane和因为窗口真的不喜欢我有一个c ++返回类型(谁知道为什么,externC),我的理解是,所有externC是防止名称在编译器中的错误) 。无论如何,使用它与其他c ++代码一般如下:

  #if定义_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)(files);

// ...使用文件... do b
$ b 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更不用说窗户和它可怕的多堆。



编辑:一旦崩溃文件向量超出范围。我当前的想法是,放入向量中的字符串分配在dll堆上,并释放在可执行堆上。

解决方案

你的主要问题是传递C ++类型DLL边界很难。
您需要以下


  1. 相同的编译器


  2. 相同的异常设置

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

  4. 在Visual C ++中, /发布配置

  5. 在Visual C ++中,您需要相同的Iterator调试级别

/ p>

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



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


  1. git clone https://github.com/jbandela/cppcomponents.git 。我们将以 localgit


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


输入以下



  #include< cppcomponents / cppcomponents.hpp> 

使用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(){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 :: vector< std :: string> GetFiles(){
std :: vector< std :: string> ret = {samplefile1.h,samplefile2.cpp};
return ret;

}

ImplementFiles(){}


};

CPPCOMPONENTS_DEFINE_FACTORY();

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



添加以下代码

  #includeinterfaces.h
#include< iostream>

int main(){

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

}

现在可以编译。为了表明我们在编译器之间兼容,我们将使用 cl Visual C ++编译器将 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文件,你需要添加<$ c $



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


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