としおの読書生活

田舎に住む社会人の読書記録を綴ります。 主に小説や新書の内容紹介と感想を書きます。 読書の他にもワイン、紅茶、パソコン関係などの趣味を詰め込んだブログにしたいです。

タグ:C++

3753817_s

本記事ではVisual Studio Codeで利用できるコンパイラのMinGwでDLLを作成して、呼び出す方法までを紹介します。

DLLを作成するとか難しそうというイメージをもっている人もいるかもしれませんが、やってみると意外と簡単なのでこの機会に作り方を覚えてしまいましょう。



DLLを作成する


今回は、サンプルとして足し算と引き算を行う関数が実装されているDLLを作成します。

ソースコードは以下の通りです。


DLLのヘッダファイル


// Calc.h
#ifndef H_CALC
#define H_CALC

int addition(int xint y);     // x+yをする
int subtraction(int xint y);  // x-yをする

#endif // H_CALC


DLLのソースファイル


// Calc.cpp
#include "Calc.h"

int addition(int xint y){
    return x+y;
}

int subtraction(int xint y){
    return x-y;
}


オブジェクトファイルの作成(.o)


ソースコードを用意したところでMinGwのG++コマンドを使ってDLLを作成していきましょう。

先ほどのソースコードにもコメントで書いているのですが、ソースコード名はCalc.cppです。

まず最初に-cオプションを使ってオブジェクトファイルを作成します。コマンドは以下のとおりです。

g++ -c .\Calc.cpp

このコマンドを実行するとCalc.cppと同じディレクトリにCalc.oというファイルが作成されます。


DLLの作成


DLLは-sharedオプションを使用することで作成することができます。コマンドは以下のとおりです。

g++ .\Calc.o -o Calc.dll -shared

上記のコマンドを実行するとCalc.dllというファイルが作成されます。

DLLの作成は以上で終了になります。
たったこれだけでいいの問う感じですが、やってみるとむちゃくちゃ簡単なんですよね。



作成したDLLを使う


次に先ほど作成したCalc.dllを呼び出す方法を紹介していきます。


DLLの呼び出し元のソースコード


// main.cpp
#include <iostream>
#include "Calc.h"

int main(void){
    int xy;

    std::cin >> x >> y;
    std::cout << "addition:" << addition(xy<< std::endl;
    std::cout << "subtraction:" << subtraction(xy<< std::endl;
    return 0;
}


コンパイルする


ソースコードができたらコンパイルしていきましょう。

コンパイルのコマンドは呼び出し元のコードをコンパイルするときにDLLの名前を教えてあげるだけです。

g++ -o .\main.exe .\main.cpp .\Calc.dll


実行結果


.\main.exe
3 5
addition:8
subtraction:-2


コンパイルして作成したmain.exeを実行してみると、DLLの関数が呼び出されていることが分かりますね。



まとめ


今回は、MinGwでDLLを作成して、その後作成したDLLを使う方法を紹介しました。

今回はコマンドを全て手打ちで実行していきましたが、ソースコードが増えるとすごく面倒です。

なのでそいうときはmakeファイルを作成してみましょう。

makeファイルの作り方については後日紹介させていただきます。




3753817_s

本記事では、C言語からC++で作成したDLLを呼びだす方法の失敗例と成功例を紹介していきます。

結果だけが知りたいという人はC言語からC++のDLLを呼び出すの章を読むだけで大丈夫です。



C++のDLLが呼び出せない例


まず最初にCからC++のDLLの呼び出しが失敗する例を紹介します。


C++のDLLのコード


ヘッダファイル

#ifndef H_SAMPLE
#define H_SAMPLE

void PrintSample();

#endif // H_SAMPLE


ソースファイル

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

void PrintSample(){
    std::cout << "Hello CPP!!" << std::endl;
}


C言語のコード


#include "sample.h"

int main(void){
    PrintSample();
    return 0;
}


上記のコードをコンパイルすると下記のエラーコードがでてコンパイルすることができません。

undefined reference to `PrintSample' collect2.exe: error: ld returned 1 exit status




C言語からC++のDLLを呼び出す


上記のコードからC++のDLLのヘッダファイルを修正するだけで、C言語からC++の関数を呼びだせるようになります。

修正内容はヘッダファイルにextern "C"を追加するだけです。

修正したコードを以下にのせます。DLLのヘッダファイル以外は先ほどの失敗例で紹介したものと同じです。

ヘッダファイル

#ifndef H_SAMPLE
#define H_SAMPLE

#ifdef __cplusplus
extern "C"{
#endif /* __cplusplus */

    extern void PrintSample();

#ifdef __cplusplus
#endif /* __cplusplus */
#endif // H_SAMPLE


ソースファイル

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

void PrintSample(){
    std::cout << "Hello CPP!!" << std::endl;
}


C言語のコード


#include "sample.h"

int main(void){
    PrintSample();
    return 0;
}


実行結果


Hello CPP!!




まとめ


extern "C"をC++のDLLのヘッダファイルに追加するだけで、簡単にC言語からC++のDLLを利用することができました。

ちなみに今回作成したDLLはC++でもそのまま利用することができるので、C言語用とC++用でDLLを2つ作成するひつようもありません。



3753817_s

静的メンバ変数を宣言した時に、コンパイルエラーが起きて
undefined reference to `Hogehoge::m_hogeNum'
といった感じに、宣言してたはずのメンバー変数が宣言していないとのエラーが発生しました。

本記事では、このエラーに対する対処方法をまとめていきます。




"undefined reference to"のエラーが出る場合のコード


以下のコードではをコンパイルすると冒頭で説明したように、「undefined reference to `Hogehoge::m_hogeNum'」というエラーメッセージが表示されます。


ソースコード

#include <iostream>

class Hogehoge{
public:
    Hogehoge(int num);
    ~Hogehoge();

private:
    static intm_hogeNum;
};

Hogehoge::Hogehoge(int num){
    m_hogeNum = &num;
}

Hogehoge::~Hogehoge(){}

int main(void){
    int x = 5;
    Hogehogehogehoge = new Hogehoge(5);

    return 0;
}



エラーメッセージ

undefined reference to `Hogehoge::m_hogeNum'
collect2.exe: error: ld returned 1 exit status



対策


調べてみると、staticメンバ変数も関数のように明示的にすることで"undefined reference"エラーを防ぐことができることが分かりました。

先ほど上記で紹介したコードを改造して、コンパイルがとおるようにしてみます。

#include <iostream>

class Hogehoge{
public:
    Hogehoge(int num);
    ~Hogehoge();

private:
    static intm_hogeNum;
};

int* Hogehoge::m_hogeNum = nullptr;

Hogehoge::Hogehoge(int num){
    m_hogeNum = &num;
}

Hogehoge::~Hogehoge(){}

int main(void){
    int x = 5;
    Hogehogehogehoge = new Hogehoge(5);

    return 0;
}

赤字にしている部分が先ほどのコードから追加した部分です。

intHogehoge::m_hogeNum = nullptr;

といった感じに明示的に宣言することで、"undefined reference"エラーを回避することができました。



まとめ


staticメンバ変数は、関数などとどうように明示的に定義しておく必要があることが分かりました。

久しぶりにC++を使ったりするとこういったくだらないミスで詰まってしまうことがありますので気をつけましょう。




3753817_s


以前、以下の記事で作成したSingletonパターンがスレッドセーフではなかったため、本記事ではスレッドセーフなSingletonパターンを実装していきます。





Singletonパターンのクラス構成


今回作成する、スレッドセーフなSingletonパターンのクラス構成は以下のとおりです。

SingletonThreadSafe


Singletonパターンを実装する場合、インスタンスの作成をgetInstanceメソッドで行うということをしがちですが、この場合複数個のスレッドから同時に呼ばれたときに別のインスタンスが作成されるという問題がありました。

そこで今回は新たにcreateメソッドを実装して、ここでインスタンスを作成することでスレッドセーフなSingletonパターンを作成します。

また新しくインスタンスを破棄するdestroyメソッドも追加しました。





C++による実装


サンプルとして複数のスレッドからgetInstanceメソッドを呼ばれても一つしかインスタンスを作成しないということを証明するプログラムを作成します。


Singletonクラス



ヘッダファイル

#ifndef H_SINGLETON
#define H_SINGLETON

class Singleton{
private:
    Singleton();
    ~Singleton();

public:
    static void create();
    static void destroy();
    static Singleton* getInstance();

private:
    static Singletonm_singleton;
};
#endif // H_SINGLETON


ソースファイル

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

SingletonSingleton::m_singleton = nullptr;

Singleton::Singleton()
{
    std::cout << "インスタンスを生成しました" << std::endl;
}

Singleton::~Singleton(){
    std::cout << "インスタンスを削除しました" << std::endl;
}

void Singleton::create(){
    if (!m_singleton){
        m_singleton = new Singleton();
    }
}

void Singleton::destroy(){
    if (m_singleton){
        delete m_singleton;
        m_singleton = nullptr;
    }
}

Singleton* Singleton::getInstance(){
    if (!m_singleton){
        return m_singleton;
    }
    return nullptr;
}



main関数


上記で作成したSingletonクラスを実際に使用してみます。

今回Windows環境で作成したため、スレッドはWin32 APIを使用して作成しています。


ソースファイル

#include <iostream>
#include <windows.h>
#include "Singleton.h"

#define LOOP 10
#define THREADNUM 10

struct stThreadArg{
    unsigned long threadNum;
};

DWORD WINAPI thread(LPVOID *arg){
    //struct stThreadArg* threadArg = (struct stThreadArg*)arg;
    unsigned longthreadArg = (unsigned long*)arg;
    std::cout << "Start thread" << *threadArg << std::endl;
    for(int i=0i<LOOP; ++i){
        Singleton::getInstance();
    }
    std::cout << "End thread" << *threadArg << std::endl;
    return 0;
}


int main(void){
    std::cout << "Start" << std::endl;
    
    // インスタンスを作成
    Singleton::create();
    
    /* スレッドの生成 */
    HANDLE handle[THREADNUM];
    unsigned long threadArg[THREADNUM];
    for (int i=0i<THREADNUM; ++i){
        threadArg[i] = i;
        handle[i] = CreateThread(00, (LPTHREAD_START_ROUTINEthread, &threadArg[i], 0NULL);
    }
    WaitForMultipleObjects(THREADNUMhandle,TRUEINFINITE);

    // インスタンスを削除
    Singleton::destroy();
    
    std::cout << "End" << std::endl;

    return 0;
}


実行結果

Start
インスタンスを生成しました
Start threadStart thread2
End thread2
Start thread9
End thread9
Start thread4
End thread4
Start thread3
End thread3
Start thread7
End thread7
Start thread8
End thread8
0
End thread0
Start thread6
End thread6
Start thread5
End thread5
Start thread1
End thread1
インスタンスを削除しました
End


実行結果からインスタンスの生成がcreateメソッドを呼んだときしか行っていないことが分かりますね。

また、destroyメソッドによってインスタンスの削除もしっかり行われています。



まとめ


Singletonパターンを作成するときは、スレッドセーフではない実装を行うようにしましょう。

もし仮にスレッドセーフではないSingletonパターンを作成したとして、不具合が起きていない場合、それはたまたま不具合が起きなかっただけにすぎません。

できる限り不具合が発生する要因をなくしていきましょう。







3753817_s

本記事では、デザインパターンの名著である結城浩さんの『Javaで学ぶデザインパターン入門』を参考にSingletonパターンをC++で実装していきます。



Singletonパターンとは


プログラムを作成するとき、同じクラスのインスタンスを複数作成することがあります。

しかし、中にはこのクラスのインスタンスは、たった1つしか作らないし、作りたくないというものも存在します。

注意深くプログラムを作成してインスタンスを1つしか作らないという方法もありますが、これはかなり手間のかかる作業です。

プログラマが注意してインスタンスが1個しか生成されないプログラムをつくるのではなく、

  • 指定したクラスのインスタンスが絶対に1個しか存在しないことを保証したい
  • インスタンスが1個しか存在しないことをプログラム上で表現したい

場合には、どうしたらよいでしょうか。

この問題を解決するために、インスタンスが1個しか存在しないことを保証するパターンをSingletonパターンと呼びます。



Singletonパターンの登場人物


Singletonパターンは以下のようなクラス構成になっています。

Singleton


Singleton


Singletonパターンには、Singletonクラスしか登場しません。

Singletonクラスの役割はインスタンスが複数個できないようにstatic変数でインスタンスを持ち、getInstanceメソッドでインスタンスを返します。

getInstanceメソッドは、何度呼ばれても同じインスタンスしか返さないという特徴があります。



C++による実装


サンプルとしてSingletonパターンを使用すると一つしかインスタンスを作成しないということを証明するプログラムを作成します。

クラス構成は以下の通りです。


Singleton


Singletonクラス


外部からnewできないようにコンストラクタとデストラクタがpirvateになっているのが特徴です。


ヘッダファイル

class Singleton{
private:
    Singleton();
    ~Singleton();

public:
    static Singleton* getInstance();

private:
    static Singletonm_singleton;
};


ソースファイル

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

SingletonSingleton::m_singleton = nullptr;

Singleton::Singleton()
{
    std::cout << "インスタンスを生成しました" << std::endl;
}

Singleton::~Singleton(){
}

Singleton* Singleton::getInstance(){
    if (!m_singleton){
        m_singleton = new Singleton();
    }
    return m_singleton;
}



main関数


上記で作成したSingletonクラスを実際に使用してみます。


ソースファイル

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

SingletonSingleton::m_singleton = nullptr;

Singleton::Singleton()
{
    std::cout << "インスタンスを生成しました" << std::endl;
}

Singleton::~Singleton(){
}

Singleton* Singleton::getInstance(){
    if (!m_singleton){
        m_singleton = new Singleton();
    }
    return m_singleton;
}


実行結果

Start
インスタンスを生成しました
obj1とobj2は同じインスタンスです。
End

実行結果からコンストラクタが一度しか呼ばれていないこと、obj1とobj2が同じインスタンスであることが分かりますね。



まとめ


今回は、Singletonパターンを実装していきました。

一つしかインスタンスを作成したいときに便利なクラスです。

ちなみに上記の実装はスレッドセーフではないため、最近のSingletonパターンではインスタンスを作成するCreateメソッドやインスタンスを削除するDestoryメソッドなどを作成してスレッドセーフを保証したりもしています。

スレッドセーフなSingletonパターンを以下の記事で作ってみました。




Singletonパターンは、すごく便利ですがglobal関数的な使い方もできるため試験が行いにくくなるという欠点もあります。

使いどころを考えて使用していきましょう。






↑このページのトップヘ