Mesoscopic Programming

タコさんプログラミング専門

DLLとストリームのサンプルプログラム

DLLにしてみるテスト

いつもよく使う関数やマクロを共通ライブラリにしようと思って、
どうせならDLLの練習を兼ねてDLLにしてみました。

家で使っているのはVC++ 2010 Expressなんですが、
こいつは2012 Expressなどと違って空のソリューションが作れない仕様になっているのね。
だから最初に何らかのプロジェクトも一緒に作ってやる必要があるんですよね。

ところが新規でプロジェクトを作るときってのは、
最初にプロジェクト名を聞いてきて、デフォルトではソリューション名も同じにされちゃうんですよね。
それでわざわざソリューション名のところで別の名前を入力する必要があるわけなんです。

で、CommonというDLLプロジェクトを最初に作って、
次にSampleというアプリケーションプロジェクトを追加したわけなんですが、
何もない空っぽのDLLプロジェクトをビルドしようとすると、Common.libが無いとか言って怒られるんですよね。
本当は無くても動くんだけど、しょうがないのでDllMain()を追加しました。

Common.h

//----------------------------------------------------------------------------
/// @file    Common.h
/// @brief   共通ライブラリモジュールヘッダ
/// @details すべてに共通のライブラリです。
//----------------------------------------------------------------------------

#pragma once

#include <windows.h>

//----------------------------------------------------------------------------
// マクロ定義
//----------------------------------------------------------------------------

#ifdef COMMON_EXPORTS
#define COMMON_API __declspec(dllexport) ///< DLL宣言マクロ
#else  // COMMON_EXPORTS
#define COMMON_API __declspec(dllimport) ///< DLL宣言マクロ
#endif // COMMON_EXPORTS

#ifndef __func__
#define __func__ __FUNCTION__ ///< 関数名事前定義マクロ
#endif // __func__

/// @brief   トークン文字列取得マクロ
/// @details トークンを文字列として取得します。
#define STRING( token ) #token

/// @brief   マクロトークン文字列取得マクロ
/// @details マクロを展開して得られたトークンを文字列として取得します。
#define MACRO_STRING( macro ) STRING( macro )

//----------------------------------------------------------------------------
// グローバル関数
//----------------------------------------------------------------------------

/// @brief   DLLメイン関数
/// @param   [in] hModule            モジュールハンドル
/// @param   [in] ul_reason_for_call 関数を呼び出す理由
/// @param   [in] lpReserved         予約済み
/// @retval  TRUE  成功
/// @retval  FALSE 失敗
/// @details DLLモジュールのエントリーポイントです。
COMMON_API BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved );

//----------------------------------------------------------------------------
/// @brief   共通ライブラリ名前空間
/// @details 共通ライブラリ名前空間です。
//----------------------------------------------------------------------------
namespace Common {}

Common.cpp

//----------------------------------------------------------------------------
/// @file  Common.cpp
/// @brief 共通ライブラリモジュール
//----------------------------------------------------------------------------

#include "Common.h"

// DLLメイン関数
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
    switch ( ul_reason_for_call )
    {
    case DLL_PROCESS_ATTACH :

        // ここでDLLの初期化処理を行います。

        break;

    case DLL_THREAD_ATTACH :
    case DLL_THREAD_DETACH :

        break;

    case DLL_PROCESS_DETACH :

        // ここでDLLの終了処理を行います。

        break;
    }

    return TRUE;
}

サンプルプロジェクトのほうは、デバッグコンソールにストリームでログを出力するだけのものです。

main.h

//----------------------------------------------------------------------------
/// @file    main.h
/// @brief   メインモジュールヘッダ
/// @details メインモジュールです。
//----------------------------------------------------------------------------

#pragma once

//----------------------------------------------------------------------------
// グローバル関数
//----------------------------------------------------------------------------

/// @brief   メイン関数
/// @return  なし
/// @details メイン関数です。
extern void main();

/// @brief   テスト関数
/// @param   [in] param 引数
/// @return  戻り値
/// @details テスト用の関数です。
extern int func( int param );

main.cpp

//----------------------------------------------------------------------------
/// @file    main.cpp
/// @brief   メインモジュール
/// @details メインモジュールです。
//----------------------------------------------------------------------------
/// @mainpage コンソールサンプルプログラム
///
/// @section summary 概要
///          コンソールアプリケーションのサンプルプログラムです。
///
/// @section history 履歴
/// -        2015/04/04 開発を開始しました。
/// -        2015/04/05 まだ開発中です。
///
/// @version 1.0.0
/// @date    2015/04/05
/// @author  Copyright (C) 2015 hidakas1961 All rights reserved.
//----------------------------------------------------------------------------

#include "main.h"
#include <TDebugOut.h>
#include <TLogOut.h>
#include <conio.h>

//----------------------------------------------------------------------------
// 名前空間使用宣言
//----------------------------------------------------------------------------

using namespace std;
using namespace Common;

//----------------------------------------------------------------------------
// ローカル変数
//----------------------------------------------------------------------------

static TDebugOut debugout;           ///< デバッグ出力ストリーム
static TLogOut   logout( debugout ); ///< ログ出力ストリーム

//----------------------------------------------------------------------------
// グローバル関数
//----------------------------------------------------------------------------

// メイン関数
void main()
{
    // 共通DLLはプロジェクト設定のほうでロード済みなので(やっても良いみたいですが)不要です。
    // LoadLibraryA( "Common" );

    logout << "サンプルプログラムです。" << endl;
    logout << __FILE__ << ":" << __LINE__ << endl << endl;

    logout.Begin( __func__ );

    int result = func( 123 );

    logout.DataValue( "func()", result );
    logout.End( __func__ );

    cout << endl << "何か押してください。" << endl;

    _getch();
}

// テスト関数
int func( int param )
{
    logout.Begin( __func__ );

    int result = -param;

    logout.DataValue( "result", result );

    logout.End( __func__ );

    return result;
}

うちのパソコンはCPUがセレロンでOSがビスタという超しょぼいノートパソコンなので、
C++の標準ライブラリを使おうとすると、信じられないくらい遅くなって精神的に良くないので、
今まで避けて通ってきたのですが、仕事でC++を使うことになって止むなく標準ライブラリに着手しました。

今まで画面出力関係はすべてprintf()でまかなってきたのですが、やはりcoutとかいうストリーム使うと、
いちいちcharとTCHARで関数を使い分ける必要もなくて、超便利なことに気が付いてしまいました。
そこでまずはログ出力用の出力ストリームクラスを作ってみました。

TLogOut.h

//----------------------------------------------------------------------------
/// @file    TLogOut.h
/// @brief   ログ出力クラスヘッダ
/// @details ログ出力クラスです。
//----------------------------------------------------------------------------

#pragma once

#include "Common.h"
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <stdarg.h>

//----------------------------------------------------------------------------
// 共通ライブラリ名前空間
//----------------------------------------------------------------------------
namespace Common
{
    //------------------------------------------------------------------------
    /// @brief   ログ出力クラス
    /// @details ログ出力クラスです。
    //------------------------------------------------------------------------
    class TLogOut
    {
        //--------------------------------------------------------------------
        // 定数
        //--------------------------------------------------------------------

    private :

        static const int m_defIndent = 2; ///< デフォルトインデント数

        //--------------------------------------------------------------------
        // 動的変数
        //--------------------------------------------------------------------

    private :

        std::ofstream * m_ofs;    ///< ファイル出力ストリームポインタ
        std::ostream  * m_out;    ///< 出力ストリームポインタ
        int             m_indent; ///< インデント数
        int             m_block;  ///< ブロック数
        const char    * m_header; ///< ヘッダー文字列

        //--------------------------------------------------------------------
        // 構築子と解体子
        //--------------------------------------------------------------------

    public :

        /// @brief デフォルト構築子
        /// @param [in] stream ストリーム参照アドレス
        /// @param [in] indent インデント数
        /// @param [in] header ヘッダー文字列
        inline explicit TLogOut( std::ostream & stream = std::clog, int indent = m_defIndent, const char * header = 0 )
            : m_ofs( 0 ), m_out( & stream ), m_indent( indent ), m_block( 0 ), m_header( header ) {}

        /// @brief ログファイル名指定構築子
        /// @param [in] filename ファイル名
        /// @param [in] indent   インデントタブ空白数
        /// @param [in] header   ヘッダー文字列
        inline explicit TLogOut( const char * filename, int indent = m_defIndent, const char * header = 0 )
            : m_ofs( new std::ofstream( filename ) ), m_out( m_ofs ), m_indent( indent ), m_block( 0 ), m_header( header ) {}

        /// @brief 解体子
        inline virtual ~TLogOut() { if ( m_ofs ) m_ofs->close(); }

        //--------------------------------------------------------------------
        // 演算子オーバーロード関数
        //--------------------------------------------------------------------

    public :

        /// @brief   左シフト演算子オーバーロード関数
        /// @param   [in] param 引数
        /// @return  ログ出力ストリーム参照アドレス
        /// @details 引数をログストリームに出力します。
        template< class T > std::ostream & operator <<( T & param ) const { return * m_out << param; }

        //--------------------------------------------------------------------
        // 動的テンプレート関数
        //--------------------------------------------------------------------

    public :

        /// @brief   データ出力関数
        /// @param   [in] name  データ名
        /// @param   [in] value 値
        /// @return  ログ出力ストリーム参照アドレス
        /// @details データ名と値を出力します。
        template< class T > std::ostream & DataValue( const char * name, T value ) const
        {
            // ヘッダーを出力します。
            Header( m_block );

            // データ名と値を出力します。
            return * m_out << name << " = " << value << std::endl;
        }

        //--------------------------------------------------------------------
        // 動的関数
        //--------------------------------------------------------------------

    public :

        /// @brief   インデント数設定関数
        /// @param   [in] indent インデント数
        /// @return  ログ出力ストリーム参照アドレス
        /// @details インデント数を設定します。
        inline virtual std::ostream & Indent( int indent ) { m_indent = indent; return * m_out; }

        /// @brief   ヘッダー文字列設定関数
        /// @param   [in] header ヘッダー文字列
        /// @return  ログ出力ストリーム参照アドレス
        /// @details ヘッダー文字列を設定します。
        inline virtual std::ostream & Header( const char * header ) { m_header = header; return * m_out; }

        /// @brief   ヘッダー出力関数
        /// @param   [in] block ブロック数
        /// @return  ログ出力ストリーム参照アドレス
        /// @details ログヘッダー文字列を出力します。
        inline virtual std::ostream & Header( int block ) const
        {
            // ヘッダー文字列を出力します。
            if ( m_header ) * m_out << m_header;

            // ブロック数を巡回します。
            for ( int n = 0; n < block; n++ )
            {
                // インデント数を巡回します。
                for ( int m = 0; m < m_indent; m++ )
                {
                    // 空白を出力します。
                    * m_out << " ";
                }
            }

            return * m_out;
        }

        /// @brief   ブロック開始行出力関数
        /// @param   [in] name ブロック名
        /// @return  ログ出力ストリーム参照アドレス
        /// @details ブロック開始行を出力します。
        inline virtual std::ostream & Begin( const char * name )
        {
            // ヘッダーを出力してブロックカウンタをインクリメントします。
            Header( m_block++ );

            // ブロック開始文字とブロック名を出力します。
            return * m_out << "{ " << name << std::endl;
        }

        /// @brief   ブロック終了行出力関数
        /// @param   [in] name ブロック名
        /// @return  ログ出力ストリーム参照アドレス
        /// @details ブロック終了行を出力します。
        inline virtual std::ostream & End( const char * name )
        {
            // ブロックカウンタをデクリメントしてヘッダーを出力します。
            Header( --m_block );

            // ブロック終了文字とブロック名を出力します。
            return * m_out << "} " << name << std::endl;
        }

        /// @brief   書式文字列出力関数
        /// @param   [in] format 書式文字列
        /// @param   [in] ...    可変引数
        /// @return  ログ出力ストリーム参照アドレス
        /// @details 書式付き文字列を出力します。
        inline virtual std::ostream & Printf( const char * format, ... ) const
        {
            // ヘッダーを出力します。
            Header( m_block );

            // 展開後の文字列サイズを取得します。
            va_list args;

            va_start( args, format );

            size_t size = _vscprintf( format, args ) + 1;

            // 書式付き文字列をバッファーに展開します。
            char * buffer = new char[ size ];

            vsprintf_s( buffer, size, format, args );

            // 展開された文字列を出力します。
            * m_out << buffer << std::endl;

            // 文字列バッファーを削除します。
            delete[] buffer;

            va_end( args );

            return * m_out;
        }
    };
}

これだけだと標準ストリームとファイルストリームぐらいにしか出せないので、
どうせならいつもデバッグに使っているDebugOutputString()に出せるようにしたいと思いました。

そこでstd::ostreamクラスの派生クラスを作りました。

TDebogOut.h

//----------------------------------------------------------------------------
/// @file    TDebugOut.h
/// @brief   デバッグ出力クラスヘッダ
/// @details デバッグ出力クラスです。
//----------------------------------------------------------------------------

#pragma once

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

//----------------------------------------------------------------------------
// 共通ライブラリ名前空間
//----------------------------------------------------------------------------
namespace Common
{
    //------------------------------------------------------------------------
    /// @brief   デバッグ出力クラス
    /// @details デバッグ出力クラスです。
    //------------------------------------------------------------------------
    class TDebugOut : public std::ostream
    {
        //--------------------------------------------------------------------
        /// @brief   ストリームバッファクラス
        /// @details ストリームバッファクラスです。
        //--------------------------------------------------------------------
        class TStreamBuf : public std::streambuf
        {
            //----------------------------------------------------------------
            // 定数
            //----------------------------------------------------------------

        private :

            static const size_t m_blocksize = 256; ///< バッファーブロックサイズ

            //----------------------------------------------------------------
            // 動的変数
            //----------------------------------------------------------------

        private :

            char * m_buffer; ///< 文字列バッファー
            size_t m_size;   ///< バッファーサイズ
            size_t m_len;    ///< 文字列長

            //----------------------------------------------------------------
            // 構築子と解体子
            //----------------------------------------------------------------

        public :

            /// @brief デフォルト構築子
            inline explicit TStreamBuf() : std::streambuf(), m_buffer( 0 ), m_size( 0 ), m_len( 0 ) {}

            /// @brief 解体子
            inline virtual ~TStreamBuf() { delete[] m_buffer; }

            //----------------------------------------------------------------
            // 動的関数
            //----------------------------------------------------------------

        public :

            /// @brief   オーバーフロー関数
            /// @param   [in] ch 文字コード
            /// @return  文字コード
            /// @details 1文字を書き込みます。
            inline virtual int_type overflow( int_type ch = EOF )
            {
                // EOF でないか調べます。
                if ( ch != EOF )
                {
                    // バッファーサイズを調べます。
                    while ( m_size < m_len + 2 )
                    {
                        // バッファーサイズを更新します。
                        if ( m_size += m_blocksize >= m_len + 2 )
                        {
                            // 新規文字列バッファーを確保します。
                            char * buffer = new char[ m_size += m_blocksize ];

                            // 確保に失敗したか調べます。
                            if ( ! buffer ) throw( 0 );

                            // 現在のバッファー内容をコピーして削除します。
                            if ( m_buffer )
                            {
                                memcpy( buffer, m_buffer, m_len + 1 );

                                delete[] m_buffer;
                            }

                            // バッファポインタを更新します。
                            m_buffer = buffer;

                            break;
                        }
                    }

                    // バッファーに書き込みます。
                    m_buffer[ m_len++ ] = ch;
                }

                return ch;
            }

            /// @brief   同期関数
            /// @return  終了コード
            /// @details バッファー文字列を出力します。
            inline virtual int sync()
            {
                // バッファー文字列をデバッグコンソールに出力します。
                if ( m_buffer )
                {
                    m_buffer[ m_len ] = 0;

                    OutputDebugStringA( m_buffer );
                }

                // バッファーを削除します。
                delete[] m_buffer;

                m_buffer = 0;
                m_size   = 0;
                m_len    = 0;

                return 0;
            }
        };

        //--------------------------------------------------------------------
        // 動的変数
        //--------------------------------------------------------------------

    private :

        std::streambuf * m_streambuf; ///< ストリームバッファー

        //--------------------------------------------------------------------
        // 構築子と解体子
        //--------------------------------------------------------------------

    public :

        /// @brief デフォルト構築子
        inline explicit TDebugOut() : std::ostream( m_streambuf = new TStreamBuf ) {}

        /// @brief 解体子
        inline virtual ~TDebugOut() { delete m_streambuf; }
    };
}

はずかしい話ですが、DebugOutputStringA()の方でも漢字が出せるということを今まで知りませんでした。
もう恥ずかしくって"tchar.h"なんてインクルードできないです。

サンプルドキュメント