便利なログは贅沢品ではなく、デバッグツール、またはhファイルを使用してdllを接続する方法です

画像



ProLog



アプリケーションの開発を始めたプログラマーが、ログの問題を無視することはありません。 簡単な質問のように思えますが、既存のオプションを並べ替えると、誰にとっても不便なことがわかります:ランタイムでログをシャットダウンすることはなく(コンパイル中のみ)、ログをファイル、場合によっては通信ポートなどにリダイレクトする必要がありますなど など 本格的なバージョンを作成するのに十分な時間ではなく、急いで別の実装を作成するのは手間がかかりません。 そして、彼らが言うように、ログは開発ツールであるため、ブーツのない靴屋がさらに悪いことになります...しかし、この問題にゆっくりとアプローチしたらどうなりますか? 開発者として、私はこのようなデバッグツールを見たいです:



  1. 簡単で使いやすい-デフォルトでプロジェクトに1つのhファイルを含めることができ、古いアプリケーションでも新しいアプリケーションでも、すべてが機能します。
  2. 拡張可能-1つのhファイルをプロジェクトに追加すると、アプリケーション自体に影響を与えることなく、必要なだけ機能を増やすことができます(結局、多くの場合、アプリケーションは既にクライアントで動作しているため、触れることはお勧めできません)。
  3. 完全に構成可能-開発者は、ユーザーとは対照的に、開発ツールを完全に制御する必要があります。




拡張性



拡張性の基本原則の1つは、既存のシステム機能への影響を最小限に抑えながら、変更の可能性を最大限にすることです。 まず、これは、ログを拡張可能にしたい場合、それからシステムを作成する必要があることを意味します。 アプリケーションから分離します。 Windowsのこのようなメカニズムはダイナミックリンクライブラリです。ライブラリが必要なインターフェイスを提供する場合、アプリケーションはどのライブラリを使用するかを気にしません。 つまり すべてのライブラリー要件はインターフェース要件に削減されます。 インターフェイスの拡張性は、C ++インターフェイスメカニズムを使用して実現できます(これに基づいてコンポーネントオブジェクトモデルが構築されます)。 これを行うには、dllで2つの関数のみを定義する必要があります。



int GetLogInterfaceVersion(); ILog* CreateLogObject();
      
      





ILogは必須のインターフェイスであり、次のように定義されます。



  interface ILog { virtual void Log( unsigned int messageId, char *fmt, ... ) = 0; };
      
      





場合には、インターフェイスに新しい関数を追加する必要があります。



  interface ILog { virtual void Log( unsigned int messageId, char *fmt, ... ) = 0; virtual void RedirectLog( void (*log) (char *)) = 0; };
      
      





古いアプリケーションはライブラリの新しいバージョンと互換性がありますが、一貫してインターフェイスに追加し、インターフェイスのバージョンを増やすだけです。 したがって、ロガーのすべての機能はILog抽象インターフェイスの背後に隠されており、Log関数が行うことは、データをファイルまたはフラッシュメモリにアプリケーションに書き込むことです。



第二に、ログを拡張可能にしたい場合は、新しい構造を追加してもライブラリ内の既存の機能が変更されないように、内部構造を整理する必要があります。 これを行うには、可変機能と不変機能を分離する必要があります。 C ++でのログの場合、これは不変部分にクラステンプレートを適用することで適切に行うことができることが示されています。 さらに、戦略はロギング戦略(LogPolicy)だけでなく、構成(LogConfigPolicy)でもあります。これは、簡単にパラメーター化することもできるためです。



  template < class LogConfigPolicy, class LogPolicy > class TLog : public LogConfigPolicy, public LogPolicy, public ILog { TLog() : LogConfigPolicy(), LogPolicy( this ) //             { defaultFilterLevel = LOG_DEBUG; if( GetString("common","filterLevel",out,sizeof(out),"debug") ) // GetString –   ,      , ,       … };
      
      





したがって、ログを出力する場所だけでなく、レジストリまたはファイルから構成を読み取る場所も簡単に変更できます。 CreateLogObject関数は次のようになります。



  ILog* CreateLogObject() { try { #if defined(LOG_REG_CONFIG_POLICY) && defined(LOG_DEBUG_POLICY) return new TLog< LogRegConfigPolicy, LogDebugPolicy >(); #elif defined(LOG_REG_CONFIG_POLICY) && defined(LOG_FILE_POLICY) return new TLog< LogFileConfigPolicy, LogFilePolicy >(); #elif defined(LOG_FILE_CONFIG_POLICY) && defined(LOG_DEBUG_POLICY) return new TLog< LogRegConfigPolicy, LogDebugPolicy >(); #elif defined(LOG_FILE_CONFIG_POLICY) && defined(LOG_FILE_POLICY) return new TLog< LogFileConfigPolicy, LogFilePolicy >(); #else #error Log policies weren't defined #endif } catch(...) { return NULL; } }
      
      





これで、プリプロセッサ定義を組み合わせて、これらまたはこれらのプロパティを使用してlog.dllロガーライブラリを取得できます。



使いやすさ



dllをアプリケーションにアップロードするのはそれほど難しい作業ではありませんが、すべてのプロジェクトでこれを行うと面倒になる可能性があり、最も重要なことは、既存のアプリケーションでは、ログを使用するときに誰かがライブラリを使用する可能性が低いことです...これを自動的に行うことは可能ですか? 明らかに、言語のいくつかの手段が必要です。 Singletonパターンは、ログの実装を最初に要求するものです(この場合、dllはSingletonコンストラクターにロードされます)。 グローバルオブジェクトもログにアクセスでき、作成の順序は定義されていないため、このパターンを使用する必要性は、利便性だけでなく、必要性によっても決まります。 シングルトンを戦略を備えたクラステンプレートとして実装しようとすると、興味深い事実に至りました-静的クラス変数のインスタンス化はhファイルで行われます! アプリケーション側のロガー全体は、cppまたはlibを追加せずに、たった1つのhファイルに実装できることがわかります。



  template <class CreatePolicy, class MainInterface, class NamedMutexObject> class TSingleton: public CreatePolicy { private: static TSingleton *instance; TSingleton():CreatePolicy(){} TSingleton( const TSingleton& ){} TSingleton& operator=( TSingleton& ){} virtual ~TSingleton(){} public: static MainInterface* GetSingletonObject() { if(!instance) { NamedMutexObject mutex( CreatePolicy::GetMutexName() ); if(!instance) instance = new TSingleton(); } return instance->mainInterface; } static void ReleaseSingletonObject() { if(instance) { NamedMutexObject mutex( CreatePolicy::GetMutexName() ); if(instance) { delete instance; instance = NULL; } } } }; template <class CreatePolicy, class MainInterface, class NamedMutexObject> TSingleton< CreatePolicy, MainInterface, NamedMutexObject > * TSingleton< CreatePolicy, MainInterface, NamedMutexObject >::instance = 0;
      
      





CreatePolicyは、この場合、log.dllライブラリをロードしてロガーオブジェクトを作成し、そのインターフェイスをmainInterfaceに保存する戦略です。 MainInterfaceインターフェースはILogで、NamedMutexObjectはインスタンスオブジェクトの作成に使用される同期オブジェクトです。 エラーロギングマクロは次のようになります。



  #define log_err(fmt,...) TSingleton<LogFromDllPolicy, ILog, CNamedMutexObject>::GetSingletonObject()->Log( LOG_ERROR, fmt, ##__VA_ARGS__)
      
      





したがって、このロガーを使用するexample.cppアプリケーションは次のようになります。



  #include "log.h" int main(int argc, char* argv[]) { log_inf("Some info...\n"); return 0; }
      
      





ビルドコマンドは次のようになります。



cl.exe example.cpp



構成



コンパイル機能であろうとランタイムであろうと、開発者にすべてのレベルで構成オプションを提供するという原則により、構成機能の完全性が保証されます。



コンパイラー設定は、ライブラリーまたはアプリケーションに特定のコードが含まれるかどうかを決定します。 示されているように、log.dllロガーライブラリの場合(CreateLogObject関数の実装を参照)、ロガーテンプレートクラスで使用される戦略が設定によって決まります。 したがって、ロガーに必要な戦略を作成し、コンパイラ設定を使用してそれらを組み合わせると、既存のコードを変更せずにロガーライブラリに必要な機能を実装できます。



アプリケーションの場合、ログが機能するようにプログラマがアプリケーションに設定を追加する必要がないように、コンパイラ設定が定義されます。





実行時レベルでは、設定は戦略によって完全に決定されます。つまり、プログラマーが設定を実装します。 たとえば、ファイルからロガーを構成するための戦略は、iniファイルにあるメッセージフィルター設定を実装します。



[共通]

filterLevel = info



プログラマーにすべてのデバッグオプションが与えられたので、それらがない場合は簡単に追加できます。アプリケーションにhファイルを1つ追加するだけで、アプリケーション自体を変更せずに後で簡単にログを変更できるため、アプリケーション自体の開発を開始できます。



All Articles