1

C++ で DLL を作成しています。次に例を示します。

namespace MathFuncs
{
  class MyMathFuncs
  {
    public:
        // Returns a + b
        static __declspec(dllexport) double Add(double a, double b);

        // Returns a - b
        static __declspec(dllexport) double Subtract(double a, double b);

        // Returns a * b
        static __declspec(dllexport) double Multiply(double a, double b);

        // Returns a / b
        // Throws DivideByZeroException if b is 0
        static __declspec(dllexport) double Divide(double a, double b);
    };
}

すべてのメソッドは静的であり、静的メソッドには多くの制限があるため、私の質問は、静的メソッドなしで同じものを実装するにはどうすればよいですか? DLL には常に静的メソッドが必要ですか? この DLL を C# および IOS アプリにインポートしたいと考えています。

4

2 に答える 2

5

グローバルな C スタイルのメソッドを使用する必要があります。この理由については、こちらで説明しています

基本的には、C 関数は DLL エクスポートに適切に変換されます。これは、言語機能の点で C の方が「地面に近い」からです。C は、より直接的にマシン コードに変換されます。C++ はコンパイラ レベルで多くのことを行い、C++ 環境以外では使用できない多くの機能を提供します。このため、エクスポートされた関数は、DLL の境界を越えて適切に機能するために C スタイルに従う必要があります。つまり、テンプレートも、インライン コードも、POD 以外のクラスや構造体もありません。

次のコードを検討してください。

extern "C"
{
    __declspec(dllexport) int GlobalFunc(int n)
    {
        return n;
    }

    namespace SomeNamespace
    {
        __declspec(dllexport) int NamespaceFunction(int n)
        {
            return n;
        }
    }

    class MyClass
    {
        __declspec(dllexport) int ClassNonStatic(int n)
        {
            return n;
        }
        __declspec(dllexport) static int ClassStatic(int n)
        {
            return n;
        }
    };

}

これにより、次の DLL エクスポート関数名が生成されます。

?ClassNonStatic@MyClass@@AAEHH@Z

?ClassStatic@MyClass@@CAHH@Z

グローバル関数

名前空間関数

おかしな名前のプロジェクトは、Visual Studio でビルドされた C++ プロジェクト以外とは本質的に互換性がありません。これは名前マングリングと呼ばれ、私が話しているエクスポートされた関数の制限に対する回避策として、名前自体にいくつかの型情報を埋め込みます。技術的には、これらの関数を外部で使用できますが、壊れやすく、コンパイラ固有の動作のニュアンスに依存しています。

関数を DLL にエクスポートするための経験則は次のとおりです。C でこれを実行できますか? それができない場合は、問題が発生することはほぼ確実です。

ここで、静的クラス メソッド (本質的にはグローバル) でさえも、名前マングリングがあることに注意してくださいextern "C"。ただし、名前空間内の独立した関数は、名前マングリングなしでエクスポートされます (ただし、名前空間のスコープは失われます)。

この経験則が理にかなっている理由を理解し始めることができます。


クラスをエクスポートする場合は、経験則に従って、C で行うように DLL インターフェイスを設計しましょう。以下に例を示します。この C++ クラスを見てみましょう:

    class Employee
    {
    private:
        std::string firstName;
        std::string lastName;

    public:
        void SetFirstName(std::string& s)
        {
            this->firstName = s;
        }
        void SetLastName(std::string& s)
        {
            this->lastName = s;
        }
        std::string GetFullName()
        {
            return this->firstName + " " + this->lastName;
        }
    };

これに固執することはできません__declspec(dllexport)。そのための C インターフェイスを提供し、それをエクスポートする必要があります。このような:

    extern "C"
    {
        __declspec(dllexport) Employee* employee_Construct()
        {
            return new Employee();
        }

        __declspec(dllexport) void employee_Free(Employee* e)
        {
            delete e;
        }

        __declspec(dllexport) void employee_SetFirstName(Employee* e, char* s)
        {
            e->SetFirstName(std::string(s));
        }

        __declspec(dllexport) void employee_SetLastName(Employee* e, char* s)
        {
            e->SetLastName(std::string(s));
        }

        __declspec(dllexport) int employee_GetFullName(Employee* e, char* buffer, int bufferLen)
        {
            std::string fullName = e->GetFullName();
            if(buffer != 0)
                strncpy(buffer, fullName.c_str(), bufferLen);
            return fullName.length();
        }
    }

次に、C# 側で別の小さなラッパーを作成すると、このクラスが正常にマーシャリングされます。

特に C# へのマーシャリングの場合、別のオプションとして、C インターフェイスではなく COM インターフェイスをクラスに提供することができます。基本的には同じことですが、個別のラッパーを作成せずに COM サポートを C++ クラスに直接追加するための多くのヘルパー クラスと特別なコンパイラ サポートがあります。COM オブジェクトは、C# から直接参照できます。

しかし、それはiosでは役に立ちません...

于 2012-09-07T08:16:26.680 に答える
1

補足として、数日前に mingw/c++ を使用して 1 つの実験を行いました。

プログラムのメモリリークを見つけるためのグローバル参照カウンターがありましたが、

class ReferenceCounter /** other implementations details are omitted.*/
{
public:

static int GlobalReferenceCounter;

//version 1
static int getReferenceCount1() { return GlobalReferenceCounter;}

//verison 2
static int getReferenceCount2(); //same code of version 1 but moved into .cpp file
};

参照カウンターを使用してライブラリを DLL にコンパイルすると、変数が複製され、1 つのバージョンが DLL にコンパイルされ、1 つのバージョンがクライアント コードにコンパイルされます。

DLL のファクトリ メソッドから参照カウントされたもののインスタンスを要求すると、DLL 内の参照カウンタのみが増減されます。クライアント コードが Ref Counter から継承された独自のクラスを使用している場合、クライアント参照カウンターが増加/減少します。

したがって、メモリリークをチェックするには、プログラムの最後に行う必要があります

assert(ReferenceCounter.getReferenceCount1() == 0);
assert(ReferenceCoutner.getReferenceCount2() == 0);

これは、メモリ リークが発生した場合、これらの値の 1 つが 0 より大きいためです。最初の値が 1 より大きい場合、メモリ リークは割り当てられていないユーザー クラスが原因であり、2 番目の値が 0 より大きい場合、メモリ リークはライブラリが原因です。クラス。

割り当てられていないライブラリのクラスが原因でリークが発生した場合、これは必ずしもライブラリのバグではないことに注意してください。ライブラリの設計の欠如を意味する場合でも、ユーザーはそのクラスをリークできるため、理想的にはすべてが返される必要があるためです。安全のために適切なスマートポインターで。)

もちろん、「GlobalReferenceCoutner」がドキュメントに重複していることを指定する必要があります。そうしないと、知らないユーザーが 2 つのゲッターが冗​​長であると考えるだけで、間違いを犯したと考える可能性があります。(可能であれば、そのようなことを避けることは、あいまいで不明確です)

また、DLL を介して静的メソッドにアクセスすることは非常に危険であることも警告する必要があります。たとえば、私のクラスで参照カウンターを 2 つではなく 1 つだけにしたい場合、安全性を付与するためにそれを行う必要がありました。

class ReferenceCounter
{
public:

static int GlobalReferenceCounter;

static void duplicate() { increaseGlobalCounter(); }

static void release() { decreaseGlobalCounter(); }

static int getGlobalCounter() { return privateGetGlobalCounter(); }

private:

static increaseGlobalCounter(); // implementation into Cpp file

static decreaseGlobalCounter(); // implementation into Cpp file

static privateGetGlobalCounter(); // implementation into Cpp file

};

そうすることで、DLL の境界を越えてユーザー アプリケーションで同じ値が使用されるようになります。したがって、ここでは 2 つの異なる変数を使用する代わりに、1 つの変数を使用します (おそらく、GlobalCounter はまだユーザー実行可能ファイルにコンパイルされていますが、「クローン効果」を削除するために誰もそれを使用していません)。

于 2012-09-07T08:48:50.220 に答える