構造体のデータメンバの情報を関数に渡す方法

[戻る]
Blue 2012/12/09(日) 16:40:28
複数のプロセスから、あるグローバルな構造体にアクセスできるように、
ファイルマッピングオブジェクトを作成して、そこに構造体のデータを格納することを考
えています。

ただ、この構造体の任意のデータメンバへのアクセス(Get、Set)を実行する関数をどの
ように作れば良いのか分かりません。

構造体は例えば以下のようなものだとします。
実際には100個くらいのメンバがあります。

struct Foo {
  int      m_nVal;
  double   m_dVal;
  wstring  m_strVal;

  (省略・・・)
};

これに対して、以下のようなアクセス用の関数テンプレートを作成します。

template <typename T>
T GetShareData([Fooのデータメンバの情報]);

実際に呼び出す時には、以下のようにデータメンバの型名を明示的に指定するつもりです。

GetShareData<[Foo.m_dValの型情報]>(・・・);

この時、[Foo.m_dValの型情報]をどうやってテンプレート引数として渡せばいいのか分か
りません。上の例では「double」を渡したいです。

double dTest = GetShareData<typeid(Foo.m_dVal)>(・・・);

こんな風にtypeidを使ってもコンパイルエラーになります。
もし、無理なら、

double dTest = GetShareData<double>(・・・);

のように直接指定するつもりですが、何かよい方法がありそうな気もします。

さらに、GetShareData()の中では、以下のような処理を行ってデータメンバのオフセット
値を使用するつもりです。

lpTop = (Foo *) MapViewOfFile(hPageMap, FILE_MAP_READ, 0L, 0L, 0L);
T tRet = * (T *) (lpTop + FIELD_OFFSET(Foo, m_dVal2));
UnmapViewOfFile(lpTop);
CloseHandle(hPageMap);

return tRet;

このFIELD_OFFSETマクロの第2引数に「m_dVal2」のような値を指定する必要があります
が、これをGetShareData()の引数としてどのように渡せばいいのかわかりません。
このような情報を関数のパラメータとして渡せるのでしょうか?

何かご存知の方がいましたら、アドバイスをお願いします。

επιστημη [E-Mail] [HomePage] 2012/12/09(日) 17:26:01
...たとえばこんなカンジかしら

#include <cstddef>
#include <cstring>
#include <iostream>
using namespace std;

struct foo {
  char cval;
  int ival;
  long lval;
  float fval;
  double dval;
};

char global[100];

template<typename T>
void set_global(char* area, size_t offset, const T& val) {
  memcpy(area+offset, &val, sizeof(T));
}

template<typename T>
void get_global(const char* area, size_t offset, T& val) {
  memcpy(&val, area+offset, sizeof(T));
}

#define GET_GLOBAL(area,STRUCT,MEMBER,val) get_global(area,offsetof
(STRUCT,MEMBER),val)
#define SET_GLOBAL(area,STRUCT,MEMBER,val) set_global(area,offsetof
(STRUCT,MEMBER),val)

int main() {
  foo snd;
  foo rcv;
  snd.cval = 'A';
  snd.ival = 123;
  SET_GLOBAL(global, foo, cval, snd.cval);
  SET_GLOBAL(global, foo, ival, snd.ival);
  GET_GLOBAL(global, foo, cval, rcv.cval);
  GET_GLOBAL(global, foo, ival, rcv.ival);
  cout << rcv.cval << rcv.ival << endl;
}

Blue 2012/12/10(月) 09:04:51
[[解決]]
επιστημη さん、
アドバイスありがとうございました。

なるほど、マクロを使って2段階にすると、私がイメージしていたことが実現できます
ね。これは気が付きませんでした。

この方法を使ってインプリメントしたいと思います。
本当にありがとうございました。

yukihiro 2012/12/10(月) 12:04:57
[[解決]]
普通にFoo*やFoo&を返しちゃいけない理由が気になるのは俺だけですかね。

tetrapod 2012/12/11(火) 07:14:34
[[解決]]
offsetof は POD にしか適用できないとか
wstring の文字列そのものは MapViewOfFile とは別の場所に有りそうとか
アクセス指定子があるとメンバーのアドレス連続性は保障されていないとか
memcpy するってことは代入演算子をスルーするってことだとか
private メンバーも const メンバーも書き換えできちゃうとか

そういうのを含めて
所詮グローバル変数なんだからまわりくどいことをする必然が感じられない。
extern Foo foo; の foo を直接使うほうが100倍ましな気がする。

Blue 2012/12/12(水) 09:41:13
> 普通にFoo*やFoo&を返しちゃいけない理由が気になるのは俺だけですかね。

説明不足だったみたいで申し訳ありません。

これまでは、以下のようにDLLの共有データセクションを使用してデータを共有していま
した。
ちなみにこのDLLはグローバルフック用のプロシージャを含んでいるために、
いろいろなプロセスにインジェクトされます。

#pragma comment(linker, "/section:Shared,rws")
#pragma data_seg("Shared")

int      g_nVal;
double   g_dVal;
・・・

#pragma data_seg()

DLL内のソースコードでは、以下のようにして普通にアクセスしています。

if (g_nVal == 0) { ・・・ }

g_nVal = 1;

通常はこれで問題ないのですが、64bit環境になると話が変わってきます。
つまり、「32bit用のDLL」と「64bit用のDLL」が必要になります。
2つのDLLを使用しても共有データセクションのデータは同じものを共有したいのです。

これを解決するために、メモリマップドファイルを使うことにしました。

επιστημηさんが教えて下さった方法を使うと、上記のコードは以下のようになります。

if (GET_GLOBAL(int, g_nVal) == 0) { ・・・ }

SET_GLOBAL(int, g_nVal, 1);

かなり機械的に変更が可能ですし、コードもすっきりしています。

もし、Fooを直接返すと以下のようなコードになります。

Foo *pfoo = GET_FOO();
if (pfoo->g_nVal == 0)  { ・・・ }

あるいは、

if (GET_FOO()->g_nVal == 0)  { ・・・ }

この場合の最大の問題点は、UnmapViewOfFile()とCloseHandle()をどこで呼ぶかというこ
とです。

GET_GLOBAL(int, g_nVal)の場合には、関数の中ですでにUnmapViewOfFile()と
CloseHandle()をコールしてから、値を返しています。

Fooを返す方法ですと、

Foo *pfoo = GET_FOO();
if (pfoo->g_nVal == 0)  { ・・・ }
UnmapViewOfFile(pfoo);
CloseHandle(hPageMap);

これでは面倒ですし、UnmapViewOfFile()やCloseHandle()を関数に押し込めるには、
どうしても引数として、データメンバに関する情報を渡す必要があります。

実際には、さらに排他アクセスを行うために、SET_GLOBAL()の中では、
ミューテックスを使用して、WaitForSingleObject()とReleaseMutex()を挟んでいます。


ただし、昨日コーディングをしていて、ある問題にぶつかりました。
共有データの中には、配列があります。
配列にアクセスする場合には、ポインタを返すことになるのですが、
この場合にはUnmapViewOfFile()とCloseHandle()をコールできないのです。
コールしてしまったら、おそらくポインタの値は無効になるでしょう。

そもそも、あるプロセスで、
MapViewOfFile(hPageMap, FILE_MAP_WRITE, 0L, 0L, 0L);
のようにライトアクセスでファイルマッピングオブジェクトをマップした時に、
別のプロセスから同じ関数をコールしたらどうなるのか?
マッピングに失敗するのか、成功するけど何かデータに不整合が起きるのか・・・

このあたりを、検証してみる必要がありそうです。

yukihiro 2012/12/13(木) 00:01:53
毎回MapViewOfFile()〜CloseHandle()を呼ぶ必要があるのかという疑問は置いとくとして
も、
ラッパークラス(アクセッサ)を用意してごにょごにょやれば
いろんなことが解決しそうな気がします。

あと、余計なお世話かも知れませんが、
> 実際には、さらに排他アクセスを行うために、SET_GLOBAL()の中では、
> ミューテックスを使用して、WaitForSingleObject()とReleaseMutex()を挟んでいます。

GET_GLOBALでは排他制御してないように読めますが、大丈夫ですか。

Blue 2012/12/13(木) 15:34:51
yukihiro さん

> GET_GLOBALでは排他制御してないように読めますが、大丈夫ですか。

一応、ソースコード上では記述していますが、今のところコメントアウトしています。

排他アクセスについてはスレタイとはまた別の話題になりますので、
もし解決できない時には、また改めて質問させて頂きます。

いろいろとありがとうございました。

古いスレッドには追加発言できません。