none
C++で書いたDLLの使い方を教えてください RRS feed

  • 質問

  • お世話になっています。

    C++でDLLを書いて、別のC++のコードから読み出ししたいです

    環境はVS2008 WinXPです

    下記のコードを書いてコンパイルしてtest_dll.hを作成しました

    // test_dll.h
    #pragma once
    namespace test_dll 
    {
    	class MyClass1
    	{
    	public:
    		void Open(void)
    		{
    			
    		}
    		void read(int *d)
    		{
    			d=d++;
    		}
    	};
    }

    別途、Win32コンソールアプリを作って、下記のようにコードをかきました

    そして、ビルドしましたが

    エラー 1 error C2065: 'MyClass1' : 定義されていない識別子です。

    と出てコンパイルできません、どうすればコンパイル可能ですか?

    #include "stdafx.h"
    #include <windows.h>
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        HMODULE hDll = LoadLibrary(_T("test_dll.dll"));
        if (hDll == NULL) {
            printf("can not load dll\n");
    	}
    	MyClass1 my = new MyClass1();
    	return 0;
    }



    2012年6月17日 8:22

回答

  • こんにちは。(私がこれを書いてる時間は深夜ですが)



    とりあえず、マネージネイティブをDLLで連携させるより前に

    まずネイティブ同士の方法が分からない、ということでしょうか?


    __declspec(dllexport) 関連が全く処理系依存でないかどうかあいまいだったので

    私自身ネイティブなクラスの、DLLでのエクスポート・インポートは自分では控えていましたが

    (Defファイルで制御しやすい、関数を介しての自作オブジェクトの操作はありました)

    気になったので試してみました。



    その前にBB-X LARISSAさんのコードについて思った留意点を挙げておきますと


    .DLLを使う、つまりエクスポート・インポートするということなら、インライン指定する必要ないですね。(してもされないだけとは思いますが)



    		void read(int *d)
    		{
    			d=d++;
    		}

    普通の実装のコンパイラであれば『鼻から悪魔』はないとは思いますが

    これは一応未定義動作だったような気がします。

    やるとすれば

    d++; か d = d+1; でしょうが

    この場合コピーされた(引数の)ポインタ変数に対して操作を加えても、この関数の内容から判断して、実質的な効果がありません。

    また、単純な型なら最適化で同じになる可能性が高いでしょうが

    これがもし今後自作型のオブジェクトの参照(と演算子のオーバーロード)に変更になった時に

    通常前置インクリメントの方が処理の手間が少なくて済む可能性があるため、少しでもその可能性があるなら

    ホントにやるとすれば

    元のint型の内容を変えたいのであれば

    void Read( int* d){ ++(*d); }

    あるいは

    void Read( int& d ){++d;}

    ポインタを一つ進めたいのであれば

    void Read( int** d ){ ++(*d); }

    あるいは

    void Read( int*& d ){ ++d; }

    かとおもいます。


    .LoadLibraryが成功したら、DLLの機能をアプリ中で使い終わった時には(常に)FreeLibrayを呼び出して参照カウントを減らしとく方が(少なくとも作法は)良いと思います。



    MyClass1 my = new MyClass1();

    これはC++の文法じゃない気がします。//何か細工してれば絶対出来ない事はない、かもしれませんが

    普通のケースであれば

    MyClass1 my; //ローカル変数

    MyClass1* my = new MyClass1; //動的確保。引数なしのコンストラクタの場合は()はあってもなくても

    のはずです。


    5.このプログラムでは影響は皆無と言っていいでしょうが、大規模(や長時間稼働する)アプリではメモリリークは致命傷となりかねません。

    仮に動的確保をするために

    MyClass1* my = new MyClass1();

    とした場合は

    使い終わってから

    delete my; //この後myポインタを使いまわすならmy=NULL;としておくなど(C++11ではnullptr)

    を常にやっておくほうが作法が良いと思います。



    以上を踏まえて、以下サンプルです。(小さなサンプルとしての単純な伝わりやすさのために、defineやtypedefはせず、また何をインクルードしてるか不透明になる要素(stdafx.h)なども排除します。)


    エクスポート側test_dll.h

    #pragma once
    
    namespace test_dll  {
    	
    	class __declspec(dllexport) MyClass1 {
    
    	public:
    
    		MyClass1(); //インライン化の必要がないのでヘッダに実装を書かない
    		~MyClass1();
    		void Open();
    		void Read(int*&);
    
    	};
    
    }

    エクスポート側test_dll.cpp

    #include "test_dll.h" namespace test_dll { MyClass1::MyClass1(){} MyClass1::~MyClass1(){} void MyClass1::Open(){} void MyClass1::Read( int*& p ){ ++p; } //ポインタを移動させると仮定 }



    インポート側のexe( main.cpp )

    ただしインポート側ではtest_dll.hでは__declspec(dllexport)の部分を__declspec(dllimport)に変えます。

    ホントは同じヘッダをそのまま流用出来るように、マクロを使って簡単に切り替えられるようにしておくのが良いです。

    (Azuleanさんご提示のリンク先の CLASS_DECLSPEC がそれに該当)


    #include <tchar.h> #include <windows.h> #include <stdio.h> #include "test_dll.h" //dllexport→dllimportに /* 例によって簡易実験のためにpragma comment 実際にはリンクは出来ればプロジェクトのプロパティで行ってください */ #pragma comment(lib, "test_dll.lib") int _tmain(int argc, _TCHAR* argv[]){ /* 一応FreeLibraryの提示のために残しておきますが 上記のとおりdllexportとdllimportを使ってリンクしてクラスを使う分には LoadLibraryやFreeLibraryを呼ぶ必要はないと考えていいはずです。 */ HMODULE hDll = LoadLibrary(_T("test_dll.dll")); if (!hDll){ printf("can not load dll\n"); } else { enum : int { LEN = 5 }; test_dll::MyClass1* my = new test_dll::MyClass1; //生成 int data[LEN], *p = data; /* ポインタをずらしつつ値を入れてみる。 簡単な内容のはずなのに可読性は必要以上に低いですがあくまで実験なので */ for ( int i=0; i<LEN; ++i ){ *p = i*2; my->Read( p ); } for ( int i=LEN; i--; ) printf( "%d ", data[i] ); //8 6 4 2 0 が表示される delete my; //破棄 FreeLibrary( hDll ); //LoadLibraryに対してFreeLibarary } Sleep( 1000 ); //余裕を持って確認できる方法を適当に return 0; }

    一例としてこんな感じですかね。

    ※一応突っ込みどころありましたらご指摘お願いします。

    • 編集済み mr.setup 2012年6月17日 18:09
    • 回答としてマーク BB-X LARISSA 2012年6月18日 1:12
    2012年6月17日 17:40

すべての返信

  • コンパイラが見える範囲に宣言されていない(知らない)ものが利用できないのは当然です。
    この場合、MyClass1 の宣言が必要なので、ヘッダーファイルをインクルードするなどの対応が必要です。

    この辺を見るとか、DLL プロジェクトの新規作成時にシンボルのエクスポートにチェックを入れたテンプレートを見て、学び取ってください。この事例の場合、lib ファイルへのリンクが必要になります。

    // これだけで問題が解決するかはわかりませんが…。

    2012年6月17日 10:48
    モデレータ
  • 厳しいですが…もっとC言語、C++言語について勉強してください。キーワードとしては、宣言・定義・リンク 辺りの知識が足りていません。
    例えば、printf()の呼び出し方はわかりますか? 本質的にはそれと同じことをすればいいのですが。
    # printf()だってDLLに定義されている識別子です。

    2012年6月17日 13:27
  • こんにちは。(私がこれを書いてる時間は深夜ですが)



    とりあえず、マネージネイティブをDLLで連携させるより前に

    まずネイティブ同士の方法が分からない、ということでしょうか?


    __declspec(dllexport) 関連が全く処理系依存でないかどうかあいまいだったので

    私自身ネイティブなクラスの、DLLでのエクスポート・インポートは自分では控えていましたが

    (Defファイルで制御しやすい、関数を介しての自作オブジェクトの操作はありました)

    気になったので試してみました。



    その前にBB-X LARISSAさんのコードについて思った留意点を挙げておきますと


    .DLLを使う、つまりエクスポート・インポートするということなら、インライン指定する必要ないですね。(してもされないだけとは思いますが)



    		void read(int *d)
    		{
    			d=d++;
    		}

    普通の実装のコンパイラであれば『鼻から悪魔』はないとは思いますが

    これは一応未定義動作だったような気がします。

    やるとすれば

    d++; か d = d+1; でしょうが

    この場合コピーされた(引数の)ポインタ変数に対して操作を加えても、この関数の内容から判断して、実質的な効果がありません。

    また、単純な型なら最適化で同じになる可能性が高いでしょうが

    これがもし今後自作型のオブジェクトの参照(と演算子のオーバーロード)に変更になった時に

    通常前置インクリメントの方が処理の手間が少なくて済む可能性があるため、少しでもその可能性があるなら

    ホントにやるとすれば

    元のint型の内容を変えたいのであれば

    void Read( int* d){ ++(*d); }

    あるいは

    void Read( int& d ){++d;}

    ポインタを一つ進めたいのであれば

    void Read( int** d ){ ++(*d); }

    あるいは

    void Read( int*& d ){ ++d; }

    かとおもいます。


    .LoadLibraryが成功したら、DLLの機能をアプリ中で使い終わった時には(常に)FreeLibrayを呼び出して参照カウントを減らしとく方が(少なくとも作法は)良いと思います。



    MyClass1 my = new MyClass1();

    これはC++の文法じゃない気がします。//何か細工してれば絶対出来ない事はない、かもしれませんが

    普通のケースであれば

    MyClass1 my; //ローカル変数

    MyClass1* my = new MyClass1; //動的確保。引数なしのコンストラクタの場合は()はあってもなくても

    のはずです。


    5.このプログラムでは影響は皆無と言っていいでしょうが、大規模(や長時間稼働する)アプリではメモリリークは致命傷となりかねません。

    仮に動的確保をするために

    MyClass1* my = new MyClass1();

    とした場合は

    使い終わってから

    delete my; //この後myポインタを使いまわすならmy=NULL;としておくなど(C++11ではnullptr)

    を常にやっておくほうが作法が良いと思います。



    以上を踏まえて、以下サンプルです。(小さなサンプルとしての単純な伝わりやすさのために、defineやtypedefはせず、また何をインクルードしてるか不透明になる要素(stdafx.h)なども排除します。)


    エクスポート側test_dll.h

    #pragma once
    
    namespace test_dll  {
    	
    	class __declspec(dllexport) MyClass1 {
    
    	public:
    
    		MyClass1(); //インライン化の必要がないのでヘッダに実装を書かない
    		~MyClass1();
    		void Open();
    		void Read(int*&);
    
    	};
    
    }

    エクスポート側test_dll.cpp

    #include "test_dll.h" namespace test_dll { MyClass1::MyClass1(){} MyClass1::~MyClass1(){} void MyClass1::Open(){} void MyClass1::Read( int*& p ){ ++p; } //ポインタを移動させると仮定 }



    インポート側のexe( main.cpp )

    ただしインポート側ではtest_dll.hでは__declspec(dllexport)の部分を__declspec(dllimport)に変えます。

    ホントは同じヘッダをそのまま流用出来るように、マクロを使って簡単に切り替えられるようにしておくのが良いです。

    (Azuleanさんご提示のリンク先の CLASS_DECLSPEC がそれに該当)


    #include <tchar.h> #include <windows.h> #include <stdio.h> #include "test_dll.h" //dllexport→dllimportに /* 例によって簡易実験のためにpragma comment 実際にはリンクは出来ればプロジェクトのプロパティで行ってください */ #pragma comment(lib, "test_dll.lib") int _tmain(int argc, _TCHAR* argv[]){ /* 一応FreeLibraryの提示のために残しておきますが 上記のとおりdllexportとdllimportを使ってリンクしてクラスを使う分には LoadLibraryやFreeLibraryを呼ぶ必要はないと考えていいはずです。 */ HMODULE hDll = LoadLibrary(_T("test_dll.dll")); if (!hDll){ printf("can not load dll\n"); } else { enum : int { LEN = 5 }; test_dll::MyClass1* my = new test_dll::MyClass1; //生成 int data[LEN], *p = data; /* ポインタをずらしつつ値を入れてみる。 簡単な内容のはずなのに可読性は必要以上に低いですがあくまで実験なので */ for ( int i=0; i<LEN; ++i ){ *p = i*2; my->Read( p ); } for ( int i=LEN; i--; ) printf( "%d ", data[i] ); //8 6 4 2 0 が表示される delete my; //破棄 FreeLibrary( hDll ); //LoadLibraryに対してFreeLibarary } Sleep( 1000 ); //余裕を持って確認できる方法を適当に return 0; }

    一例としてこんな感じですかね。

    ※一応突っ込みどころありましたらご指摘お願いします。

    • 編集済み mr.setup 2012年6月17日 18:09
    • 回答としてマーク BB-X LARISSA 2012年6月18日 1:12
    2012年6月17日 17:40
  • 回答ありがとうございます

    DLL読み出し側でDLL側のヘッダを読みだすようにするんですね

    2012年6月18日 0:57
  • 回答ありがとうございます
    printfはstdio.hをインクルードして使うんだったとおもいます

    ヘッダのインクルードがぬけていました

    もっと勉強します

    2012年6月18日 0:59
  • 回答ありがとうございます

    > とりあえず、マネージネイティブをDLLで連携させるより前に
    > まずネイティブ同士の方法が分からない、ということでしょうか?

    そうです、マネージネイティブの連携にあたって、ネイティブ同士の連携させる方法が必要だったのでご質問させていただいております

    ご提示頂いたサンプルで、インポートエクスポートのマクロを記述して、そのままで動きました

    エクスポート側test_dll.cpp

    // これは メイン DLL ファイルです。
    #include "stdafx.h"
    #define _EXPORTING
    #include "test_dll.h"
    namespace test_dll {
        MyClass1::MyClass1(){}
        MyClass1::~MyClass1(){}
        void MyClass1::Open(){}
        void MyClass1::Read( int*& p ){ ++p; } //ポインタを移動させると仮定
    }
    エクスポート側test_dll.h
    #pragma once
    #ifdef _EXPORTING
       #define CLASS_DECLSPEC    __declspec(dllexport)
    #else
       #define CLASS_DECLSPEC    __declspec(dllimport)
    #endif
    namespace test_dll  {
    	class CLASS_DECLSPEC MyClass1 {
    	public:
    		MyClass1(); //インライン化の必要がないのでヘッダに実装を書かない
    		~MyClass1();
    		void Open();
    		void Read(int*&);
    	};
    }

    やりたかったことを明確にご提示頂いて、

    メモリリークの注意点など詳細なご教示も頂き大変感謝しております

    2012年6月18日 1:12
  • 付け加えるなら、printf()やMFCクラスを使うときにLoadLibrary()は呼びませんよね。

    DLLに定義されている関数やクラスを呼び出す方法が3種類あります。

    • LoadLibrary()を使わない方法
    • LoadLibrary()を使う方法
    • /DELAYLOADを使う方法

    このうち最初のものはprintf()などで既に実践しているはずですから、それを同じことをやってみてください。それで不都合があれば、問題点を挙げて再度質問すればいいと思います。

    2012年6月18日 1:15