- PR -

【C#】INT_PTR型からユーザ定義構造型へのキャスト方法

1
投稿者投稿内容
Makoto
大ベテラン
会議室デビュー日: 2004/03/31
投稿数: 133
投稿日時: 2005-12-26 19:02
いつもお世話になっております、

C#のINT_PTR型に関連する型管理について教えて下さい。

現在、共有メモリAPIを使用して複数プロセスから共有できる
メモリ管理処理を実装していますが、下記で行き詰っています。

●INT_PTR型からユーザ定義構造型へのキャスト方法

下記コード例では、『MapViewOfFileメソッド』の戻りをINT_PTR型で受けます。
その戻り値は、当然確保した共有メモリエリアを指します。
そこで、戻り値をユーザ定義のSharedData型にキャストして値を代入したい
(データをマッピングしたい)と考えています。

が、当然のようにキャストできないのでコンパイルエラーが出ます...(泣)

なぜユーザ定義型にキャストしたいかというと、アクセスする処理を簡単にするためです。
(クラスメンバ名でアクセスしていたほうが、コードが読みやすいからです。)

どなたか、『INT_PTR型からユーザ定義構造型へのキャスト方法』
あるいは『本現象の回避方法』に関して、
ご存知の方いらっしゃいましたらご意見などよろしくお願いいたします。

〜〜〜下記にコード例を示します。〜〜〜

using System;
using System.Runtime.InteropServices;

namespace APIWrapper
{
public class SharedData //サイズは、512+512+4(バイト)
{
public char[] m_mylineNo = new char[512];
public char[] m_url = new char[512];
public int m_Value = 0;
}

/// 共有メモリ管理クラス
public class SharedMemoryMgr
{
//すでに共有メモリ作成済の場合のエラー値
const int ERROR_ALREADY_EXISTS = 183;

//共有メモリ確保用のハンドルとポインタを定義
IntPtr m_memAreaHandle = IntPtr.Zero;
IntPtr m_memAreaPointer = IntPtr.Zero;

//
public unsafe bool CreateMemory()
{
try
{
m_memAreaHandle = CreateFileMapping( (IntPtr)0xFFFFFFFF, // ファイルハンドル
(IntPtr)0, // セキュリティ属性
SECTION_ALL_ACCESS, // 保護属性
0, // サイズ上位
1028, // サイズ下位
"テスト用の共有メモリ"); // オブジェクト名

int retval = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
if(retval == ERROR_ALREADY_EXISTS)
{
//他プロセスで既に作成済⇒マッピング処理なし
}
else
{
//データマッピングを行う。

//エリアを初期化する(※C#なので、多分自動的にnullリセットされてると思うケド...)
m_memAreaPointer = MapViewOfFile(m_memAreaHandle,SECTION_ALL_ACCESS,0,0,0);

//下は、いずれもコンパイルエラーになる。
//((SharedData)m_memAreaPointer).m_url = "test";
//SharedData* p = (SharedData*)m_memAreaPointer;
//SharedData data = (SharedData)m_memAreaPointer;

}
}
catch(Exception ex)
{
ex.ToString();
}
return false;
}

//●下記にアクセス制限設定などを定義する。
const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;
const UInt32 SECTION_QUERY = 0x0001;
const UInt32 SECTION_MAP_WRITE = 0x0002;
const UInt32 SECTION_MAP_READ = 0x0004;
const UInt32 SECTION_MAP_EXECUTE = 0x0008;
const UInt32 SECTION_EXTEND_SIZE = 0x0010;
const UInt32 SECTION_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED|SECTION_QUERY|
SECTION_MAP_WRITE |
SECTION_MAP_READ |
SECTION_MAP_EXECUTE |
SECTION_EXTEND_SIZE);
const UInt32 FILE_MAP_ALL_ACCESS = SECTION_ALL_ACCESS;


//共有メモリ制御用のAPIを.NET(C#)用にマーシャリングして再定義する。
[DllImport("kernel32.dll", SetLastError=true)]
static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr lpFileMappingAttributes, uint flProtect, uint dwMaximumSizeHigh,
uint dwMaximumSizeLow, string lpName);

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, uint
dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow,
uint dwNumberOfBytesToMap);
}
}
囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2005-12-26 19:28
こんにちは。

全然回答になってない上に、超〜多分ですが。ヒントになればと。

まず、SharedData は意図したサイズ通りにならないかと思います。
safe なクラスのサイズは規定されていないからです。

そういう事情があるので、IntPtr を ShareData にキャストしてオフセットでアクセスするというのも不可能でしょう。
SharedData も unsafe にしたら、サイズがきっちり決まるのかな?sizeof も使えるし多分そうでしょう。(値型にする必要があるのかも)

もう少し unsafe にすべき項目を明確にしたら、もしかしたらいけるかもしれませんね。


_________________
囚人のジレンマな日々
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2005-12-26 19:29
//INT_PTRとか書かれるとまるで独自定義した型みたいでイヤンですな。


C#では、ポインタと構造体の相互変換はあまりサポートされていません。
MarshalAs属性に頼らない(つまり固定長配列や固定長文字列を含まない(*)、かつclassでもない)単純な構造体の場合のみ、unsafe構文内でキャストが可能な程度です。
(*)C# 2.0ではfixedキーワードを使えば固定長メンバを扱えますが、今度はマネージドの世界で極めて扱いにくくなります。

この点をカバーするため、.NET FrameworkはSystem.Runtime.InteropServices.Marshalクラスで、各種マーシャリングの実装を提供しています。
構造体とポインタの相互変換には、Marshal.PtrToStructure/Marshal.StructureToPtrを使うことになります。
//Marshal.StructureToPtrは変換と言うよりは書き込みですが。
これらのメソッドではMarshalAs属性も考慮して変換が行われます。

[ メッセージ編集済み 編集者: Hongliang 編集日時 2005-12-26 19:30 ]
Makoto
大ベテラン
会議室デビュー日: 2004/03/31
投稿数: 133
投稿日時: 2005-12-27 11:20
回答ありがとうございました。

下記のようにコピー処理を実装したところ、コンパイルは通りました。

SharedData data = new SharedData();
data.m_url[0]='c';
data.m_mylineNo[0]='d';
data.m_Value=123;
System.Runtime.InteropServices.Marshal.StructureToPtr(data, m_memAreaPointer, false);

ところで、上記でコンパイルは通りましたという微妙な表現をしているのは、
実はメモリ確保処理で下記メッセージ例外がスローされるためです。
(⇒上記のコピー処理まで処理が進まないのです。)

●例外メッセージ:『演算操作の結果オーバーフローが発生しました。』

●例外発生処理:単純に下記のように引数を与えて処理すると例外が発生します。
 (いくつか気になる点を変更してみたのですが、やはり同じエラーでした...)

確認点としては、セキュリティ属性、ファイルハンドル、保護属性などの定義値を確認しましたが
あっているようでした。
(CreateFileMappingの定義を間違えているのかなぁ?という気もしてきています。
 ハンドル型は、IntPtrで良いのかなど現在調査中です。)

m_memAreaHandle = CreateFileMapping( (IntPtr)0xFFFFFFFF,// ファイルハンドル
IntPtr.Zero, // セキュリティ属性
0x04, // 保護属性(R/W)
0, // サイズ上位
1024, // サイズ下位 "mutualMem1"); // オブジェクト名

せっかく回答いただいたのに、動作報告をできなくて申し訳ないです...
囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2005-12-27 11:32
またまた局所的な回答ですが…。
http://mag.autumn.org/Content.modf?id=20041117123623
例外はここですね。( IntPtr )0xFFFFFFFF
_________________
囚人のジレンマな日々
Makoto
大ベテラン
会議室デビュー日: 2004/03/31
投稿数: 133
投稿日時: 2005-12-27 14:16
アドバイスありがとうございました。

下記のように、変更したところうまくいきました。

●変更点1
 CreateFileMapping関数の第一引数の型を『UIntPtr』⇒『IntPtr』へ
 変更したところ処理が正常に動作しました。
(プロセスを2つ起動すると、すでにマッピング済だから作成しないの処理に入りました。)

●変更点2
 SharedData型を暮クラスから構造体へ変更。
 (Javaではstructが存在しなかったような気がしたので、
  C#にもないと思っていましたあるんですね。)

(オーバーフローだったので、メモリ割当例外としか思いませんでした...
 メモリサイズの指定がおかしいのかなどと悩んでいました。
 まさかintがオーバーフローとは...)

ところで、いくつか気になる点があります。
ご存知の方いらっしゃいましたら、アドバイスなどお願いします。

●1
 System.Runtime.InteropServices.Marshal.StructureToPtr()は、
 『第三引数にfalseを入れるとメモリリークするかもしれない』という
 表記がMSDNにありました。これってどういうことなんでしょうか?

>false を渡すと、メモリ リークが生じる場合があります。

●2
 『(C#とはいえ)共有メモリは、明示的に開放処理を行わないと開放されない』
 と思うのですが、プロセスをすべて落とすと開放されているようです。
 (開放されていると思う判断基準は、同様の名前でメモリを作成すると
  再作成しようとするからです。)

●3
 下記で、同様の値を参照できることはわかったのですが、
 下記で取得したエリアに値を書き込むと共有メモリ値は変更されないようです。
 (ローカルデータが変更されるようです。)
 ひとつのメンバを書き換える場合でも、下記を利用してすべてを上書きするしかないのでしょうか?

System.Runtime.InteropServices.Marshal.StructureToPtr();

あとは、開放処理、同期処理などを実装すれば、使えそうな感じです。
ありがとうございました。

ご参考までに、下記にコード例を示します。

〜〜〜コード例〜〜〜

using System;
using System.Runtime.InteropServices;

namespace APIWrapper
{
//テスト用の共有データ型
public struct SharedData
{
public char m_mylineNo;
public char m_url;
public int m_Value ;
}

public class SharedMemoryMgr
{
//すでに共有メモリ作成済の場合のエラー値
const int ERROR_ALREADY_EXISTS = 183;

//共有メモリ確保用のハンドルとポインタを定義
IntPtr m_memAreaHandle = IntPtr.Zero;
IntPtr m_memAreaPointer = IntPtr.Zero;

public unsafe bool CreateMemory()
{
try
{
m_memAreaHandle = CreateFileMapping( (UIntPtr )0xFFFFFFFF, // ファイルハンドル
IntPtr.Zero, // セキュリティ属性
0x04, // 保護属性(R/W)
0, // サイズ上位
1024, // サイズ下位
"mutualMem1"); // オブジェクト名

int retval = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
if(retval == ERROR_ALREADY_EXISTS)
{
//////////////////////////////////////////////////
// 他プロセスで既に作成済⇒マッピング処理なし //
//////////////////////////////////////////////////
m_memAreaPointer = MapViewOfFile(m_memAreaHandle,SECTION_ALL_ACCESS,0,0,0);

SharedData data ;
object obj = System.Runtime.InteropServices.Marshal.PtrToStructure(m_memAreaPointer,typeof(SharedData));

data = (SharedData)obj;

//////////////////////////////////////////////////////////////////////
//ここで、dataのメンバには、すでに確保されている情報が入っている。 //
//////////////////////////////////////////////////////////////////////
}
else
{
//////////////////////////////////////////
//初回なので、データマッピングを行う。 //
//////////////////////////////////////////

//エリアを初期化する(※C#なので、多分自動的にnullリセットされてると思うケド...)
m_memAreaPointer = MapViewOfFile(m_memAreaHandle,SECTION_ALL_ACCESS,0,0,0);

SharedData data = new SharedData();
data.m_url='c';
data.m_mylineNo='d';
data.m_Value=123;
System.Runtime.InteropServices.Marshal.StructureToPtr(data, m_memAreaPointer, false);
}
}
catch(Exception ex)
{
ex.ToString();
}
return false;
}

//////////////////////////////////////////////////////
// //
//●参考サイト:pinvoke.net //
// http://www.pinvoke.net/index.aspx //
// //
//////////////////////////////////////////////////////

//●下記にアクセス制限設定などを定義する。
const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;
const UInt32 SECTION_QUERY = 0x0001;
const UInt32 SECTION_MAP_WRITE = 0x0002;
const UInt32 SECTION_MAP_READ = 0x0004;
const UInt32 SECTION_MAP_EXECUTE = 0x0008;
const UInt32 SECTION_EXTEND_SIZE = 0x0010;
const UInt32 SECTION_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED|SECTION_QUERY|
SECTION_MAP_WRITE |
SECTION_MAP_READ |
SECTION_MAP_EXECUTE |
SECTION_EXTEND_SIZE);
const UInt32 FILE_MAP_ALL_ACCESS = SECTION_ALL_ACCESS;


//共有メモリ制御用のAPIを.NET(C#)用にマーシャリングして再定義する。
[DllImport("kernel32.dll", SetLastError=true)]
static extern IntPtr CreateFileMapping(UIntPtr hFile,
IntPtr lpFileMappingAttributes, uint flProtect, uint dwMaximumSizeHigh,
uint dwMaximumSizeLow, string lpName);

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, uint
dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow,
uint dwNumberOfBytesToMap);
}
}

以上です。
Hongliang
ぬし
会議室デビュー日: 2004/12/25
投稿数: 576
投稿日時: 2005-12-27 19:04
引用:

●変更点1
 CreateFileMapping関数の第一引数の型を『UIntPtr』⇒『IntPtr』へ
 変更したところ処理が正常に動作しました。
(プロセスを2つ起動すると、すでにマッピング済だから作成しないの処理に入りました。)


これは『IntPtr』⇒『UIntPtr』の間違いですね。
ところでINVALID_HANDLE_VALUEは ((HANDLE)(-1)) という定義であって、0xFFFFFFFFでは無いと思うのですが。
//Win32なら確かに同値ですけどね。
そして、IntPtr型はコンストラクタ引数にintを受け付けるのですから、当然-1も渡せます。

引用:

 SharedData型を暮クラスから構造体へ変更。
 (Javaではstructが存在しなかったような気がしたので、
  C#にもないと思っていましたあるんですね。)


構造体の導入は.NET/C#の大きな特徴のひとつですね。

引用:

●1
 System.Runtime.InteropServices.Marshal.StructureToPtr()は、
 『第三引数にfalseを入れるとメモリリークするかもしれない』という
 表記がMSDNにありました。これってどういうことなんでしょうか?

>false を渡すと、メモリ リークが生じる場合があります。


あるアドレスAが構造体Bを指していて、この構造体Bはメンバに文字列へのポインタを持っているとします。
falseを指定した場合、何も考えずそのまま構造体をアドレスAに書き込みます。
その結果、Aが元々指していた構造体Bは上書きされ、Bが持っていた文字列へのポインタは失われます。
この文字列へはもうアクセスしようがなくなる、つまりリークします。
アドレスがなくなるのですから解放もできません。

trueを指定した場合は、書き込む前にMarshal.DestoryStructureを呼び出して構造体Bの各メンバを解放してくれます。

引用:

●2
 『(C#とはいえ)共有メモリは、明示的に開放処理を行わないと開放されない』
 と思うのですが、プロセスをすべて落とすと開放されているようです。
 (開放されていると思う判断基準は、同様の名前でメモリを作成すると
  再作成しようとするからです。)


これはC#がどうこうというよりWindowsの管理する問題です。
プロセスが獲得したハンドルやメモリは、プロセスの終了時にWindowsが自動的に解放します。
//そうでなければ不正終了したときにハンドルやメモリが奪われっぱなしになってしまいます!
勿論全ての参照が無くなった共有メモリも自動的に解放されます。

引用:

●3
 下記で、同様の値を参照できることはわかったのですが、
 下記で取得したエリアに値を書き込むと共有メモリ値は変更されないようです。
 (ローカルデータが変更されるようです。)
 ひとつのメンバを書き換える場合でも、下記を利用してすべてを上書きするしかないのでしょうか?

System.Runtime.InteropServices.Marshal.StructureToPtr();


構造体は値型です。
代入したときはメンバがそれぞれコピーされ、元の構造体とは別のインスタンスになります。
//というかPtrToStructure時点で別インスタンスになる気がするけど。
それがいやなら、Marshal.Write.../Read...メソッドで直に読み書きするしかないでしょう。

引用:

//エリアを初期化する(※C#なので、多分自動的にnullリセットされてると思うケド...)
m_memAreaPointer = MapViewOfFile(m_memAreaHandle,SECTION_ALL_ACCESS,0,0,0);


CreateFileMappingによるメモリ確保にC#/.NETは関知しませんから、ゼロクリアされているとはいえません。
//まあ、いちど構造体を書き込めば元々の値に意味はなくなるでしょうけど。
なお、C#では構造体の各メンバがインスタンス作成時に0で初期化されることは保証されています。引数付きコンストラクタを使う場合は別ですが。
Makoto
大ベテラン
会議室デビュー日: 2004/03/31
投稿数: 133
投稿日時: 2005-12-27 19:43
Hongliangさん、回答ありがとうございます。

上記の回答で、すべてのなぞが解けました。
(すべて合点がいきました。)

アンマネージとなると、C#も奥が深いですね。
(非常に勉強になりました。)

わかりやすい解説、本当にありがとうございました。
1

スキルアップ/キャリアアップ(JOB@IT)