C++ 在dll边界上传递对STL向量的引用

C++ 在dll边界上传递对STL向量的引用,c++,windows,dll,cmake,C++,Windows,Dll,Cmake,我有一个很好的库来管理需要返回特定字符串列表的文件。因为我将使用的唯一代码是C++(和java,但是C++通过JNI),所以我决定使用标准库中的向量。库函数看起来有点像这样(其中文件管理器导出是平台定义的导出要求): extern“C”文件\u管理器\u导出无效获取\u所有文件(向量和文件) { clear()文件; 对于(向量::迭代器i=file_structs.begin();i!=file_structs.end();++i) { 文件。向后推(i->full\u路径); } } 我之

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

extern“C”文件\u管理器\u导出无效获取\u所有文件(向量和文件)
{
clear()文件;
对于(向量::迭代器i=file_structs.begin();i!=file_structs.end();++i)
{
文件。向后推(i->full\u路径);
}
}

我之所以使用向量作为引用而不是返回值是为了保持内存分配正常,因为Windows对我在C++返回类型周围有“C”(真的很不高兴)(谁知道为什么,我的理解是所有的外部C”都是防止编译器中的名字篡改)。无论如何,使用其他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"); //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;
}
#如果已定义"WIN32
#包括
#定义GET_方法GetProcAddress
#定义OPEN_库(X)LoadLibrary((LPCSTR)X)
#定义库\指针\类型HMODULE
#定义CLOSE_库FreeLibrary
#否则
#包括
#定义获取方法dlsym
#定义OPEN_库(X)dlopen(X,RTLD_现在)
#定义库\指针\类型void*
#定义CLOSE\u库dlclose
#恩迪夫
typedef void(*getAllFileType)(向量和文件);
int main(int argc,字符**argv)
{
LIBRARY\u POINTER\u TYPE manager=LOAD\u LIBRARY(“LIBRARY.dll”);//只是一个示例,实际名称也是平台定义的
GetAllFileType获取所有文件指针=(GetAllFileType)获取方法(管理器,“获取所有文件”);
矢量文件;
(*获取所有文件(指针)(文件);
//…对文件做些什么。。。
返回0;
}
该库是通过cmake使用add_库(file_manager SHARED file_manager.cpp)编译的。该程序使用add_可执行文件(file_manager_command_wrapper command_wrapper.cpp)在单独的cmake项目中编译。没有为这两个命令指定编译标志,只有这些命令

现在,该程序在mac和linux上都运行良好。问题是windows。运行时,我收到以下错误:

调试断言失败

表达式:_pFirstBlock==\u pHead

我发现这是因为可执行文件和加载的DLL之间存在单独的内存堆。我相信当内存在一个堆中分配,而在另一个堆中释放时,就会发生这种情况。问题是,就我个人而言,我不知道哪里出了问题。内存在可执行文件中分配,并作为对dll函数的引用传递,通过引用添加值,然后对这些值进行处理,最后在可执行文件中重新分配

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

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

我确实考虑过使用非STL类型,将C++中的向量复制到char **,但是内存分配是一个噩梦,我努力让它在Linux上运行得很好,更不用说Windows和它的可怕的多堆了。


编辑:一旦文件向量超出范围,它肯定会崩溃。我目前的想法是,放入向量的字符串在dll堆上分配,在可执行堆上解除分配。如果是这种情况,有谁能告诉我更好的解决方案吗?

您可能遇到了二进制兼容性问题。在Windows上,如果你想在DLL之间使用C++接口,你必须确保有很多事情是有序的,对于Ex.<
  • 所有涉及的DLL必须使用相同版本的visual studio编译器生成
  • 所有DLL必须具有与C++运行时相同的版本(在VS的大多数版本中,这是配置下的运行库设置-> C++ ->项目属性中的代码生成)
  • 迭代器调试设置对于所有构建都必须相同(这是您不能混合发布和调试DLL的部分原因)
不幸的是,这并不是一份详尽的清单:(

内存在可执行文件中分配,并作为对dll函数的引用传递,通过引用添加值,然后对这些值进行处理,最后在可执行文件中重新分配

如果没有剩余空间(容量),则添加值意味着重新分配,因此旧的将被取消分配,新的将被分配。这将由库的std::vector::push_back函数完成,该函数将使用库的内存分配器


除此之外,您还有明显的编译设置必须完全匹配,当然它们是某种编译器特定的。您很可能需要在编译方面保持它们的同步。

那里的向量使用默认的std::allocator,它使用::operator new进行分配

问题是,当在DLL的上下文中使用向量时,它是用该DLL的向量代码编译的,该代码知道该DLL提供的::运算符new

EXE中的代码将尝试使用EXE的::运算符new

我打赌这在Mac/Linux而不是Windows上起作用的原因是Windows要求在编译时解析所有符号

例如,您可能看到Visual Studio给出了一个类似“未解析的外部符号”的错误,它的意思是“您告诉我这个名为foo()的函数存在,但我在任何地方都找不到它。”

这与Mac/Linux的操作不同
#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;
}
Foo *bar = nullptr;
int barCount = 0;
getFoos( bar, &barCount );
// use your foos
releaseFoos(bar);
#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;
#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();
#include "interfaces.h"
#include <iostream>

int main(){

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

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

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