共有メモリに構造体中のポインタの「中身」をコピーする方法について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
tarojo

共有メモリに構造体中のポインタの「中身」をコピーする方法について

#1

投稿記事 by tarojo » 10年前

はじめて質問します。

現在共有メモリに複数の構造体をメンバに持った構造体を確保しようとしており、それを他のプロセスで参照しようとしています。
ローカルで共有メモリに確保する構造体ごと作成し、ローカル構造体を共有メモリ構造体にmemcpyして更新しようと考えていました。

しかしメンバの構造体は「ポインタとして宣言した構造体」や、ダブルポインタをメンバとして持っており、単純にmemcpyするとメンバのポインタはローカルのアドレスを持ってしまいます。

共有メモリに確保されたままのアドレスで、そのアドレス先にコピーする方法はないでしょうか。

中身について、
構造体のメンバである「ポインタとして宣言した構造体」はcharの配列をメンバとして持っています。
下のコード例での「stKid_t *strSt」が、memcpyするとローカルのアドレスがコピーされてしまいます。
このポインタの指す先はそのままで、指す先の中身を変更したいと考えています。

ちなみに共有メモリのサイズはsize(stPrnt_t)ではなく、各メンバの実体分のサイズを足し上げた分を確保しています。

コード:

// ポインタとして宣言した構造体の使い方
typedef stKid
{
char str1[128];
char str2[256];

}stKid_t

typedef stPrnt
{
stKid_t *strSt;
}stPrnt_t

// 例えばこのような使い方をしたい
stPrnt_t stPrnt;
// ローカルでmallocやらデータ格納やら
for (i = 0; i < 10; i++) {
printf("%s¥n", stPrnt.strSt[i].str1);
}


YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: 共有メモリに構造体中のポインタの「中身」をコピーする方法について

#2

投稿記事 by YuO » 10年前

基本的に,共有メモリであれファイルへの出力であれ,ポインタをオフセットに変換して取り扱う必要があります。

なお,VC++固有の仕様としてベース ポインターというのがあります。
これは正に内部的にもポインタを(ポインタ+オフセット)として取り扱えるようにする機能です。
これを使えば,記述は楽になるかと思います。
オフトピック
使ったことがないですが……。
また,普通に考えると

コード:

*pointer

コード:

*(base_pointer + offset)
になるのですから,ポインタアクセスが遅くなりそうです

sleep

Re: 共有メモリに構造体中のポインタの「中身」をコピーする方法について

#3

投稿記事 by sleep » 10年前

プロセス同士で仮想メモリ空間は共有されないので、
仮想メモリ空間内のアドレス(ポインタ)をコピーしたところで
別のプロセスでは別の仮想メモリ空間なんですよね。

同じアドレス(ポインタ)を参照したところで、似て非なる場所なので
格納されているのは全然関係ないものです。

別のプロセスで使用するためには、結局どこかのタイミングで全ての値を
何かしらのカタチでコピーすることになりますね。

OpenProcess で、例え相手プロセスの仮想メモリ空間を参照できたところで
結局、値にアクセスするためには自分の仮想メモリ空間に値を持ってこないといけない訳で
そのタイミングでどうしても ReadProcessMemory による値コピーが必要になります。

sleep

Re: 共有メモリに構造体中のポインタの「中身」をコピーする方法について

#4

投稿記事 by sleep » 10年前

迷ったんですが・・・
こういう方法もある、という、あくまで情報としてだけ提供しておきます。
特にこの件については答える気は無いので、必要であれば自分の力で調べてください。

別プロセスの仮想メモリ内アドレス(ポインタ)経由で
OpenProcess、WriteProcessMemoryを使用することで
値の書き変えをするという方法を、一応、例という形でだけ示してはおきます。
(ビルド環境:Windows7、VS2013update3)

Process1.cpp

コード:


#include <windows.h>

#include <iostream>
using namespace std;


typedef
struct stKid {
	char str1[128];
	char str2[256];
} stKid_t;

typedef
struct stPrnt {
	stKid_t *strSt;
} stPrnt_t;



DWORD GetProcessID();


int main()
{
	DWORD idProcess = GetProcessID();
	cout << "idProcess: " << idProcess << endl;

	HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, idProcess);
	if (!hProcess){
		cerr << "OpenProcess(): " << GetLastError() << endl;
		cin.ignore();
		return -1;
	}

	LPVOID pInterProcessSharedPointer = VirtualAllocEx(hProcess, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
	if (!pInterProcessSharedPointer){
		cerr << "VirtualAllocEx(): " << GetLastError() << endl;
		cin.ignore();
		return -1;
	}

	stKid_t stK = { "hello", "world" };
	stPrnt_t stP = { &stK };

	DWORD	len = 0;
	BOOL	res = 0;
	if (!WriteProcessMemory(hProcess, pInterProcessSharedPointer, &stP, sizeof(stPrnt_t), &len)){
		cerr << "WriteProcessMemory(): " << GetLastError() << endl;
		cin.ignore();
		return -1;
	}

	printf("pInterProcessSharedPointer: 0x%p\n", pInterProcessSharedPointer);
	cout << endl;
	cout << stP.strSt->str1 << endl;
	cout << stP.strSt->str2 << endl;
	cout << endl;
	cout << "このタイミングで Process2のプログラムの pInterProcessSharedPointer を、こちらの pInterProcessSharedPointer で書き換えてビルドしてください。" << endl;
	cout << "その後、Process2の実行を開始し、止まったらEnterを押下してください。" << endl;
	cin.ignore();

	cout << stP.strSt->str1 << endl;
	cout << stP.strSt->str2 << endl;

	cout << endl;
	cout << "Enterを押下するとプロセスを終了します。";
	cin.ignore();

	VirtualFreeEx(hProcess, pInterProcessSharedPointer, 0, MEM_RELEASE);
	CloseHandle(hProcess);
	return 0;
}

DWORD GetProcessID()
{
	DWORD idProcess = NULL;
	char name[512] = { 0 };
	GetConsoleTitle(name, sizeof(name));
	cout << name << endl;
	string cosole_name(name);

	DWORD idThread = GetWindowThreadProcessId(FindWindow(NULL, cosole_name.data()), &idProcess);
	if (!idThread || !idProcess) {
		cerr << "GetWindowThreadProcessId(): " << GetLastError() << endl;
		cin.ignore();
		return -1;
	}

	return idProcess;
}
Process2.cpp

コード:


#include <windows.h>

#include <iostream>
using namespace std;


typedef
struct stKid {
	char str1[128];
	char str2[256];
} stKid_t;

typedef
struct stPrnt {
	stKid_t *strSt;
} stPrnt_t;


DWORD GetProcessID(const char name[]);
DWORD GetProcessID();


int main()
{
	//Process1.exe のパス
	char name[512] = { "C:\\Process1.exe" };

	//注意: ビルド&実行前に、相手プロセスのPIDを指定する必要がある。
	DWORD idProcess = GetProcessID(name);
	cout << "idProcess: " << idProcess << endl;

	HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, idProcess);
	if (!hProcess){
		cerr << "OpenProcess(): " << GetLastError() << endl;
		cin.ignore();
		return -1;
	}

	DWORD	len = 0;
	stKid_t stK_clone = { 0 };
	stPrnt_t stP_clone = { 0 };
	stP_clone.strSt = &stK_clone;


	//注意:
	// Process2が止まったら、Process1画面で表示されている相手プロセスの pInterProcessSharedPointer と
	// 同じ値にハードコーティングで書き換えた後、ビルドして実行してください。
	LPVOID pInterProcessSharedPointer = (LPVOID)0x001C0000;


	stKid_t *strSt;
	if (!ReadProcessMemory(hProcess, (LPCVOID)pInterProcessSharedPointer, &strSt, sizeof(strSt), &len)) {
		cerr << "ReadProcessMemory(): " << GetLastError() << endl;
		cin.ignore();
		return -1;
	}
	// (LPCVOID)strSt->str1 は、相手プロセスの仮想メモリ空間内のメモリアドレスを指しているだけで、こちらのプロセスで直接中身が参照できるわけではない。
	if (!ReadProcessMemory(hProcess, (LPCVOID)strSt->str1, stP_clone.strSt->str1, sizeof(stP_clone.strSt->str1), &len)) {
		cerr << "ReadProcessMemory(): " << GetLastError() << endl;
		cin.ignore();
		return -1;
	}
	// (LPCVOID)strSt->str2 は、相手プロセスの仮想メモリ空間内のメモリアドレスを指しているだけで、こちらのプロセスで直接中身が参照できるわけではない。
	if (!ReadProcessMemory(hProcess, (LPCVOID)strSt->str2, stP_clone.strSt->str2, sizeof(stP_clone.strSt->str2), &len)) {
		cerr << "ReadProcessMemory(): " << GetLastError() << endl;
		cin.ignore();
		return -1;
	}



	//★相手プロセスの仮想メモリ空間内の値を書き換える
	char str1[128] = "good evening";
	if (!WriteProcessMemory(hProcess, (LPVOID)strSt->str1, str1, sizeof(str1), &len)){
		cerr << "WriteProcessMemory(): " << GetLastError() << endl;
		cin.ignore();
		return -1;
	}


	cout << stP_clone.strSt->str1 << endl;
	cout << stP_clone.strSt->str2 << endl;

	cout << endl;
	cout << "Process1でEnterを押下して、stP.strSt->str1 の内容が書き換わったか確認してください。" << endl;

	cout << endl;
	cout << "Enterを押下するとプロセスを終了します。";
	cin.ignore();
	CloseHandle(hProcess);
	return 0;
}

DWORD GetProcessID(const char name[])
{
	DWORD idProcess = NULL;
	cout << name << endl;

	DWORD idThread = GetWindowThreadProcessId(FindWindow(NULL, name), &idProcess);
	if (!idThread || !idProcess) {
		cerr << "GetWindowThreadProcessId(): " << GetLastError() << endl;
		cin.ignore();
		return -1;
	}

	return idProcess;
}
Process1 で確保したヒープメモリの内容を
Process2 側でアドレス(ポインタ)のみの参照から 相手プロセスのヒープメモリの内容を書き換えています。

例を示すために
共有メモリにセットされたローカルアドレス(ポインタ)を
相手プロセスのヒープメモリのアドレス(ポインタ)で見立てて代用してます。

説明用に書き殴ったものなので、コードを短くするために
めんどうな実行方法を取らなければ実行できないようになってます。

準備
-----------------------------------------------------------------------------------------------------------
Process2.cpp の 27行目

コード:

char name[512] = { "C:\\Process1.exe" };
を、Process1.cpp をビルドして完成した実行ファイルのパスに書き換えておきます。
-----------------------------------------------------------------------------------------------------------

実行方法
-----------------------------------------------------------------------------------------------------------
① Process1 起動
→ Process1 が途中で止まり、メッセージが表示されるので指示にしたがう。
② Process2 の コード 49行目 を指示どおり、Process1の pInterProcessSharedPointer で直に書き換え、ビルドして起動。

コード:

LPVOID pInterProcessSharedPointer = (LPVOID)0x001C0000;
③ Process2の動作が止まったら、Process1側の画面でEnterキーを押して確認。
→ Process1側の stP.strSt->str1 が "hello" から "good evening" に書き換わっている。
④ Process1、Process2 共に終了。
-----------------------------------------------------------------------------------------------------------

ただ、折角の仮想メモリ空間を超えてまで、こんなことをする必要があるかは疑問です。

閉鎖

“C言語何でも質問掲示板” へ戻る