8

環境は Windows、MS Visual Studio 2012 です。C++ で記述された DLL ライブラリを C (C++ ではない) アプリケーションで使用するにはどうすればよいですか? 私の DLL では、名前空間、クラスなどを使用しました。たとえば、2 つの単純なプロジェクトを作成しました。

  • DLL プロジェクト
  • EXE プロジェクト (DLL を使用)

どちらも C++ で書かれています。C++ ライブラリを使用する C で記述された同様のアプリケーションを誰かに見せてもらえますか?

ありがとうございました。

4

2 に答える 2

19

これに対する解決策は、C++ で DLL への C インターフェイスを記述し (DLL の一部として、または別の DLL として)、C コードからその C インターフェイスを使用することです。

C インターフェイスがすべての関数で構成されていることを確認してくださいextern "C"

編集: SigTerm がコメントで指摘したように、関数を作成することはお勧めできstdcallません (デフォルトではありませんが、WinAPI 関数からコピーする人もいます)。これは、他の言語から関数を使用しようとすると問題が発生するためです。

課題とその解決方法は次のとおりです。

あなたのクラスは名前空間にあります

通常、 opaque を宣言するだけで、C で C++ クラスをエクスポートできますstruct ClassName*。ただし、C は名前空間の概念を知らないため、これは名前空間内のクラスでは不可能です。それを解決するにはいくつかの方法があります:

  • 簡単な方法: C インターフェースに void ポインターを渡し、受け入れさせるだけです。

    利点:簡単に実装できます。必要なのは、各エントリ ポイントでの型キャストだけです。

    欠点: C インターフェイスはタイプ セーフではありません。インターフェイスに間違ったポインタを渡して混乱させるのは簡単です。

  • 煩わしい方法: C++ ライブラリで、公開する各 C++ クラスのグローバル スコープ基本クラスを定義し、そこから実際のクラスを派生させます。グローバル スコープの基本クラスは空である可能性があることに注意してください。これはstatic_cast、使用する前に実際の型を取得するためです。

    利点:実装が容易で、タイプセーフです。

    短所:クラスが多い場合、多くの作業が必要になります。また、そのように公開したいすべてのクラスを変更できない場合は、それができない場合があります。さらに、基底クラスであるため、C クライアントだけが必要とするように分離することはできません。C インターフェイスが含まれていない場合でも、すべての C++ クライアントはグローバル クラスを取得します。

  • ハンドルの使用: int にキャストされたポインターを実際に含む整数ハンドルを使用します。コンパイラがサポートしている場合は、使用しているプラ​​ットフォームに関係なく十分な大きさが保証されているため、 intptr_tfromを使用してください。stdint.hそれ以外の場合はlong、安全のために a を使用することをお勧めします。

    利点:void* OS のファイル処理などの低レベル関数を扱ったことのある C プログラマにとって使い慣れたインターフェイス スタイルであり、実装が容易で、 よりもわずかにタイプ セーフです。

    短所:タイプセーフではありません。関数に間違ったタイプのハンドルを渡すことができます。

  • 「カプセル化された」ハンドルの使用:void*公開する型ごとに、グローバル スコープで、aまたは整数ハンドルのいずれかのみをメンバーとして含む C 構造体を定義します。

    利点:比較的簡単な実装 (void*生のハンドルほど簡単ではありませんが)、タイプセーフ (C コードが構造体メンバーを台無しにしない限り)。

    欠点: C コンパイラの最適化が難しくなる可能性があります。しかし、それは C コンパイラに大きく依存します。

私の提案は、パフォーマンスの問題がない限り、カプセル化されたハンドルを使用することです。その場合は、整数ハンドルを使用します。

クラスインターフェースは受け入れて返しますstd::string

C コードが で動作する方法はありstd::stringません。また、インターフェイスを C でラップする作業を行いたくないことは確かです (とにかく、C インターフェイスのユーザーはそれを好まないでしょう)。したがって、C インターフェイスでchar*/を使用することは必須です。char const*

ライブラリに文字列を渡す

文字列をライブラリに渡す場合は簡単です。単に a を渡し、あとchar const*はコンストラクターに任せstd::stringます。

ライブラリから文字列を返す

これは複雑なケースです。の結果を返すことはできません。c_str一時的なstd::stringものが破棄されると、ダングリング ポインターが残るためです。したがって、基本的に次のオプションがあります。

  • staticstd::stringを保存し(戻り時に割り当てが解除されないように)、関数呼び出しの結果をそれにコピーして、 return c_str)。

    利点:実装が簡単で、完全な文字列が得られることが保証されています。

    短所:スレッド セーフではありません。また、ユーザーはすぐにコンテンツをどこかにコピーすることを覚えておく必要があります。そうしないと、関数の次の呼び出しでデータが無効になるか、さらに悪いことにポインターが無効になります。

  • ラッパー関数で適切なサイズの文字配列を割り当て、文字列の内容をその配列にコピーして、その配列へのポインターを返します。

    利点:完全な文字列を返すことが保証されます (メモリ割り当てが失敗しなかったと仮定します)。

    欠点:メモリを割り当てた DLL とは別の DLL でメモリの割り当てを解除すると、正しく動作しないことはよく知られている問題です。したがって、解放インターフェイスを提供する必要があり、C インターフェイスのユーザーは、単にメモリを解放するのではなく、常にそれを使用することを覚えておく必要があります。

  • ユーザーに文字配列と長さを渡させ、strncpy文字列データを文字配列にコピーするために使用します。

    利点:文字列のメモリ管理を行う必要はありません。それはユーザーの責任です。また、ユーザーが文字列を特定の場所に書き込みたい場合は、別のコピーを作成する代わりに、単純にアドレスをラッパーに渡すことができます。

    欠点:ユーザーが指定した配列が十分に長くない場合、切り捨てられた文字列が得られます。

私の提案:クロス DLL メモリ割り当ての問題を考えると、3 番目の方法を使用し、文字列が切り捨てられるリスクを受け入れることをお勧めします (ただし、ユーザーにエラーを報告する方法があることを確認してください)。

enumクラスの1つにメンバーがいます

これは簡単です。C インターフェースに同一の (名前を除いて) グローバル スコープの列挙型定義を提供します。同一enumの定義は識別子に対して同一の値を与えるため、値をクラス内列挙型からグローバル列挙型にキャストし、元に戻すだけで完全に機能するはずです。唯一の問題は、クラス内の列挙型を更新するたびに、クラス外の列挙型も更新することを忘れないでください。

タイプ std::vector のパブリック メンバーがあります

まあ、まず第一に、それはとにかく悪いインターフェースの決定です。これを処理する方法は、C++ でも実際にこれらのベクトルを処理する必要があるのと基本的に同じです。それらにイテレータ インターフェイスを提供します。

現在、C++ イテレータは C ではうまく機能しませんが、C では C++ イテレータ スタイルに固執する理由もありません。

インターフェイス メンバーが であることを考えるとstd::vector、別のオプションは、ベクトルの最初の要素へのポインターを与えることです。ただし、次にベクターのサイズが変更されたときに無効になるため、非常に限られた場合にのみオプションになります。

とオブジェクトへの参照をprint受け取る関数があります。std::ostream

もちろんstd::ostream、C コードでは使用できません。C では、ファイルは を使用してアクセスされますFILE*FILE*ここでの最良のオプションは、a をラップして 1 つから構築できるiostream クラスを使用することだと思います。それがMSVC ++によって提供されているかどうかはわかりませんが、そうでない場合、Boostにはそのようなストリームがあります。

印刷ラッパーはFILE*、オブジェクト ハンドル (または void ポインター、または C でオブジェクトを表すために選択した任意のオプション) を受け取り、FILE ポインターから一時的な ostream オブジェクトを構築し、ハンドルからオブジェクトへのポインターを抽出し、次にprint、ポイント先のオブジェクトを呼び出します。

追加の考慮事項: 例外

例外をキャッチして、C が処理できるエラーに変換することを忘れないでください。通常、最適なオプションはエラー コードを返すことです。

C インターフェイスのヘッダーの例

以下は、リンクされたサンプル ライブラリの可能な C インターフェイス ヘッダーです。

/* somelib_c_interface.h */
#ifndef SOMELIB_C_INTERFACE_H
#define SOMELIB_C_INTERFACE_H

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <stdlib.h>

/* typedef for C convenience */
typedef struct
{
  intptr_t handle;
} some_namespace_info;

typedef struct
{
  intptr_t handle;
} some_namespace_employee;

/* error codes for C interface */
typedef int errcode;
#define E_SOMELIB_OK 0              /* no error occurred */
#define E_SOMELIB_TRUNCATE -1       /* a string was created */
#define E_SOMELIB_OUT_OF_MEMORY -2  /* some operation was aborted due to lack of memory */
#define E_SOMELIB_UNKNOWN -100      /* an unknown error occurred */

/* construct an info object (new)
errcode some_namespace_info_new(some_namespace_info* info, char const* name, char const* number);

/* get rid of an info object (delete) */
errcode some_namespace_info_delete(some_namespace_info info);

/* Some_namespace::Info member functions */
errcode some_namespace_info_get_name(some_namespace_info info, char* buffer, size_t length);
errcode some_namespace_info_set_name(some_namespace_info info, char const* name);

errcode some_namespace_info_get_number(some_namespace_info info, char* buffer, size_t length);
errcode some_namespace_info_set_number(some_namespace_info info, char const* name);

/* the employee class */

/* replicated enum from employee */
enum some_namespace_employee_sex { male, female };

errcode some_namespace_employee_new(some_namespace_employee* employee,
                                    char const* name, char const* surname, int age,
                                    some_namespace_employee_sex sex);

errcode some_namespace_employee_delete(some_namespace_employee employee);

errcode some_namespace_employee_get_name(some_namespace_employee employee, char* buffer, size_t length);
errcode some_namespace_employee_set_name(some_namespace_employee employee, char const* name);

errcode some_namespace_employee_get_surname(some_namespace_employee employee, char* buffer, size_t length);
errcode some_namespace_employee_set_surname(some_namespace_employee employee, char const* name);

/* since ages cannot be negative, we can use an int return here
   and define negative results as error codes, positive results as valid ages
   (for consistency reason, it would probably be a better idea to not do this here,
   but I just want to show another option which sometimes is useful */
int some_namespace_employee_get_age(some_namespace_employee employee);

errcode some_namespace_employee_set_age(some_namespace_employee employee, int age);

errcode some_namespace_employee_get_set(some_namespace_employee employee,
                                        enum some_namespace_employee_sex* sex);
errcode some_namespace_employee_set_sex(some_namespace_employee employee,
                                        enum some_namespace_employee_sex sex);

typedef struct
{
  intptr_t handle;
} info_iter;

info_iter_delete(info_iter iterator);

some_namespace_info info_iter_get(info_iter iterator);
void info_iter_next(info_iter iterator);
bool info_iter_finished(info_iter iterator);

info_iter some_namespace_employee_phones(some_namespace_employee employee);
info_iter some_namespace_employee_emails(some_namespace_employee employee);
info_iter some_namespace_employee_sites(some_namespace_employee employee);

errcode some_namespace_print(FILE* dest, some_namespace_employee employee);

#ifdef __cplusplus
}
#endif

#endif // defined(SOMELIB_C_INTERFACE_H)
于 2013-06-30T17:54:48.563 に答える
3

クラス インスタンスに不透明なポインターを渡すことができる C ラッパー関数を作成する必要があります。
合理的な例のように思われるものを次に示します

リンクから引用:

typedef Foo* FOO_HDL;
extern "C" { 
__declspec(dllexport) FOO_HDL FooCreate() {
  return new Foo();
};
__declspec(dllexport) void FooDestroy(FOO_HDL obj) {
  delete obj;
};
__declspec(dllexport) double FooGetValue(FOO_HDL obj) {
  return obj->GetValue();
};
__declspec(dllexport) void FooSetValue(FOO_HDL obj, double NewValue) {
  obj->SetValue(NewValue);
};
};
于 2013-06-30T17:54:37.700 に答える