2012年12月30日日曜日

共有ライブラリを複数のプロセスから呼び出した場合のグローバル変数の振る舞い

共有ライブラリはロードしたプログラムデータはプロセス間で共有するが、インスタンスはプロセス毎に持つことを確認してみました。
実験はプロセスAとプロセスBが共有のライブラリTest.dllを参照して、グローバル変数を変更します。

ここはグローバル変数をインスタンス化して、このインスタンスのGetterとSetterをインターフェイスにして公開します。

プロセスAとBが参照するTest.dllの中身
///
/// Test.h
///
#ifndef _INC_TEST_H_
#define _INC_TEST_H_

#ifdef _TEST
 #define TEST_API __declspec(dllexport)
#else
 #define TEST_API __declspec(dllimport)
#endif

namespace Test {

TEST_API void SetValue(int val);
TEST_API int GetValue();

}

#endif

///
/// Test.cpp
///
#include "Test.h"

namespace Test {

///
/// プロセス間のメモリ共有の確認
/// プロセス毎に初期化が行われ、プロセス毎にインスタンスが生成される
///
static int _val = 0;

TEST_API int GetValue()
{
 return _val;
}

TEST_API void SetValue(int val)
{
 _val = val;
}

}

暗黙的リンクでTest.dllをロードします。エントリポイントのはじめにTest空間にあるグローバル変数の値を2に変更し、プロセスBを実行します。そのあとにグローバル変数を確認します。表示される値は2か100のどちらかになると思います。2であれば、グローバル変数のインスタンスが共有されていることになります。100であれば、プロセス毎にインスタンスが生成されていることになります。

プロセスAのエントリポイント
///
/// AMain.cpp
///
#include "Test.h"
#include <iostream>
#include <stdlib.h>

int _tmain(int argc, _TCHAR* argv[])
{
 // 初期値の表示
 std::cout << "A.exe " << Test::GetValue() << std::endl;

 // 値を2に更新
 Test::SetValue(2);

 // 別プロセスを実行
 // 別プロセスで値を100に更新している
 system("B.exe");

 // 値の表示
 std::cout << "A.exe " << Test::GetValue() << std::endl;

 return 0;
}

プロセスBのエントリポイント
///
/// BMain.cpp
///
#include "Test.h"
#include <iostream>

int _tmain(int argc, _TCHAR* argv[])
{
 // 値の表示
 std::cout << "B.exe " << Test::GetValue() << std::endl;

 // 値を100に更新
 Test::SetValue(100);

 return 0;
}

出力結果
A.exe 0
B.exe 0
A.exe 2

結果からプロセス毎にインスタンスが生成されていることが分かりますね。

2012年12月25日火曜日

ダイナミックライブラリのエントリポイント

ダイナミックライブラリのエントリポイントはWindows環境ならDllMainで良いけどLinux環境でどのように実現すればよいのか困っていたけどグローバル変数とコンストラクタとデストラクタを使うと似たような振る舞いを実現できます。以下はその例です。

共通ライブラリがロードされるとグローバル変数がインスタンス化されて、DLManagerのコンストラクタが呼び出されます。グローバル変数のインスタンス化はエントリポイントの開始よりも先に呼び出されるので、初期化の位置としては悪くないように思います。
アンロードの場合もエントリポイントを抜けたあとにデストラクタが呼び出されます。

共通ライブラリのロードアンロードクラス
///
/// DLManager.cpp
///
#include <iostream>

class DLManager
{
public:
 DLManager()
 {
  std::cout << "Test library start!!" << std::endl;
 }

 ~DLManager()
 {
  std::cout << "Test library end!!" << std::endl;
 }
};

///
/// グローバル空間にインスタンス化する
///
static DLManager _dlManager;

DLL内に定義されている例題クラス
///
/// Someone.h
///
#ifndef _INC_HEADER_H_
#define _INC_HEADER_H_

#ifdef _TEST
#define TEST_API __declspec(dllexport)
#else
#define TEST_API __declspec(dllimport)
#endif

class TEST_API Someone
{
public:
 Someone();
 ~Someone();
 void Do();
};

#endif

///
/// Someone.cpp
///
#include <iostream>
#include "Someone.h"

Someone::Someone()
{
}

Someone::~Someone()
{
}

void Someone::Do()
{
 std::cout << "something" << std::endl;
}

エントリポイント
///
/// Main.cpp
///
#include <iostream>
#include "Someone.h"

int _tmain(int argc, _TCHAR* argv[])
{
 Someone one;
 one.Do();
 return 0;
}

出力結果
Test library start!!
something
Test library end!!

2012年12月24日月曜日

DLL内のテンプレートクラスを呼び出す方法

別DLLで定義したテンプレートクラスをmain関数で呼び出すときにリンカーに怒られました。これを躱すには明示的にテンプレートクラスをインスタンス化するという方法があるみたいです。以下はその例です。

LoggerSingletonクラスはSingletonテンプレートクラスをバインドしています。Singletonクラスは静的メンバ関数を経由してインスタンスにアクセスする機能を提供します。Log.cppの末尾で明示的テンプレートとして定義するとDLL外からも呼び出すことができます。リンクエラーの原因は単純に実装部が公開されていなかったからだけですね。つまり逆に言えば、この方法であれば実装部を隠ぺいすることができたことになり、より疎結合になったと言えます。

エントリポイント
///
/// main.cpp
///
#include <iostream>
#include "Logger.h"

int _tmain(int argc, _TCHAR* argv[])
{
 // インスタンスを取得
 Test::Logger* logger = Test::LoggerSingleton::Instance();

 // 記録
 logger->Record("Test 1");
 logger->Record("Test 2");
 logger->Record("Test 2");

 // 記録を出力
 std::cout << logger->Message();

 return 0;
}

シングルトンパターンを提供するテンプレートクラス
///
/// Singleton.h
///
#ifndef _INC_SINGLETON_H_
#define _INC_SINGLETON_H_

#ifdef _TEST
#define TEST_API __declspec(dllexport)
#else
#define TEST_API __declspec(dllimport)
#endif

namespace Test {

template <typename T>
class TEST_API Singleton
{
private:
 T* _d;

public:
 ~Singleton();
 static T* Instance();

private:
 // インスタンス化をガード
 Singleton();
 Singleton(const Singleton<T>& other);
 Singleton<T>& operator=(const Singleton<T>& other);
};

}
#endif

テンプレートクラスの実装部
///
/// SingletonImp.h
///
#ifndef _INC_SINGLETON_IMP_H_
#define _INC_SINGLETON_IMP_H_

#include "Singleton.h"

namespace Test {

template <typename T>
Singleton<T>::~Singleton()
{
 delete this->_d;
}

template <typename T>
T* Singleton<T>::Instance()
{
 static Singleton<T> singleton;
 return singleton._d;
}

template <typename T>
Singleton<T>::Singleton() : _d(new T)
{
}

}
#endif

テンプレートクラスの明示的インスタンス化
///
/// Logger.h
///
#ifndef _INC_LOGGER_H_
#define _INC_LOGGER_H_

#include <string>
#include <vector>
#include "Singleton.h"

namespace Test {

class TEST_API Logger
{
private:
 typedef std::vector<std::string*> Table;

 Table _table;

public:
 Logger();
 ~Logger();
 void Record(const std::string& msg);
 std::string Message();
};

typedef Test::Singleton<test::logger> LoggerSingleton;

}
#endif

///
/// Log.cpp
///
#include "SingletonImp.h"
#include "Logger.h"

namespace Test {

Logger::Logger()
{
}

Logger::~Logger()
{
 for(Table::iterator it = this->_table.begin(); it != this->_table.end(); ++it)
 {
  // インスタンスを解放
  delete *it;
 }
}

void Logger::Record(const std::string& msg)
{
 // インスタンスをコピーして格納
 this->_table.push_back(new std::string(msg));
}

std::string Logger::Message()
{
 std::string msg;

 // メッセージの作成
 for(Table::iterator it = this->_table.begin(); it != this->_table.end(); ++it)
 {
  msg += *(*it);
  msg += "\n";
 }

 return msg;
}

// 明示的テンプレートのインスタンス化
template Test::Singleton<test::logger> LoggerSingleton;
}

2012年12月9日日曜日

匿名型のリスト

匿名型は便利なのだけどListで扱う場合に困ったのでメモ。

var rows = Enumerable.Range(0, 0).Select(x => new { Name = String.Empty, Value = Double.NaN }).ToList();
rows.Add(new { Name = "ABC", Value = 1.23 });

DBからレコードを取り出すときなんかに便利。