19

特定の文字列リストを返す必要があるファイルを管理するための優れたライブラリがあります。私がこれを使用する唯一のコードは C++ (および Java ですが、JNI を介して C++ を使用しています) になるため、標準ライブラリのベクトルを使用することにしました。ライブラリ関数は次のようになります (ここで、FILE_MANAGER_EXPORT はプラットフォーム定義のエクスポート要件です)。

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++ で使用するためのコードは、一般的に次のとおりです。

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

ライブラリは、add_library(file_manager SHARED file_manager.cpp) を使用して cmake でコンパイルされます。プログラムは、add_executable(file_manager_command_wrapper command_wrapper.cpp) を使用して別の cmake プロジェクトにコンパイルされます。これらのコマンドだけで、どちらにも指定されたコンパイル フラグはありません。

これで、プログラムは mac と linux の両方で完全に正常に動作します。問題は窓です。実行すると、次のエラーが発生します。

デバッグ アサーションに失敗しました!

...

式: _pFirstBlock == _pHead

これは、実行可能ファイルとロードされたdllの間のメモリヒープが分離されているためです。これは、メモリが一方のヒープに割り当てられ、もう一方のヒープで割り当て解除された場合に発生すると思います。問題は、私の人生にとって、何がうまくいかないのか理解できないことです. メモリは実行可能ファイルに割り当てられ、dll 関数への参照として渡されます。参照を介して値が追加され、それらが処理され、最終的に実行可能ファイルに割り当てが解除されます。

できればもっと多くのコードを公開したいと思いますが、私の会社の知的財産は公開できないと述べているため、上記のコードはすべて例にすぎません。

このエラーを理解するのに役立ち、デバッグして修正するための正しい方向に私を向けることができる、主題についてより多くの知識を持っている人はいますか? 残念ながら、私は Linux で開発しているため、Windows マシンをデバッグに使用することはできません。変更を gerrit サーバーにコミットし、ジェンキンを介してビルドとテストをトリガーします。コンパイルおよびテスト時に出力コンソールにアクセスできます。

stl 以外の型を使用して、c++ のベクトルを char** にコピーすることを検討しましたが、メモリ割り当ては悪夢であり、Windows はもちろん Linux でもうまく動作させるのに苦労していました。それは恐ろしい複数のヒープです。

編集: ファイル ベクトルが範囲外になるとすぐにクラッシュします。私の現在の考えでは、ベクトルに入れられた文字列は dll ヒープに割り当てられ、実行可能ヒープで割り当て解除されます。この場合、誰かがより良い解決策について教えてくれますか?

4

7 に答える 7

15

あなたの主な問題は、DLL の境界を越えて C++ 型を渡すのが難しいことです。以下が必要です

  1. 同じコンパイラ
  2. 同じ標準ライブラリ
  3. 例外の同じ設定
  4. Visual C++ では、同じバージョンのコンパイラが必要です
  5. Visual C++ では、同じデバッグ/リリース構成が必要です
  6. Visual C++ では、同じ Iterator デバッグ レベルが必要です

等々

それが必要な場合は、C++ で行う最も簡単な方法を提供するcppcomponents https://github.com/jbandela/cppcomponentsというヘッダーのみのライブラリを作成しました。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>

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

次のコードを追加します

#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

次のコードを追加します

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

int main(){

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

}

これでコンパイルできます。コンパイラ間で互換性があることを示すためclに、Visual C++ コンパイラを使用して にコンパイルUseFiles.cppUseFiles.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.dllUseFiles.exeが同じディレクトリにあることを確認してください。

実行可能ファイルを実行しますUseFiles

cppcomponents は Linux でも動作します。主な変更点は、exe をコンパイルする-ldlときにフラグを追加する必要があり、.so ファイルをコンパイルするときにフラグを追加する必要があることです-fPIC

ご不明な点がございましたら、お気軽にお問い合わせください。

于 2013-07-29T12:46:23.483 に答える
6

ここでは、悪名高い DLL コンパイラの非互換性の問題に誰もが頭を悩ませているようですが、これがヒープ割り当てに関連していることについては正しいと思います。何が起こっているのかは、ベクトル (メイン exe のヒープ スペースに割り当てられている) に、DLL のヒープ スペースに割り当てられている文字列が含まれているためだと思われます。ベクトルが範囲外になり、割り当てが解除されると、文字列の割り当ても解除しようとします。これはすべて .exe 側で発生し、クラッシュの原因となります。

2 つの本能的な提案があります。

  1. 各文字列を でラップしますstd::unique_ptr。unique_ptr が範囲外になったときにその内容の割り当て解除を処理する「deleter」が含まれています。unique_ptr が DLL 側で作成されると、そのデリータも作成されます。そのため、ベクトルが範囲外になり、その内容のデストラクタが呼び出されると、文字列は DLL にバインドされたデリータによって割り当てが解除され、ヒープの競合は発生しません。

    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)));
        }
    }
    
  2. ベクトルを DLL 側に保持し、参照を返すだけです。DLL の境界を越えて参照を渡すことができます。

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

準関連: 「ダウンキャスト」unique_ptr<Base>からunique_ptr<Derived>(DLL 境界を越えて) :

于 2014-01-18T01:40:13.073 に答える