DOBON.NET DOBON.NETプログラミング掲示板過去ログ

擬似ポインタによる値の読み書き

環境/言語:[[OS : Windows XP / 言語 : VB6.0]]
分類:[VB6以前]

初めて質問させていただきます。
開発言語はVB6.0です。

変数がたくさん用意されていており、その変数のアドレスを含む情報を
持つ構造体の配列で管理しています。(各変数をオフセットで管理するため)

オフセットを指定して、各変数に値の設定・取得を行う処理をするのですが、
VB6.0でサポートされていないポインタの概念を、下記ホームページ

http://imagingsolution.blog107.fc2.com/blog-entry-69.html

のVarPtr()とCopyMemory()で無理矢理実現させています。

今回起こっている現象は、
文字列型配列の配列番号[0]のアドレスに"abc"、[1]のアドレスに"def"を書込み、
配列番号[0]のアドレス部分を読み取ると"def"が取れてしまうと言うものです。

以下、ソースの概要を記述します。(必要部分だけ抜粋)
−−−−−−−−−−−−−−−−−−−−−−−
' オフセット管理用構造体
Private Type OFST_TBL
lOfst As Long ' オフセット
pAdrArray() As Long ' 配列各要素の先頭アドレス
lAtSize As Long ' データサイズ(要素1つ分のbyte数)
lTotalSize As Long ' データサイズ(変数全体のbyte数)
lType As Long ' データ型(0:String 1,2,4,8:各バイト数型)
End Type

Dim aaa(4) As String
(※他の変数省略)
Dim OfstTBLArray(10) As OFST_TBL

'コンストラクタ
Private Sub Class_Initialize()
With OfstTBLArray(0) ' 配列aaaを管理する構造体
.lOfst = 0     ' 配列aaaのオフセット
ReDim .pAdrArray(UBound(aaa))
For i = 0 To UBound(aaa)
.pAdrArray(i) = VarPtr(aaa(i)) ’配列aaaの各要素のアドレス
Next i
.lAtSize = 16    ’配列aaaの各要素のサイズ(16byte)
.lTotalSize = 80  ’配列aaaの全サイズ(16byte * 5)
.lType = 0     ' 配列aaaの型(String)
End With

(※他変数の構造体の初期化省略)
End Sub

' 概要
' 指定したオフセットの共有メモリデータを指定した型・サイズで取得する
' パラメタ説明
' ByVal iOfstAdr : 先頭からのオフセットアドレス
' ByVal iType : データの型(0:String 1,2,4,8:各バイト数型)
' ByVal iSize : データ長(byte)
' ByRef oData() : 取得したいデータの配列
' 戻り値
' 0 : 取得成功
' -100 : 引数範囲外
' -500 : 例外発生
Public Function GetSharedMemoryData(ByVal iOfstAdr As Long, _
ByVal iType As Long, ByVal iSize As Long, ByRef oData() As Variant) As Long

Dim i As Integer ' カウンタ変数
Dim targetIdx As Integer ' メモリマップ情報の該当インデックス
Dim diff As Long ' 該当データの先頭アドレスからの差

' 引数のオフセットがこのクラスの共有データの
' 開始オフセットより小さい場合
If iOfstAdr < OfstTBLArray(0).lOfst Then

' 引数異常
GetSharedMemoryData = -100
Exit Function

End If

' 引数のオフセットを用いて、該当データを検索
For i = 0 To UBound(OfstTBLArray)

If iOfstAdr < OfstTBLArray(i).lOfst Then
Exit For
End If

targetIdx = i

Next i

' 該当データの先頭アドレスからの差を取得
diff = iOfstAdr - OfstTBLArray(targetIdx).lOfst

On Error GoTo errorHandler
' 引数のデータ型を判断
Select Case iType
Case 0

' 引数の配列要素数チェック
If UBound(oData) < 0 Then
' 引数異常
GetSharedMemoryData = -100
Exit Function
End If

Dim str As String

' メモリをコピー
Dim adrIndex As Long
adrIndex = diff / OfstTBLArray(targetIdx).lAtSize
If UBound(OfstTBLArray(targetIdx).pAdrArray) < adrIndex Then
' 引数異常
GetSharedMemoryData = -100
Exit Function
End If
Call CopyMemory(VarPtr(str), _
OfstTBLArray(targetIdx).pAdrArray(adrIndex), iSize)

oData(0) = str

Case 1
(※省略)
Case 2
(※省略)
Case 4
(※省略)
Case 8
(※省略)
Case Else
' 引数異常
GetSharedMemoryData = -100
Exit Function

End Select

GetSharedMemoryData = 0
Exit Function

errorHandler:
GetSharedMemoryData = -500
Exit Function

End Function

' 概要
' 指定したオフセットの共有メモリデータを指定した型・サイズで設定する
' パラメタ説明
' ByVal iOfstAdr : 先頭からのオフセットアドレス
' ByVal iType : データの型(0:String 1,2,4,8:各バイト数型)
' ByVal iSize : データ長(byte)
' ByRef oData() : 設定したいデータの配列
' 戻り値
' 0 : 設定成功
' -100 : 引数範囲外
' -500 : 例外発生
Public Function SetSharedMemoryData(ByVal iOfstAdr As Long, _
ByVal iType As Long, ByVal iSize As Long, ByRef oData() As Variant) As Long

Dim i As Integer ' カウンタ変数
Dim targetIdx As Integer ' メモリマップ情報の該当インデックス
Dim diff As Long ' 該当データの先頭アドレスからの差

' 引数のオフセットがこのクラスの共有データの
' 開始オフセットより小さい場合
If iOfstAdr < OfstTBLArray(0).lOfst Then

' 引数異常
SetSharedMemoryData = -100
Exit Function

End If

' 引数のオフセットを用いて、該当データを検索
For i = 0 To UBound(OfstTBLArray)

If iOfstAdr < OfstTBLArray(i).lOfst Then
Exit For
End If

targetIdx = i

Next i

' 該当データの先頭アドレスからの差を取得
diff = iOfstAdr - OfstTBLArray(targetIdx).lOfst

On Error GoTo errorHandler
' 引数のデータ型を判断
Select Case iType
Case 0

' 引数の配列要素数チェック
If UBound(oData) < 0 Then
' 引数異常
SetSharedMemoryData = -100
Exit Function
End If

Dim str As String
str = oData(0)

' メモリをコピー
Dim adrIndex As Long
adrIndex = diff / OfstTBLArray(targetIdx).lAtSize
If UBound(OfstTBLArray(targetIdx).pAdrArray) < adrIndex Then
' 引数異常
SetSharedMemoryData = -100
Exit Function
End If
Call CopyMemory(OfstTBLArray(targetIdx).pAdrArray(adrIndex), _
VarPtr(str), iSize)

Case 1
(※省略)
Case 2
(※省略)
Case 4
(※省略)
Case 8
(※省略)
Case Else
' 引数異常
SetSharedMemoryData = -100
Exit Function

End Select

SetSharedMemoryData = 0
Exit Function

errorHandler:

SetSharedMemoryData = -500
Exit Function

End Function
−−−−−−−−−−−−−−−−−−−

手順としては、
@SetSharedMemoryData(0, 0, 16, bbb())を呼ぶ ' bbb()は要素数1 値"abc"
ASetSharedMemoryData(16, 0, 16, ccc())を呼ぶ ' ccc()は要素数1 値"def"
BGetSharedMemoryData(0, 0, 16, ddd())を呼ぶ ' ddd()は要素数1

ddd()には"abc"が取れてほしいのですが、"def"が取れてしまいます。

どこが間違っているのでしょうか?
お願い致します。
■No28996に返信(劉曹孫さんの記事)
> 変数がたくさん用意されていており、その変数のアドレスを含む情報を
> 持つ構造体の配列で管理しています。(各変数をオフセットで管理するため)

文字列コピーだけなら、lstrcpyA / lstrcpyW / lstrcpynA / lstrcpynW API が使えますし、
配列操作なら、SafeArray 系の API が使えます。

とはいえ、無理にアドレス操作するよりも、配列をプロパティ化するなどして
API を使わない実装を検討した方が安全だと思います。

固定長文字列に対するオフセットなら、Mid で切り出すという手もありますし、
バイト単位なら、Byte 配列と StrConv の組み合わせという手もあります。
(あるいは、バイナリファイルへの Get/Put で代用するとか)


> 文字列型配列の配列番号[0]のアドレスに"abc"、[1]のアドレスに"def"を書込み
同じ文字数での置き換えなら、Mid ステートメントを使った方が良いと思いますよ。


> ByRef oData() As Variant
Variant 配列と String 配列とではデータの配置が異なりますが、
その点は大丈夫ですか?


> ' 概要
> ' 指定したオフセットの共有メモリデータを指定した型・サイズで取得する
第三者が試せるコード(そのまま貼り付けるだけで動作を検証できる最低限のコード)には
なっていないこともあり、提示されたコードをまだ読み解いてはいないのですが、何となく
String の配列を Long の配列と同じように扱おうとして失敗しているかのような印象を受けました。

たとえば VB の文字列は、BSTR と呼ばれる形式で管理されていますが、
その点は理解されていますでしょうか。

ReDim a(2) As String
a(0) = "12"  'ChrW(&HFF11) & ChrW(&HFF12)
a(1) = "34"  'ChrW(&HFF13) & ChrW(&HFF14)
a(2) = "56"  'ChrW(&HFF15) & ChrW(&HFF16)

Debug.Print "VarPtr が 4 バイト単位に連続していたとしても、StrPtr もそうなるとは限らない"
Debug.Print VarPtr(a(0)), StrPtr(a(0))
Debug.Print VarPtr(a(1)), StrPtr(a(1))
Debug.Print VarPtr(a(2)), StrPtr(a(2))

a(0) = "56"
Debug.Print "文字列を差し替えると、VarPtr は変わらないが StrPtr は変化する"
Debug.Print VarPtr(a(0)), StrPtr(a(0))

Mid(a(0), 1) = "78"
Debug.Print "Mid ステートメントでの置換時は、VarPtr も StrPtr も変化しない"
Debug.Print VarPtr(a(0)), StrPtr(a(0))
■No28998に返信(魔界の仮面弁士さんの記事)

魔界の仮面弁士さんありがとうございます。

> 文字列コピーだけなら、lstrcpyA / lstrcpyW / lstrcpynA / lstrcpynW API が使えますし、
> 配列操作なら、SafeArray 系の API が使えます。
>
> とはいえ、無理にアドレス操作するよりも、配列をプロパティ化するなどして
> API を使わない実装を検討した方が安全だと思います。

構造体でメモリマップのように扱いたかったもので。
やはりアドレス操作は避けた方がいいですか。

> 同じ文字数での置き換えなら、Mid ステートメントを使った方が良いと思いますよ。

ご指摘ありがとうございます。

> Variant 配列と String 配列とではデータの配置が異なりますが、
> その点は大丈夫ですか?

すいません。それは知りませんでした。
もう少し調べてみます。

> 第三者が試せるコード(そのまま貼り付けるだけで動作を検証できる最低限のコード)には
> なっていないこともあり、提示されたコードをまだ読み解いてはいないのですが、

申し訳ありませんでした。
以後気をつけます。

> たとえば VB の文字列は、BSTR と呼ばれる形式で管理されていますが、
> その点は理解されていますでしょうか。

わかっているつもりでしたが、まだまだ理解が足りませんでした・・・


いろいろと勉強不足ですみません。ご迷惑をおかけしました。

大変勉強になりました。
ご丁寧に教えてくださいましてありがとうございました。
もう少し理解を深めてから進めようと思います。

なので、一旦解決済みとさせていただきます。
解決済み!
2011/09/07(Wed) 13:10:19 編集(投稿者)

■No28999に返信(劉曹孫さんの記事)
> なので、一旦解決済みとさせていただきます。
あれ。まだ解決していないのでは?


> 構造体でメモリマップのように扱いたかったもので。
> やはりアドレス操作は避けた方がいいですか。
LPTSTR な文字列への操作であるとか、Byte配列に対する操作程度ならば容易ですが、
BSTR な文字列への操作となると、ポインタ指定で行うのはむしろ面倒かと思いますよ。


>>Variant 配列と String 配列とではデータの配置が異なりますが、
>>その点は大丈夫ですか?
> すいません。それは知りませんでした。
> もう少し調べてみます。
配列自体の構造はこんな感じです。今回は気にする必要は無いでしょうけれどね。
http://www5f.biglobe.ne.jp/~f-lap/tips_staticarray.htm

むしろ問題となるのは「String」と「内部処理形式StringなVariant」の違いです。
Variant のデータ構造は、
 00-01: vt : 共用体を区別するためのフラグ
 02-07: wReserved1〜3 : 未使用
 08-  : 共用体。
のようになっています。

Variant の先頭 2 バイトはデータ型に相当し、これはVarType関数の戻り値で調べられます。
一方、実データの構造はデータ型によって異なりますが、VarType が vbString な
データが格納されている場合、この部分は BSTR を意味します。

参考までに、「String」と「文字列形式な Variant」の内容を比較出力するサンプルを書いてみました。
http://www.vb-user.net/junk/replySamples/2011.09.07.12.00/VariantString.zip
添付ファイル: VariantString.zip (2 KB)

DOBON.NET | プログラミング道 | プログラミング掲示板