C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

2次元配列のメモリ配置

[トピック内 19 記事 (1 - 19 表示)]  << 0 >>

■86024 / inTopicNo.1)  2次元配列のメモリ配置
  
□投稿者/ masa (1回)-(2017/12/13(Wed) 11:37:03)

分類:[その他の言語] 

2017/12/13(Wed) 11:57:08 編集(投稿者)
EXCEL VBAの質問ですが、こちらで大丈夫でしょうか?
(EXCEL:2007 OS:Woindows7 64bit)

2次元配列のメモリ配置がどのようになっているかと、
MoveMemory関数の勉強のために以下のようなコードで実験しました。

2次元配列から指定した1列のみ取り出すというコードです。
配列はRangeオブジェクトから取得しているので添え字は1Base、
又データー型はVariant型としています。
配列データが数値(SetDataLong)なら期待する結果になりますが、
文字列(SetDataString)だとEXCEL毎、落ちてしまいます。
値型と参照型でメモリ配置が違うのが原因と思いますが理解不足の為、自力で対応できません。

文字列データの場合も1列のみ取り出すにはどうすればいいですか?

Private Declare Sub MoveMemory Lib "Kernel32" Alias "RtlMoveMemory" _
   (pDest As Any, pSrc As Any, ByVal cbLen As Long)

Private Sub MoveMemory_2次元配列から列を取り出す()
    Const field As Long = 3 '取り出す列
    Dim Source() As Variant
    Dim Dest() As Variant
    Dim lbyte As Long
    SetDataLong '数値だけならOK
    'SetDataString 'EXCELが落ちる
    Source = Range("A1").CurrentRegion
    ReDim Dest(1 To UBound(Source, 1), 1 To 1) 'Sourceの1列分と同じサイズをメモリに確保
    lbyte = VarPtr(Source(2, 1)) - VarPtr(Source(1, 1)) '=16byte
    MoveMemory ByVal VarPtr(Dest(1, 1)), ByVal VarPtr(Source(1, field)), lbyte * (UBound(Dest) - LBound(Dest) + 1)
    Range("A1").CurrentRegion.Resize(, 1).Offset(, Range("A1").CurrentRegion.Columns.Count + 1).Value = Dest
    Erase Source, Dest
End Sub

Private Sub SetDataLong()
    Dim cnt As Long
    Dim i As Long, j As Long
    For i = 0 To 3
        For j = 0 To 2
            Range("A1").Offset(j, i).Value = cnt
            cnt = cnt + 1
        Next
    Next
End Sub

Private Sub SetDataString()
    Dim cnt As Long
    Dim i As Long, j As Long
    For i = 0 To 3
        For j = 0 To 2
            Range("A1").Offset(j, i).Value = Chr(65 + cnt)
            cnt = cnt + 1
        Next
    Next
End Sub

引用返信 編集キー/
■86025 / inTopicNo.2)  Re[1]: 2次元配列のメモリ配置
□投稿者/ masa (2回)-(2017/12/13(Wed) 11:58:45)
No86024 (masa さん) に返信
> 2017/12/13(Wed) 11:57:08 編集(投稿者)
>
> EXCEL VBAの質問ですが、こちらで大丈夫でしょうか?
> (EXCEL:2007 OS:Woindows7 64bit)
>
> 2次元配列のメモリ配置がどのようになっているかと、
> MoveMemory関数の勉強のために以下のようなコードで実験しました。
>
> 2次元配列から指定した1列のみ取り出すというコードです。
> 配列はRangeオブジェクトから取得しているので添え字は1Base、
> 又データー型はVariant型としています。
> 配列データが数値(SetDataLong)なら期待する結果になりますが、
> 文字列(SetDataString)だとEXCEL毎、落ちてしまいます。
> 値型と参照型でメモリ配置が違うのが原因と思いますが理解不足の為、自力で対応できません。
>
> 文字列データの場合も1列のみ取り出すにはどうすればいいですか?
>
> Private Declare Sub MoveMemory Lib "Kernel32" Alias "RtlMoveMemory" _
> (pDest As Any, pSrc As Any, ByVal cbLen As Long)
>
> Private Sub MoveMemory_2次元配列から列を取り出す()
> Const field As Long = 3 '取り出す列
> Dim Source() As Variant
> Dim Dest() As Variant
> Dim lbyte As Long
> SetDataLong '数値だけならOK
> 'SetDataString 'EXCELが落ちる
> Source = Range("A1").CurrentRegion
> ReDim Dest(1 To UBound(Source, 1), 1 To 1) 'Sourceの1列分と同じサイズをメモリに確保
> lbyte = VarPtr(Source(2, 1)) - VarPtr(Source(1, 1)) '=16byte
> MoveMemory ByVal VarPtr(Dest(1, 1)), ByVal VarPtr(Source(1, field)), lbyte * (UBound(Dest) - LBound(Dest) + 1)
> Range("A1").CurrentRegion.Resize(, 1).Offset(, Range("A1").CurrentRegion.Columns.Count + 1).Value = Dest
> Erase Source, Dest
> End Sub
>
> Private Sub SetDataLong()
> Dim cnt As Long
> Dim i As Long, j As Long
> For i = 0 To 3
> For j = 0 To 2
> Range("A1").Offset(j, i).Value = cnt
> cnt = cnt + 1
> Next
> Next
> End Sub
>
> Private Sub SetDataString()
> Dim cnt As Long
> Dim i As Long, j As Long
> For i = 0 To 3
> For j = 0 To 2
> Range("A1").Offset(j, i).Value = Chr(65 + cnt)
> cnt = cnt + 1
> Next
> Next
> End Sub
引用返信 編集キー/
■86027 / inTopicNo.3)  Re[2]: 2次元配列のメモリ配置
□投稿者/ とっちゃん (474回)-(2017/12/13(Wed) 13:48:42)
No86024 (masa さん) に返信

> EXCEL VBAの質問ですが、こちらで大丈夫でしょうか?
> (EXCEL:2007 OS:Woindows7 64bit)
答えてくれる人がどの程度いるかはわかりませんが、問題ないと思います。

さて本題。

> 2次元配列のメモリ配置がどのようになっているかと、
> MoveMemory関数の勉強のために以下のようなコードで実験しました。
>
> 2次元配列から指定した1列のみ取り出すというコードです。
> 配列はRangeオブジェクトから取得しているので添え字は1Base、
> 又データー型はVariant型としています。
> 配列データが数値(SetDataLong)なら期待する結果になりますが、
> 文字列(SetDataString)だとEXCEL毎、落ちてしまいます。
> 値型と参照型でメモリ配置が違うのが原因と思いますが理解不足の為、自力で対応できません。
>
> 文字列データの場合も1列のみ取り出すにはどうすればいいですか?
>

まず、本当に知りたいこと(学習の要素)は
2次元配列なのか、MoveMemory なのかもしくは
Excel の特定のセルのリストを取り出したいなど質問として書かれていないことなのか
では大きく変わります。

2次元配列を学習したいのなら、ExcelVBAを使ってもいいと思いますが、
純粋なデータ型としての配列を学習する必要があります(すいません、VBAはよくわかっていないので
2次元配列をどうやって宣言すればいいかは知りません)。

また、MoveMemory の使い方が学習したい要素なのであれば、VBAではなく、CまたはC++言語をお勧めします。
そうではなく、API(たまたま事例に挙げたのがMoveMemoryなだけ)なのであれば、具体的に
学習したいAPIについて質問するほうがいいでしょう。

それ以外の何かということであれば、この質問についてはこれまでとし、
知りたいことを改めて質問することをお勧めします。

引用返信 編集キー/
■86028 / inTopicNo.4)  Re[3]: 2次元配列のメモリ配置
□投稿者/ masa (3回)-(2017/12/13(Wed) 15:40:51)
No86027 (とっちゃん さん) に返信

返信ありがとうございます。

文字列2次元配列の場合、MoveMemoryの第1引数、第2引数のポインタ、第3引数のバイト数に
何を設定すればよいのかを知りたいです。
上記のコードで文字列配列の場合でも成功する時もあり、あれ?となった次第です。
引用返信 編集キー/
■86035 / inTopicNo.5)  Re[4]: 2次元配列のメモリ配置
□投稿者/ 774RR (577回)-(2017/12/13(Wed) 16:23:01)
Excel が中で持っている文字列を RtlMoveMemory して動作保証があるかどうか、あたりから始める必要があるってこと。
オイラは保証は無いと思っている。

なので真にやりたいことが
・ Excel の持っているデータを拾い出す・書き換えること
・ RtlMoveMemory を使うこと
のどっちかで話は変わってくるという旨を とっちゃん (さん略) は言っているわけだ。

どっち?


引用返信 編集キー/
■86036 / inTopicNo.6)  Re[4]: 2次元配列のメモリ配置
□投稿者/ とっちゃん (475回)-(2017/12/13(Wed) 17:16:29)
No86028 (masa さん) に返信
> ■No86027 (とっちゃん さん) に返信
>
> 返信ありがとうございます。
>
> 文字列2次元配列の場合、MoveMemoryの第1引数、第2引数のポインタ、第3引数のバイト数に
> 何を設定すればよいのかを知りたいです。
> 上記のコードで文字列配列の場合でも成功する時もあり、あれ?となった次第です。

VBA上でですか?

であれば、いかなる理由があろうとも、MoveMemory でVBA内の型へデータ転送してはいけません。
どのような変数であってもです。

VBAの変数はVARIANT型, BSTR型, SAFEARRAY型か、IDispatch型のいずれかの内部型が利用されています。
これらは、いずれも単純なメモリイメージではなく、付加的な情報が多数ついたメモリ上のオブジェクトになります。
詳しくは、COMのリファレンスを参照していただいたうえで、Windows SDK の該当する型(C言語レベルの定義)を見てください。

MoveMemory でデータを転送する場合、少なくともC言語レベルでデータ構造を熟知している必要があり
その状態を見て何をどこに転送すればいいかを考えることができる必要があります。

また、コピーした状態(実質的にはデッドコピーなので、そのまま本来の型として使うとあとでクラッシュする)で
さらに整合性の取れる状況にするのは非常に困難といえます。

ちなみに。。。ですが、SAFEARRAY(VBAでの配列型)に格納された文字列(BSTR)をCで転送する場合も
VBAでFor文を回して Dest(i) = Source(i) とやるのと同じ処理をC言語レベルで書きます。

その場合、残念ですが、Dest[i] = Source[i] などという簡素なコードではなく、文字列データのコピー処理を書く必要があります。
(BSTRのコピーを行う必要があるため、もちろん転送先もBSTRとして正しく初期化・解放する必要がある)。

引用返信 編集キー/
■86042 / inTopicNo.7)  Re[5]: 2次元配列のメモリ配置
□投稿者/ masa (4回)-(2017/12/14(Thu) 10:30:20)
No86036 (とっちゃん さん) に返信
返信ありがとうございます。
非常に細かく丁寧な解説で概略は理解しました。

あと1つだけ教えてください。

> VBA上でですか?
> であれば、いかなる理由があろうとも、MoveMemory でVBA内の型へデータ転送してはいけません。
> どのような変数であってもです。

の発言に対して、
最初のVBAのコードで数値配列の場合は100%の確率で期待する結果が得られていますが
これは偶然と考えるべきですか?(50回位しかテストしてませんが....)
もう少し広義で言うと、プリミティブな値型の配列の場合という意味です。

それとも上の発言は参照型の配列に対してのみダメという意味ですか?






引用返信 編集キー/
■86043 / inTopicNo.8)  Re[6]: 2次元配列のメモリ配置
□投稿者/ とっちゃん (476回)-(2017/12/14(Thu) 11:41:44)
No86042 (masa さん) に返信

>>VBA上でですか?
>>であれば、いかなる理由があろうとも、MoveMemory でVBA内の型へデータ転送してはいけません。
>>どのような変数であってもです。
>
> の発言に対して、
> 最初のVBAのコードで数値配列の場合は100%の確率で期待する結果が得られていますが
> これは偶然と考えるべきですか?(50回位しかテストしてませんが....)
> もう少し広義で言うと、プリミティブな値型の配列の場合という意味です。
>
> それとも上の発言は参照型の配列に対してのみダメという意味ですか?
>
SAFEARRAY は、その構造上値型と呼ばれるデータ型であっても、それが値として格納される保証がありません。

保証がないので、そこで得られた結果は必然とは言えません。
保証がないので、それを期待して使うことには問題があります。

同じ環境で動かしている、同じプログラムは何度動かしても同じ結果を返します。
毎回違う要素が含まれるように作ってある(乱数を使うとか)なら異なる結果が出ることもありますが
そうではないのなら、同じ結果にならないほうが問題です。




最初に書いていますが、ここで得たい知見は何ですか?
それによって回答が変わります。


MoveMemory そのものの使い方ですか?

もし、そうであれば、まずVBA上で学習するのではなく
より直接的にメモリを扱える開発環境で学習することをお勧めします。


Source = Range("A1").CurrentRegion
で返ってくる、Source の内部データ構造の詳細ですか?

そうであれば、先に提示した SAFEARRAY や VARIANT という型について
調べるということになります。
内容によっては、より深くCOMについて学ぶ必要があるかもしれません。


それとも、VBA上での変数の型の定義方法でしょうか?

Option Strict On

とすれば、型を厳格化させられるので、学習しやすいと思います。
Variant で受けるでいいと思いますが、Excel を外部制御したことがないので
よくわからないです。



それとも、当初の質問には出ていない違う何かでしょうか?
違う何かなのであれば、ここは一度閉じてしまい
改めて、知りたいことが何かを整理して質問することをお勧めします。

http://www.hyuki.com/writing/techask.html

こういうページもあります。
質問を取りまとめる際の参考にしてみてください。

引用返信 編集キー/
■86044 / inTopicNo.9)  Re[7]: 2次元配列のメモリ配置
□投稿者/ masa (5回)-(2017/12/14(Thu) 12:08:46)
2017/12/14(Thu) 12:09:26 編集(投稿者)

No86043 (とっちゃん さん) に返信

何度もすみません。
上記の件、了解しました。
質問の主旨がわかりにくかったですかね。申し訳ありません。

自分としては配列がメモリ上でどのように配置されているかが知りたかったのです。
「MoveMemoryの引数に与える値が分かる」=「メモリ上でどのように配置されて
いるかが理解できている」逆に「メモリ配置が理解できていなければ
MoveMemoryの引数に与える値が分からない」という理解でした。
結局質問のタイトル通りなんですけど。
VBAで検証したのは、軽いのとExcelの表は2次元配列そのもののようなものだからです。


とりあえず、ご指摘にありましたC言語を学ぶことから始めたいと思います。
お付き合いいただき、ありがとうございました。
解決済み
引用返信 編集キー/
■86059 / inTopicNo.10)  Re[5]: 2次元配列のメモリ配置
□投稿者/ 774RR (578回)-(2017/12/15(Fri) 08:48:42)
C における「二次元配列」は「「固定長の配列」の配列」なわけだけど、他の言語・処理系では
・配列という名前で実はリストであるとか
・二次元配列という名前で実はジャグ配列であるとか
・二次元配列という名前で実は「「配列への参照」の配列」であるとか
そういう場合もあるわけだ。

なので言語や処理系を特定した上で「その処理系で」二次元配列って何、どんな構造?
という話ならば意味あるけど、一般論だとなんともいえないで終わっちゃうよ。

まあ「メモリ上のデータ配置」を気にしなきゃならない言語って C/C++ くらいだし
それを学びたいのであれば C/C++ を学んでみるのはありかもしれない。
# いまどきの入門コースだともっと良い言語はいっぱいあるんだけど

解決済み
引用返信 編集キー/
■86061 / inTopicNo.11)  Re[6]: 2次元配列のメモリ配置
□投稿者/ masa (6回)-(2017/12/15(Fri) 10:01:21)
No86059 (774RR さん) に返信
返信ありがとうございます。
言語の違いによりメモリ配置が変わってくるのは理解できます。
今回も事前にVB.NETでは、Buffer.BlockCopyメソッドで確認しております。

例えばarr(1,1)の配列の場合
.NETでは
arr(0,0)
arr(0,1)
arr(1,0)
arr(1,1)

VBAでは(VarPtrで確認)
arr(0,0)
arr(1,0)
arr(0,1)
arr(1,1)

となると理解しております。間違ってたらご指摘ください。

高級言語ではメモリに関してほとんどの場合、意識することなく
プログラミングできてしまいます。
でもそういうことが理解できていると、より細かい処理がしたい場合や、
処理速度の向上に寄与すると思うのです。

とりあえず自分にはとっちゃん様が指摘くださったようにC系の言語を勉強するのが先決かなと
今は思う次第です。

引用返信 編集キー/
■86070 / inTopicNo.12)  Re[7]: 2次元配列のメモリ配置
□投稿者/ とっちゃん (478回)-(2017/12/15(Fri) 12:23:32)
No86061 (masa さん) に返信

>言語の違いによりメモリ配置が変わってくるのは理解できます。

んと。。。同じ言語でも用意の仕方によって配置は変わります。
VB系でいえば、VB.NET があります。
VB.NET では、連続したメモリとして確保する多次元配列もジャグ配列も用意できます。
ジャグ配列は、1次元目と2次元目がつながっていないのでまとめて一括コピーはできません。

VBAで実現できるかどうかはわかりませんがSAFEARRAY自体には
ジャグ配列も、連続メモリの多次元配列もどちらもSAFEARRAYで表現できるように作られています。

もっともVBA自身が実行時のメモリ状態などを考慮できるように
プログラム可能になっていないので、実際のところどんな状態で
確保しているかはわからないです。

実行するマシンで変わってしまうなどもあるかもしれないレベルです。



ところで、配列(というか、要素指定でアクセス可能な変数)の物理メモリ上の並びが重要なのではないですよね?


> 高級言語ではメモリに関してほとんどの場合、意識することなく
> プログラミングできてしまいます。

高級言語がどこを指しているのかわかりませんが、おそらく意識すべきはメモリ配置などではなく
「アルゴリズムとデータ構造」といった基礎知識を含む設計全般ではないかな?と思います。


> とりあえず自分にはとっちゃん様が指摘くださったようにC系の言語を勉強するのが先決かなと
> 今は思う次第です。
>

CやC++でも構いませんが、せっかくVBの知識があるのならまずは、VB.NET でもいいと思いますよ。

引用返信 編集キー/
■86072 / inTopicNo.13)  Re[8]: 2次元配列のメモリ配置
□投稿者/ 魔界の仮面弁士 (1508回)-(2017/12/15(Fri) 16:27:12)
No86044 (masa さん) に返信
> 「MoveMemoryの引数に与える値が分かる」=「メモリ上でどのように配置されて
> いるかが理解できている」逆に「メモリ配置が理解できていなければ
> MoveMemoryの引数に与える値が分からない」という理解でした。

VarPtr/StrPtr/ObjPtr の意味や、SAFEARRAY に対してどこまで理解されているのか分からなかったので、
ひとまず String データ(BSTR)の内容を出力するサンプルを書いてみました。

理解の手助けになるかは分かりませんが、参考までに。


' VB6 および VBA7.1(x64) で動作確認してあります
Option Explicit
Option Base 0

#If Win64 Then
Private Declare PtrSafe Sub RtlMoveMemory Lib "kernel32" (ByVal dst As LongPtr, ByVal src As LongPtr, ByVal length As Long)
#Else
Private Declare Sub RtlMoveMemory Lib "kernel32" (ByVal dst As OLE_HANDLE, ByVal src As OLE_HANDLE, ByVal length As Long)
#End If

Public Sub Main()
    DumpBStr vbNullString
    DumpBStr ""
    DumpBStr "全角5文字"   'UCS-2
    DumpBStr "Hankaku 21 Characters"    'UCS-2
    DumpBStr "全半Mixな9文字"   'UCS-2
    DumpBStr StrConv("全角5文字", vbFromUnicode, 1041)   'CP932
    DumpBStr StrConv("Hankaku 21 Characters", vbFromUnicode, 1041)   'CP932
    DumpBStr StrConv("全半Mixな9文字", vbFromUnicode, 1041)   'CP932
End Sub

Private Function ToHexString(ByRef bin() As Byte) As String
    Dim length As Long
    length = UBound(bin) + 1
    
    Dim result As String, n As Long
    result = String(length * 3, "*")
    For n = 0 To length - 1
        Mid(result, n * 3 + 1, 3) = "," & Right(Hex(bin(n) + &H100), 2)
    Next
    ToHexString = Mid(result, 2)
End Function

Private Sub DumpBStr(ByVal s As String)
' VarPtr(s)が指す場所
' ↓
' ┏━━┓
' ┃参照┃
' ┗━━┛
'  参照 => StrPtr(s) と等しい
'
'
'    StrPtr(s)が指す場所
'    ↓
' ┏━━┳━━━━━┯━━┓
' ┃長さ┃文字列本体│終端┃… BSTR データ
' ┗━━┻━━━━━┷━━┛
'  長さ => 2 バイト整数(文字列本体の長さ,終端部含まず)
'  文字列本体 => 0 バイト以上の可変長バイナリ(UTF-16以外でも構わない)
'  終端 => 2 バイト長のゼロ (vbNullChar)
#If Win64 Then
    Dim pVar As LongPtr, pStr As LongPtr, p As LongPtr
#Else
    Dim pVar As OLE_HANDLE, pStr As OLE_HANDLE, p As OLE_HANDLE
#End If
    Dim length As Long
    Dim b0 As Byte, b1 As Byte
    Dim bin() As Byte

    pVar = VarPtr(s)
    pStr = StrPtr(s)
    If pStr = 0 Then
        Debug.Print "s = vbNullString  の場合"
    Else
        Debug.Print "s = """ & s & """  の場合"
    End If
    Debug.Print "VarPtr(s) が指すアドレス値 = &H"; Hex(pVar)
    Debug.Print "StrPtr(s) が指すアドレス値 = &H"; Hex(pStr)
    Debug.Print "Len(s)     で得られる文字数 = "; Len(s); "文字"
    Debug.Print "LenB(s)    で得られるサイズ = "; LenB(s); "バイト"
    If pStr = 0 Then
        Debug.Print
        Exit Sub
    End If
    RtlMoveMemory VarPtr(p), pVar, 4
    Debug.Print "VarPtr(s) にて参照された値 = &H"; Hex(p), "… StrPtr(s) の値に等しい"
    Debug.Print "StrPtr(p)-4が指すアドレス値 = &H"; Hex(pStr - 4)
    RtlMoveMemory VarPtr(length), pStr - 4, 4
    Debug.Print "StrPtr(p)-4にて参照された値 = "; length; "バイト", "… LenB(s) の値に等しい"
    RtlMoveMemory VarPtr(b0), StrPtr(s) + 0, 1
    RtlMoveMemory VarPtr(b1), StrPtr(s) + 1, 1
    Debug.Print "StrPtr(p)+0にて参照された値 = &H"; Hex(b0)
    Debug.Print "StrPtr(p)+1にて参照された値 = &H"; Hex(b1)
    bin = s
    Debug.Print "文字列変数sの内部バイナリ値 = {"; ToHexString(bin); "}", "… 下記と等しい"
    If length > 0 Then
        ReDim bin(0 To length - 1) As Byte
        RtlMoveMemory VarPtr(bin(0)), StrPtr(s), length
    Else
        bin = ""
    End If
    Debug.Print "StrPtr(p)  にて参照された値 = {"; ToHexString(bin); "}"
    ReDim bin(0 To 1)
    RtlMoveMemory VarPtr(bin(0)), StrPtr(s) + length, 2
    Debug.Print "文字列部の後続にある2バイト = {"; ToHexString(bin); "}", "… 常に 00,00"
    
    Debug.Print
End Sub

引用返信 編集キー/
■86073 / inTopicNo.14)  Re[8]: 2次元配列のメモリ配置
□投稿者/ masa (7回)-(2017/12/15(Fri) 17:10:09)
2017/12/15(Fri) 17:30:19 編集(投稿者)

No86070 (とっちゃん さん) に返信
返信ありがとうございます。

>>高級言語ではメモリに関してほとんどの場合、意識することなく
高級言語という言葉は語弊がありましたね。
CやC++に対してVB.NETやC#という意味でした。

>ところで、配列(というか、要素指定でアクセス可能な変数)の物理メモリ上の
>並びが重要なのではないですよね?
自分の中のメモリイメージはNo86061で書いた順に連続した領域に配置されているという
イメージだったのです。でも実際はとっちゃんさんがNo86036が言及されているように
>単純なメモリイメージではなく、付加的な情報が多数ついたメモリ上のオブジェクトになります。
ですよね。この質問をするまでSAFEARRAYが構造体であるということすら知りませんでした。
やはりCやC++のデータ型について勉強する必要があるようです。



引用返信 編集キー/
■86074 / inTopicNo.15)  Re[9]: 2次元配列のメモリ配置
□投稿者/ masa (8回)-(2017/12/15(Fri) 17:17:15)
2017/12/15(Fri) 17:19:10 編集(投稿者)

No86072 (魔界の仮面弁士 さん) に返信
返信ありがとうございます。
VarPtr/StrPtr/ObjPtrについては理解しているつもりです。
CのSAFEARRAYについては上でも書いたようにこのスレッドで初めて知りました。

今、外出先から帰ってきたばかりでVBA上で1回試しただけですけど色々な情報が
イミディエイトウインドウに出力されました。
ちょっと腰を据えてじっくり研究させてください。
引用返信 編集キー/
■86075 / inTopicNo.16)  Re[9]: 2次元配列のメモリ配置
□投稿者/ 魔界の仮面弁士 (1509回)-(2017/12/15(Fri) 20:01:41)
No86074 (masa さん) に返信
> VBA上で1回試しただけですけど色々な情報が
> イミディエイトウインドウに出力されました。
> ちょっと腰を据えてじっくり研究させてください。

VB6 や VBA のヘルプでは、『データ型の概要』のところで下記のように説明されています。

【データ型】
 文字列型 (String) (可変長)
【記憶領域のサイズ】
  10 バイト + 文字列の長さ


この場合の『10 バイト』領域内訳は、 No86072 で言う所の
> ' ┏━━┓
> ' ┃参照┃
> ' ┗━━┛
> ' ┏━━┳━━━━━┯━━┓
> ' ┃長さ┃文字列本体│終端┃
> ' ┗━━┻━━━━━┷━━┛
参照 => 4 バイト
長さ => 4 バイト
終端 => 2 バイト

に当たります。


■No86074 (masa さん) に返信
> この質問をするまでSAFEARRAYが構造体であるということすら知りませんでした。

SAFEARRAY 構造体のメモリ配置はこんな感じ。
http://bytecomb.com/vba-internals-array-variables-and-pointers-in-depth/
https://www26.atwiki.jp/ipubluedictionary/pages/105.html



あとは VARIANT 構造体ですね。
これは型情報を表す vt フィールド(2バイト)と、
そのデータ部を表す共用体フィールドの、計 2 フィールドで
構成される 16 バイトのデータ型です。

メモリ配置はこんな感じ。
最初の角括弧は、構造体アドレスのオフセットです。


[00-01] 2 バイト / vt         … データ型を表す。VBA で言う所の VarType(v) と同じ。
[02-03] 2 バイト / wReserved1 … 予約領域
[03-04] 2 バイト / wReserved2 … 予約領域
[05-06] 2 バイト / wReserved3 … 予約領域

ここまでで 8 バイト。
この後に、実データを格納するための 8 バイトの共用体があり、
数値型の場合は、実体がそのまま格納されています。
https://msdn.microsoft.com/en-us/library/windows/desktop/ms221170%28vs.85%29.aspx
https://msdn.microsoft.com/en-us/vba/language-reference-vba/articles/vartype-constants

[07-0E] 8 バイト / llVal     … vt が VT_I8       (=vbLongLong)   の場合
[07-0A] 4 バイト / lVal      … vt が VT_I4       (=vbLong)       の場合
[07-07] 1 バイト / bVal      … vt が VT_UI1      (=vbByte)       の場合
[07-08] 2 バイト / iVal      … vt が VT_I2       (=vbInteger)    の場合
[07-0A] 4 バイト / fltVal    … vt が VT_R4       (=vbSingle)     の場合
[07-0E] 8 バイト / dblVal    … vt が VT_R8       (=vbDouble)     の場合
[07-08] 2 バイト / boolVal   … vt が VT_BOOL     (=vbBoolean)    の場合
[07-0A] 4 バイト / scode     … vt が VT_ERROR    (=vbError)      の場合
[07-0E] 8 バイト / cyVal     … vt が VT_CY       (=vbCurrency)   の場合
[07-0E] 8 バイト / date      … vt が VT_DATE     (=vbDate)       の場合


vt が VT_DECIMAL (=vbDecimal) の場合、VBA で言うところの 10 進型 (Decimal) では、
予約領域の 6 バイト部分も使うことになるので、上記とは異なるレイアウトを取ります。
https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms221061%28vs.85%29.aspx

[00-01] USHORT wReserved    … 予約領域(vt 相当)
[02-02] BYTE scale          … 小数点位置(0〜28)
[03-03] BYTE sign           … 符号フラグ
[02-03] USHORT signscale    … sign と scale
[04-07] ULONG Hi32          … 96bit整数の上位32bit
[08-0B] ULONG Lo32          … 96bit整数の下位32bit(Macでは Mid32)
[0D-0E] ULONG Mid32         … 96bit整数の中位32bit(Macでは Lo32)
[08-0E] ULONGLONG Lo64      … 96bit整数の下位64bit


vt が参照型を示している場合には、データ部にはポインタ情報が格納され、
実データは、そのポインタによって示されて別領域に配置されます。

VT_BSTR (=vbString)
 → BSTR 文字列。生成は SysAllocString API、解放は SysFreeString API など。

VT_UNKNOWN  (=vbDataObject)
VT_DISPATCH (=vbObject)
 → インターフェイスポインタ。解放は IUnknown.Release メソッド。

VT_BYREF | 他の型
 → ポインタで格納。実体は呼び出し元で管理される。

VT_ARRAY | 他の型 (=vbArray Or 他の型)
 → SAFEARRAY 型として格納。生成は SafeArrayCreate API、解放は SafeArrayDestroy API など。

引用返信 編集キー/
■86093 / inTopicNo.17)  Re[10]: 2次元配列のメモリ配置
□投稿者/ masa (9回)-(2017/12/18(Mon) 11:09:54)
No86075 (魔界の仮面弁士 さん) に返信
魔界の仮面弁士様、ありがとうございます。

No86075の解説のおかげでVBA上でVARIANT構造体、及びSAFEARRAY構造体
を取得して内部データーを取り出すことが出来ました。
VT_DECIMALの96bit部分が負数を返却する場合があり、符号無Longに変換するのに
はまってしまいましたが、無事取得できました。
どこかの引用ではなく御自身で上の解説を書かれたのですよね?すばらしいですね。
どうもありがとうございました。

揚げ足取りのようですが、大事な部分だと思うので確認させて下さい。
以下のように思うのですが如何でしょうか?
VARIANT構造体
[03-04] 2 バイト / wReserved2 … 予約領域==>[04-05]
[05-06] 2 バイト / wReserved3 … 予約領域==>[06-07]
残りの8バイト部分も=============>[08-0F]
VT_DECIMAL
[0D-0E] ULONG Mid32 ============>[0C-0F]
[08-0E] ULONGLONG Lo64===========>[08-0F]

引用返信 編集キー/
■86096 / inTopicNo.18)  Re[11]: 2次元配列のメモリ配置
□投稿者/ 魔界の仮面弁士 (1510回)-(2017/12/18(Mon) 15:02:50)
No86093 (masa さん) に返信
> どこかの引用ではなく御自身で上の解説を書かれたのですよね?

はい。ヘッダーファイル等(OAIdl.h など)を見ながら、ブラウザ上に
書き連ねただけなので、十分に推敲しきれていない点がありますがご容赦を。

なお、COM インターフェイス(IUnknown)のメモリ構造については調査未完了です。


> 以下のように思うのですが如何でしょうか?

あちゃ。全部ずれてますね。
先の BSTR の時と違って、検証実験していない手抜き回答だったりします。


> VARIANT構造体
> [03-04] 2 バイト / wReserved2 … 予約領域==>[04-05]
> [05-06] 2 バイト / wReserved3 … 予約領域==>[06-07]
> 残りの8バイト部分も=============>[08-0F]

失礼しました。
その通り、最初の 8 バイトなので、vt と予約領域は 00-07 の範囲、
それ以降の 8 バイトはデータは 08〜0F です。

# なんで、末尾が 0E で終わっているという時点で気がつけなかったのだろう。orz


> VT_DECIMAL
> [0D-0E] ULONG Mid32 ============>[0C-0F]
> [08-0E] ULONGLONG Lo64===========>[08-0F]

先の図を流用したので、間違いがそのまま引き継がれていますね。

改めて書き直すと、Macintosh 環境が
 [00-01] USHORT wReserved
 [02-02] BYTE scale
 [03-03] BYTE sign
 [02-03] USHORT signscale
 [04-07] ULONG Hi32
 [08-0B] ULONG Mid32
 [0C-0F] ULONG Lo32
 [08-0F] ULONGLONG Lo64
で、それ以外(Windows 環境)が、
 [00-01] USHORT wReserved
 [02-02] BYTE scale
 [03-03] BYTE sign
 [02-03] USHORT signscale
 [04-07] ULONG Hi32
 [08-0B] ULONG Lo32
 [0C-0F] ULONG Mid32
 [08-0F] ULONGLONG Lo64
のはずです。情報ソースは oledb.h の DECIAMAL 構造体。

また、WTypes.h にも DECIAMAL 構造体が定義されていました。
こちらもメモリ配置は上記と同じでしたが、WTypes.h では
共用体メンバーである signscale と Lo64 が定義されていないようです。


あとは、VB6 以降で可能になった「ユーザー定義型(UDT)を格納した Variant 型」を表す、
vt = 36 の vbUserDefinedType タイプ (VT_RECORD) とかですかね。

この場合は、VARIANT 構造体のデータ部(オフセット0x08 以降)が
 _PVOID pvRecord;     // a reference to a database record.
 IRecordInfo *pRecInfo;  // a reference to a User-Defined-Type
という 2 フィールドを持つ構造体になっていました。(OAIdl.h)
引用返信 編集キー/
■86098 / inTopicNo.19)  Re[12]: 2次元配列のメモリ配置
□投稿者/ masa (11回)-(2017/12/18(Mon) 15:42:10)
No86096 (魔界の仮面弁士 さん) に返信
上記、了解しました。
どうもありがとうございました。

PS
VARIANT構造体について新規に質問を上げていますので
よろしければ、又お付き合いください。
解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ