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"なんてインクルードできないです。