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

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

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

Re[29]: 【VB2005、VB.NET】構造体のコピーについて [1]


(過去ログ 109 を表示中)

[トピック内 49 記事 (21 - 40 表示)]  << 0 | 1 | 2 >>

■64984 / inTopicNo.21)  Re[15]: 【VB2005、VB.NET】構造体のコピーについて
  
□投稿者/ コンバート後に悩む人 (10回)-(2013/01/28(Mon) 15:37:45)
No64981 (shu さん) に返信
> Class1の部分は1個でよくClassA, ClassBの部分だけなので15個作るだけですみます。

返信ありがとうございます。
早速、ClassC、ClassDを作成して、試してみました。

Public Class ClassC
Inherits Class1

Public Sub New()
MyBase.New(2, 3)
End Sub

Public Property aaaa() As String
Get
Return MyBase.Value(0)
End Get
Set(ByVal value As String)
MyBase.Value(0) = value
End Set
End Property

Public Property bbbb() As String
Get
Return MyBase.Value(1)
End Get
Set(ByVal value As String)
MyBase.Value(1) = value
End Set
End Property

End Class

Public Class ClassD
Inherits Class1

Public Sub New()
MyBase.New(2)
End Sub

Public Property aaaa() As String
Get
Return MyBase.Value(0)
End Get
Set(ByVal value As String)
MyBase.Value(0) = value
End Set
End Property

End Class

-------------------

Protected Property InnerValue() As String
Get
Return _InnerValue
End Get
Set(ByVal value As String)
Dim NewValueLength = value.Length
Dim MyValueLength = _InnerValue.Length
Select Case NewValueLength
Case Is < MyValueLength
If Trim(_InnerValue) <> "" Then
_InnerValue = Trim(_InnerValue) & value & New String(" "c, MyValueLength - (Trim(_InnerValue).Length + NewValueLength))
Else
_InnerValue = value & New String(" "c, MyValueLength - NewValueLength)
End If
Case MyValueLength
_InnerValue = value
Case Else
If Trim(_InnerValue) <> "" Then
_InnerValue = Trim(_InnerValue) & value.Substring(0, MyValueLength)
Else
_InnerValue = value.Substring(0, MyValueLength)
End If
End Select
End Set
End Property


---------
Dim clsA As New ClassA
Dim clsB As New ClassB
Dim clsC As New ClassC
Dim clsD As New ClassD

clsA.aaaa = "A1"
clsA.bbbb = "テスト"
clsA.cccc = "あああああ"
clsA.dddd = "1234567"
clsC.aaaa = "C1"
clsC.bbbb = "いいい"
clsD.aaaa = "D1"

clsA.Copy(clsB)
clsC.Copy(clsB)
clsD.Copy(clsB)

InnerValueの一部を上記のように変更したところ
ClassA、ClassC、ClassDの内容をClassBにすべてコピーされるようにできました。
ただし、判定方法が「Trim(_InnerValue) <> ""」としたのですが、
なんとなくこれだと不安が残りますよね。
一応、うまくはいきましたが。

clsA.Copy(clsB)で、Elseのほうにうまく分岐し、clsC.Copy(clsB)以降のコピー時は
すでに文字列があると見なしてくれるため成功してる感じです。

もっと良い方法あれば良いのですが、どうでしょうか。



引用返信 編集キー/
■64987 / inTopicNo.22)  Re[15]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (11回)-(2013/01/28(Mon) 16:19:59)
2013/01/28(Mon) 17:32:26 編集(投稿者)

No64983 (魔界の仮面弁士 さん) に返信

丁寧な説明ありがとうございます。
ぼんやりと理解していた部分がだいぶ解消されました。

> 複数の構造体があるなら、メモリバッファの ptr As IntPtr に対して、
>   Marshal.StructureToPtr(コピー元変数, ptr + 0 , False)
>   Marshal.StructureToPtr(コピー元変数, ptr + 12, False)
>   Marshal.StructureToPtr(コピー元変数, ptr + 30, False)
>   コピー先変数 = DirectCast(Marshal.PtrToStructure(ptr + 0, GetType(型)), 型)
> などのように、位置を指定して読み書きできます。
> (書き込み開始位置は、実際のデータ位置に合わせて調整してください)
>
>
> ただし、上記のようなポインタ演算が行えるのは、VB2010 からです。
> VB2008 以下においては、IntPtr 型に対して
>  ptr += 12
> のような直接演算を行うことができないため、
>  ptr = New IntPtr(ptr.ToInt64() + 12)
> あるいは、
>  ptr = CLng(ptr) + 12
> などのように、一度 Int32 か Int64 に変換してから IntPtr に戻します。

上記の部分についてですが、
実装を以下のようにしてみました。
上記の「ptr = New IntPtr(ptr.ToInt64() + 12)」の12の部分、a1の例でいくと
a1Len = a1.aaaa.Length + a1.bbbb.Length + a1.cccc.Length + a1.dddd.Length
かと思ったのですが、
Marshal.StructureToPtr(c1, pBin, False)で
「埋め込まれた配列インスタンスがレイアウトで宣言された長さと一致しない」エラーが出てしまいました。
もう少し理解が足りなかったようです。
a1、c1、d1の順にb1の構造体にコピーしていく場合
Marshal.StructureToPtr(a1, pBin, False)
の直後に
pBin = New IntPtr(pBin.ToInt64() + a1Len)
を行うものと理解していたのですが
誤りだったのでしょうか。
それとも、そもそも足す値が誤っていたのでしょうか。
すみません、ご教示お願いします。

Dim a1 As typAAAA
Dim b1 As typBBBB
Dim c1 As typCCCC
Dim d1 As typdddd
Dim a1Len As Integer
Dim c1Len As Integer

a1.aaaa = "文字"
a1.bbbb = "VB6"
a1.cccc = "[A&Z]"
a1.dddd = "1234567"
c1.aaaa = "11"
c1.bbbb = "22"
d1.aaaa = "あ"

a1Len = Marshal.SizeOf(GetType(typAAAA))
c1Len = Marshal.SizeOf(GetType(typCCCC))

Dim size As Integer = Math.Max( _
Marshal.SizeOf(GetType(typAAAA)), _
Marshal.SizeOf(GetType(typBBBB)))

Dim bin(size - 1) As Byte
With GCHandle.Alloc(bin, GCHandleType.Pinned)

Dim pBin As IntPtr = .AddrOfPinnedObject()
Marshal.StructureToPtr(a1, pBin, False)

pBin = New IntPtr(pBin.ToInt64() + a1Len)
Marshal.StructureToPtr(c1, pBin, False)

pBin = New IntPtr(pBin.ToInt64() + c1Len)
Marshal.StructureToPtr(d1, pBin, False)

b1 = DirectCast(Marshal.PtrToStructure(pBin, GetType(typBBBB)), typBBBB)
.Free()

End With

引用返信 編集キー/
■64988 / inTopicNo.23)  Re[16]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ shu (152回)-(2013/01/28(Mon) 16:34:40)
No64984 (コンバート後に悩む人 さん) に返信
>> InnerValueの一部を上記のように変更したところ
> ClassA、ClassC、ClassDの内容をClassBにすべてコピーされるようにできました。
> ただし、判定方法が「Trim(_InnerValue) <> ""」としたのですが、
> なんとなくこれだと不安が残りますよね。
> 一応、うまくはいきましたが。
>
> clsA.Copy(clsB)で、Elseのほうにうまく分岐し、clsC.Copy(clsB)以降のコピー時は
> すでに文字列があると見なしてくれるため成功してる感じです。
>
> もっと良い方法あれば良いのですが、どうでしょうか。
想定されているCopyの結果はclsBへの文字列連結でしょうか?
提示したコードの想定ではCopyは置き換えになります。
文字列連結であればそのようなメソッドを用意されるとよいと思います。有効な文字数を管理する
情報が必要になりそうです。後ろの空白は無視してもよいということであればコンバート後に悩む人 さんの
なおした内容でよいと思いますが別メソッドの方が良いと思います。
引用返信 編集キー/
■64989 / inTopicNo.24)  Re[17]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (12回)-(2013/01/28(Mon) 16:40:08)
No64988 (shu さん) に返信
> 想定されているCopyの結果はclsBへの文字列連結でしょうか?

はい、そのとおりです。
もう1つの魔界の仮面弁士さんの手法のほうが落ち着きましたら
改めて整理いたします。

> 提示したコードの想定ではCopyは置き換えになります。
> 文字列連結であればそのようなメソッドを用意されるとよいと思います。有効な文字数を管理する
> 情報が必要になりそうです。後ろの空白は無視してもよいということであればコンバート後に悩む人 さんの
> なおした内容でよいと思いますが別メソッドの方が良いと思います。

後ろの空白は無視して良いので、前述のとおりのコーディングとします。
また、有効な文字数の管理する情報が必要かどうかも、実際のコーディング時に検討したいと思います。
このたびはご協力いただきまして、ありがとうございました。
すべて解決後改めてお礼いたします。

引用返信 編集キー/
■64993 / inTopicNo.25)  Re[16]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ 魔界の仮面弁士 (135回)-(2013/01/28(Mon) 17:48:32)
No64987 (コンバート後に悩む人 さん) に返信
> 上記の「ptr = New IntPtr(ptr.ToInt64() + 12)」の12の部分、a1の例でいくと
> a1Len = a1.aaaa.Length + a1.bbbb.Length + a1.cccc.Length + a1.dddd.Length
> かと思ったのですが、

上記で求めた a1Len が、Marshal.SizeOf(a1) と一致しているか確認しておいてください。


そもそも、各フィールドの幅は、「文字数」で指定したいのでしょうか。
それとも「バイト数」で指定したいのでしょうか。

No64971 の前半部のサンプルのように、「文字数」で管理するのであれば、
 「<MarshalAs(UnmanagedType.ByValArray, SizeConst:=2)> Public aaaa() As Char」
とすれば、2 文字分(4 バイト)のフィールドになります。

一方、Shift_JIS 相当として扱いたいなら、
 「<MarshalAs(UnmanagedType.ByValArray, SizeConst:=2)> Public aaaa() As Byte」
としておいて、Encoding クラスで Byte 配列化したデータを取り扱う事になるでしょう。



> Marshal.StructureToPtr(c1, pBin, False)で
> 「埋め込まれた配列インスタンスがレイアウトで宣言された長さと一致しない」エラーが出てしまいました。
各フィールドに書き込まれたデータ長が、属性で指定している長さと一致していないのでしょう。

No64971 の前半部のサンプルで言えば、下記はいずれも 34 を返したはずです。
 Dim x As Integer = Marshal.SizeOf(GetType(typAAAA))
 Dim y As Integer = Marshal.SizeOf(a1)
 Dim z As Integer = 2 * (a1.aaaa.Length + a1.bbbb.Length + a1.cccc.Length + a1.dddd.Length)

データが Unicode のため、最後の z が文字数の倍になっていることに注意してください。
VB6 と同様、各文字は 2 バイト単位で換算されるためです。



もし、Shift_JIS 換算でデータを処理したいのであれば、
データを As Byte() 型で扱ってください。この場合、VB6 の
StrConv での vbFromUnicode / vbUnicode と同様に、
Encoding クラスでの変換作業を伴うことになります。


また、As String にしろ As Char() にしろ As Byte() にしろ、
そのデータ長は可変でありことに注意してください。
VB6 の固定長のように、不足分を補ったり、過剰分を切り捨てて
格納するといった芸当は行ってくれませんので、コピー処理の前に、
各フィールドには、ぴったり一致するサイズのデータを
セットしておかないと、コピー動作は失敗します。
引用返信 編集キー/
■64994 / inTopicNo.26)  Re[17]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (13回)-(2013/01/28(Mon) 18:17:03)
No64993 (魔界の仮面弁士 さん) に返信
> No64971 の前半部のサンプルで言えば、下記はいずれも 34 を返したはずです。
>  Dim x As Integer = Marshal.SizeOf(GetType(typAAAA))
>  Dim y As Integer = Marshal.SizeOf(a1)
>  Dim z As Integer = 2 * (a1.aaaa.Length + a1.bbbb.Length + a1.cccc.Length + a1.dddd.Length)
>
> データが Unicode のため、最後の z が文字数の倍になっていることに注意してください。
> VB6 と同様、各文字は 2 バイト単位で換算されるためです。

投降したあとに誤りに気付き、
Marshal.SizeOf(typAAAA)に修正しました。
軽率に投稿してすみませんでした。


> もし、Shift_JIS 換算でデータを処理したいのであれば、
> データを As Byte() 型で扱ってください。この場合、VB6 の
> StrConv での vbFromUnicode / vbUnicode と同様に、
> Encoding クラスでの変換作業を伴うことになります。
>
> また、As String にしろ As Char() にしろ As Byte() にしろ、
> そのデータ長は可変でありことに注意してください。
> VB6 の固定長のように、不足分を補ったり、過剰分を切り捨てて
> 格納するといった芸当は行ってくれませんので、コピー処理の前に、
> 各フィールドには、ぴったり一致するサイズのデータを
> セットしておかないと、コピー動作は失敗します。

代入するべき値をぴったり一致するサイズでなかったため
エラーとなっていたようです。
一致するようにデータを整えたら、正常に動作しました。
お騒がせしました。


今回の実現したいことの中に、
> VB6 の固定長のように、不足分を補ったり、過剰分を切り捨てて
> 格納するといった芸当は行ってくれませんので、
が漏れていましたので
もし、この手法でやるのであれば、過剰になることはないですが
不足することはあると思われるので
たとえば、3バイトの「a1.bbbb」に対して2バイトしか入らない場合は
スペース等で補完する作業があると感じました。
#元々のベースとなっているVB6のソースがそんな感じでした。


また、ぴったり一致するサイズのデータで動作させたところ
最後の構造体で上書きされてb1に設定されるようになりましたが
a1+c1+d1=b1とする場合、この方法では実現できないのでしょうか?


引用返信 編集キー/
■64996 / inTopicNo.27)  Re[18]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ shu (155回)-(2013/01/29(Tue) 08:15:38)
2013/01/29(Tue) 11:21:04 編集(投稿者)

No64989 (コンバート後に悩む人 さん) に返信

魔界の仮面弁士さんが何度か話題に出していますが
文字数固定なのかバイト数固定なのかというところが今回の
実装には重要だと思うのですがどうなのでしょう?



==> 以下の内容は正しくないので無視して下さい。 ここから

バイト数固定であれば先の私の提示したプログラムをByte配列等を内部データとして保持するようにして
それにあわせた変更をして頂くと実装出来ると思います。手間なのは複数byte文字が固定長内から1byte分
入らないときです。Shift-JISなら固定長で切ったときの後ろ2Byteが(&H80未満,&H80以上)の組になっていれば
後ろの1Byteは無効となりますので空白で置き換えるとよいと思います。

<== ここまで

#魔界の仮面弁士さんの指摘部分を無効扱いにしました。
引用返信 編集キー/
■64997 / inTopicNo.28)  Re[19]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ 魔界の仮面弁士 (136回)-(2013/01/29(Tue) 09:52:26)
No64996 (shu さん) に返信
> Shift-JISなら固定長で切ったときの後ろ2Byteが(&H80未満,&H80以上)の組になっていれば
> 後ろの1Byteは無効となりますので空白で置き換えるとよいと思います。

それだけでは判定条件として網羅できませんので、Shift_JIS の場合は
基本的に文字列の先頭から順に判断していくべきかと思います。

確かに、データの途中からでも「分断されている」と判定できる場合もありますが、
「先導バイトにも後続バイトにもなりうる組合せなので判断できない」場合が
多々存在するため、結局、先頭まで辿らないと確実には判定できません。たとえば
 8181 … =
 8182 … ≠
 8281 … a
 8282 … b
の 4 種の文字だけが延々と並んでいるデータの場合、途中のバイナリを
見ただけでは、分断位置が先導バイトか後続バイトかを見分けることができません。



> 手間なのは複数byte文字が固定長内から1byte分入らないときです。

自前で判定もできますが、Encoding クラスを使った方が安全かと思います。

Encoding.GetEncoding メソッドの引数で、第2〜第3引数を明示すれば、
分断データに対して、『別のデータ(空白など)に置き換える』か
『破損箇所を報告させる』かといったモード指定が可能です。

ここには、Encoder 系(文字列をバイナリに変換する処理)と、
Decoder 系(バイナリを文字列に復元する処理)をそれぞれ指定できます。
http://msdn.microsoft.com/ja-jp/library/system.text.decoderfallback%28vs.80%29.aspx
引用返信 編集キー/
■64999 / inTopicNo.29)  Re[20]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ shu (156回)-(2013/01/29(Tue) 11:19:13)
No64997 (魔界の仮面弁士 さん) に返信
> ■No64996 (shu さん) に返信
>>Shift-JISなら固定長で切ったときの後ろ2Byteが(&H80未満,&H80以上)の組になっていれば
>>後ろの1Byteは無効となりますので空白で置き換えるとよいと思います。
>
> それだけでは判定条件として網羅できませんので、Shift_JIS の場合は
> 基本的に文字列の先頭から順に判断していくべきかと思います。
>
> 確かに、データの途中からでも「分断されている」と判定できる場合もありますが、
> 「先導バイトにも後続バイトにもなりうる組合せなので判断できない」場合が
> 多々存在するため、結局、先頭まで辿らないと確実には判定できません。たとえば
>  8181 … =
>  8182 … ≠
>  8281 … a
>  8282 … b
> の 4 種の文字だけが延々と並んでいるデータの場合、途中のバイナリを
> 見ただけでは、分断位置が先導バイトか後続バイトかを見分けることができません。
確かに条件が足りませんでした。やはり全部スキャンしないとこの方法では駄目ですね。


>
>>手間なのは複数byte文字が固定長内から1byte分入らないときです。
>
> 自前で判定もできますが、Encoding クラスを使った方が安全かと思います。
>
> Encoding.GetEncoding メソッドの引数で、第2〜第3引数を明示すれば、
> 分断データに対して、『別のデータ(空白など)に置き換える』か
> 『破損箇所を報告させる』かといったモード指定が可能です。
>
> ここには、Encoder 系(文字列をバイナリに変換する処理)と、
> Decoder 系(バイナリを文字列に復元する処理)をそれぞれ指定できます。
> http://msdn.microsoft.com/ja-jp/library/system.text.decoderfallback%28vs.80%29.aspx

EncodingでGetString=>GetBytesで一度変換後戻した方がよさそうですね。



No64989 (コンバート後に悩む人 さん) に返信
連結分断時の処理ですが
連結時は元が固定長内に収まっているのであればそのまま連結でも大丈夫ですね。
分断時は2バイト文字を分断時各フィールドの前後に空白を入れるのか前のフィールドも後ろのフィールドも左詰めに
するのかも考慮する必要がありそうです。(このような分断処理が発生することに意味があるのかという疑問はあります)
引用返信 編集キー/
■65006 / inTopicNo.30)  Re[21]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (14回)-(2013/01/29(Tue) 14:38:46)
No64996 (shu さん) に返信
> 魔界の仮面弁士さんが何度か話題に出していますが
> 文字数固定なのかバイト数固定なのかというところが今回の
> 実装には重要だと思うのですがどうなのでしょう?


VB6.0のコンバートからの話題ですので
例)dim aaa as String * 2
であり、固定文字列がいくつか定義してある構造体を
コンバートしたものなので文字数固定と思っていました。
各固定文字列は、半角全角混在であったりします。
フェーズ的には動作テストの段階ではないので、半角全角混在した時の動作が問題ないかとどうか
改めてそのときに検証したいと思っています。

今回の仕様としては
@1つのサイズの大きい固定文字列が定義されている構造体(A1)から複数の構造体(B1,B2,B3…Bn)にコピーする手法
A複数の構造体(B1,B2,B3…Bn)から1つのサイズの大きい固定文字列が定義されている構造体(A1)にコピーする手法
※複数の構造体には、複数の固定文字列が定義されています。


Marshal.StructureToPtr、Marshal.PtrToStructureを使った手法で改めて試行錯誤したところ
@の場合、1つ前の構造体に格納した内容を取得し(Bn-1)、今回構造体に格納した文字のみを予め保持(Bn)しておき
文字結合する手法で行いました。(Bn-1 + Bn → Bn:1回Bnを退避するワークを用意)
この操作を複数の構造体分行うことで、B1からBnまでの構造体の内容をA1構造体に格納することに成功しました。
Aの場合は、@の逆ですので試してはいませんが、同じようような操作になるものだと踏まえました。

一方、構造体別にClassを定義する場合でも同じように前回構造体の値を保持しておいて
文字結合する手法には変わらないことがわかりました。
#New String(" "c, MyValueLength - NewValueLength)の処理がVB6.0のときの動作と同じような動作をしてくれたのも発見でした。

2つの手法で検討してみたところ、
従来のソースだと1つの標準モジュールに複数の構造体を定義してある程度のソースで
それをコンバートしただけだと、可読性の悪い構造体定義が複数行にわたって長く定義されていることを鑑み、
今回の質問結果を参考にクラスファイルごとに構造体を持つ方向で軌道修正することにしました。
従って、コンバートするとPublic Structure で生成されますがこれを廃止し、
コピーするクラス、構造体ごとのクラスに分けて作成する方向で検討することとします。
VB6.0のLsetをコピーするクラスで代替という感じです。

このたびは、ご協力いただいた方々まことにありがとうございました。
howling さん
shu さん
soy さん
魔界の仮面弁士 さん
(順不同)

引用返信 編集キー/
■65007 / inTopicNo.31)  Re[22]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (15回)-(2013/01/29(Tue) 14:39:58)
■クラス別に構造体を定義した例
@1つのサイズの大きい固定文字列が定義されている構造体(A1)から複数の構造体(B1,B2,B3…Bn)にコピーする手法
A複数の構造体(B1,B2,B3…Bn)から1つのサイズの大きい固定文字列が定義されている構造体(A1)にコピーする手法
※複数の構造体には、複数の固定文字列が定義されています。

Public Class ClassA
    Inherits Class1
    Public MAXLENGTH As Integer = 17

    Public Sub New()
        MyBase.New(2, 3, 5, 7)
    End Sub

    Public Property aaaa() As String
        Get
            Return MyBase.Value(0)
        End Get
        Set(ByVal value As String)
            MyBase.Value(0) = value
        End Set
    End Property

    Public Property bbbb() As String
        Get
            Return MyBase.Value(1)
        End Get
        Set(ByVal value As String)
            MyBase.Value(1) = value
        End Set
    End Property

    Public Property cccc() As String
        Get
            Return MyBase.Value(2)
        End Get
        Set(ByVal value As String)
            MyBase.Value(2) = value
        End Set
    End Property

    Public Property dddd() As String
        Get
            Return MyBase.Value(3)
        End Get
        Set(ByVal value As String)
            MyBase.Value(3) = value
        End Set
    End Property

End Class

Public Class ClassB
    Inherits Class1

    Public Sub New()
        MyBase.New(100)
    End Sub

    Public Property strB() As String
        Get
            Return MyBase.Value(0)
        End Get
        Set(ByVal value As String)
            MyBase.Value(0) = value
        End Set
    End Property

End Class

Public Class ClassC
    Inherits Class1
    Public MAXLENGTH As Integer = 5

    Public Sub New()
        MyBase.New(2, 3)
    End Sub

    Public Property aaaa() As String
        Get
            Return MyBase.Value(0)
        End Get
        Set(ByVal value As String)
            MyBase.Value(0) = value
        End Set
    End Property

    Public Property bbbb() As String
        Get
            Return MyBase.Value(1)
        End Get
        Set(ByVal value As String)
            MyBase.Value(1) = value
        End Set
    End Property

End Class

Public Class ClassD
    Inherits Class1
    Public MAXLENGTH As Integer = 2

    Public Sub New()
        MyBase.New(2)
    End Sub

    Public Property aaaa() As String
        Get
            Return MyBase.Value(0)
        End Get
        Set(ByVal value As String)
            MyBase.Value(0) = value
        End Set
    End Property

End Class

Public Class Class1
    Private _InnerValue As String
    Private _FieldStarts() As Integer
    Private _FieldLengths() As Integer

    Public Sub New(ByVal ParamArray FieldLengthS() As Integer)
        Dim TotalSize As Integer = 0
        Dim FieldLength As Integer
        Dim Pos As Integer = 0
        Dim Idx As Integer = 0

        _FieldStarts = New Integer(FieldLengthS.Length) {}
        _FieldStarts(Idx) = Pos
        For Each FieldLength In FieldLengthS
            TotalSize += FieldLength
            Idx += 1
            Pos += FieldLength
            _FieldStarts(Idx) = Pos
        Next
        _FieldLengths = FieldLengthS
        _InnerValue = New String(" "c, TotalSize)
    End Sub

    Protected Property InnerValue(ByVal start As Integer) As String
        Get
            Return _InnerValue
        End Get
        Set(ByVal value As String)
            Dim NewValueLength = value.Length
            Dim MyValueLength = _InnerValue.Length
            Select Case NewValueLength
                Case Is < MyValueLength
                    If Trim(_InnerValue) <> "" Then
                        _InnerValue = _InnerValue.Substring(0, start) & value & New String(" "c, MyValueLength - (Trim(_InnerValue).Length + NewValueLength))
                    Else
                        _InnerValue = value & New String(" "c, MyValueLength - NewValueLength)
                    End If
                Case MyValueLength
                    _InnerValue = value
                Case Else
                    If Trim(_InnerValue) <> "" Then
                        _InnerValue = Trim(_InnerValue) & value.Substring(start, MyValueLength)
                    Else
                        _InnerValue = value.Substring(start, MyValueLength)
                    End If
            End Select
        End Set
    End Property

    Public Property Value(ByVal Index As Integer) As String
        Get
            Dim FieldStart As Integer = _FieldStarts(Index)
            Dim FieldLength As Integer = _FieldLengths(Index)

            Return _InnerValue.Substring(FieldStart, FieldLength)
        End Get
        Set(ByVal value As String)
            Dim FieldStart As Integer = _FieldStarts(Index)
            Dim FieldLength As Integer = _FieldLengths(Index)

            Dim NewValue As New System.Text.StringBuilder
            '設定する部分の前
            If FieldStart > 1 Then
                NewValue.Append(_InnerValue.Substring(0, FieldStart))
            End If
            '設定する文字列
            Select Case value.Length
                Case Is < FieldLength
                    NewValue.Append(value.PadRight(FieldLength, " "c))
                Case FieldLength
                    NewValue.Append(value)
                Case Else
                    NewValue.Append(value.Substring(0, FieldLength))
            End Select
            '設定する部分の後ろ
            If _InnerValue.Length > FieldStart + FieldLength + 1 Then
                NewValue.Append(_InnerValue.Substring(FieldStart + FieldLength))
            End If
            _InnerValue = NewValue.ToString
        End Set
    End Property

    Public Sub Copy(ByVal Dest As Class1, Optional ByVal start As Integer = 0)
        Dest.InnerValue(start) = Me.InnerValue(start)
    End Sub

End Class


    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim clsA As New ClassA
        Dim clsB As New ClassB
        Dim clsC As New ClassC
        Dim clsD As New ClassD

        '各構造体の内容をメインとなる構造体にコピー
        clsA.aaaa = "1"
        clsA.bbbb = "a1"
        clsA.cccc = "a123"
        clsA.dddd = "ああいう"
        clsC.aaaa = "2"
        clsC.bbbb = "c2"
        clsD.aaaa = "3"
        clsA.Copy(clsB)
        clsC.Copy(clsB, clsA.MAXLENGTH)
        clsD.Copy(clsB, clsA.MAXLENGTH + clsC.MAXLENGTH)

        'メインとなる構造体の内容を各構造体にコピー
        clsA = New ClassA
        clsC = New ClassC
        clsD = New ClassD
        clsB.Copy(clsA, 0)
        clsB.Copy(clsC, clsA.MAXLENGTH)
        clsB.Copy(clsD, clsA.MAXLENGTH + clsC.MAXLENGTH)

    End Sub

解決済み
引用返信 編集キー/
■65012 / inTopicNo.32)  Re[22]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ 魔界の仮面弁士 (139回)-(2013/01/29(Tue) 17:35:09)
2013/01/29(Tue) 17:55:16 編集(投稿者)

No65006 (コンバート後に悩む人 さん) に返信
> VB6.0のコンバートからの話題ですので
> 例)dim aaa as String * 2
> であり、固定文字列がいくつか定義してある構造体を
> コンバートしたものなので文字数固定と思っていました。
> 各固定文字列は、半角全角混在であったりします。

VB6 の固定長文字列型は、確かに文字数換算での処理ではありますが、
それは単に、全角半角の混在時を考慮していないコードであったとか、
あるいは半角文字しか来ないという前提として設計されている場合も
ありますので、元の仕様を良く確認しておいたほうが良いでしょうね。

時には、Shift_JIS 相当の全角半角を混在した上で扱うために、
「As String * 2」に StrConv で vbFromUnicode 変換した
「長さ×2」バイト分のバイナリを格納している場合もありますし。



> VB6.0のLsetをコピーするクラスで代替という感じです。
リフレクションで、構造体の各メンバーを FieldInfo クラスとして扱い、
まとめて文字列として扱うサンプルを作ってみました。

ここでは文字数単位で処理していますが、応用すれば、Shift_JIS でのバイナリ幅で
処理させることも可能でしょう。


'VB2005 + Windows 7 環境で動作確認

Imports System.Reflection
Imports System.Text
Imports System.Runtime.InteropServices
Imports System.Runtime.CompilerServices

Public Structure AAA
 <VBFixedString(2)> Public aaa As String
 <VBFixedString(3)> Public bbb As String
 <VBFixedString(5)> Public ccc As String
End Structure

Public Structure BBB
 <VBFixedString(3)> Public zzz As String
 <VBFixedString(5)> Public yyy As String
 <VBFixedString(2)> Public xxx As String
End Structure

Public Structure CCC
 <VBFixedString(2)> Public Field1 As String
 <VBFixedString(3)> Public Field2 As String
 <VBFixedString(5)> Public Field3 As String
 <VBFixedString(3)> Public Field4 As String
 <VBFixedString(5)> Public Field5 As String
 <VBFixedString(2)> Public Field6 As String
End Structure

Module Module1
 Sub Main()
  '文字列から構造体を作れます。
  Dim a1 As AAA = StructureFromText(Of AAA)("XXYYYZZZZZ")
  Console.WriteLine("=== AAA ===")
  Console.WriteLine("aaa=[" & a1.aaa & "]")
  Console.WriteLine("bbb=[" & a1.bbb & "]")
  Console.WriteLine("ccc=[" & a1.ccc & "]")

  '構造体メンバーを繋げて一つの文字列にできます。
  Dim s As String = StructureToString(a1)
  Console.WriteLine("[" & s & "]")

  '文字列を経由することで、別の構造体から取得することもできます。
  Dim b1 As BBB = StructureFromText(Of BBB)(StructureToString(a1))
  Console.WriteLine("zzz=[" & b1.zzz & "]")
  Console.WriteLine("yyy=[" & b1.yyy & "]")
  Console.WriteLine("xxx=[" & b1.xxx & "]")

  '文字列化して再代入すると、長さ調整ができます。
  Dim a2 As AAA
  a2.aaa = "Long Text"
  a2.bbb = "全+半"
  a2.ccc = "短い"
  Console.WriteLine("=== 長さ調整前 ===")
  Console.WriteLine("aaa=[" & a2.aaa & "]")
  Console.WriteLine("bbb=[" & a2.bbb & "]")
  Console.WriteLine("ccc=[" & a2.ccc & "]")
  Console.WriteLine("=== 長さ調整後 ===")
  a2 = StructureFromText(Of AAA)(StructureToString(a2))
  Console.WriteLine("aaa=[" & a2.aaa & "]")
  Console.WriteLine("bbb=[" & a2.bbb & "]")
  Console.WriteLine("ccc=[" & a2.ccc & "]")

  '複数の構造体を結合して、別の構造体に入れることも簡単にできます。
  Dim a3 As AAA
  a3.aaa = "Long Text"
  a3.bbb = "全+半"
  a3.ccc = "短い"
  Dim b3 As BBB = StructureFromText(Of BBB)("zzzYYYYYxx")
  Dim c3 As CCC = StructureFromText(Of CCC)(StructureToString(a3) & StructureToString(b3))
  Console.WriteLine("=== CCC ===")
  Console.WriteLine("Field1=[" & c3.Field1 & "]")
  Console.WriteLine("Field2=[" & c3.Field2 & "]")
  Console.WriteLine("Field3=[" & c3.Field3 & "]")
  Console.WriteLine("Field4=[" & c3.Field4 & "]")
  Console.WriteLine("Field5=[" & c3.Field5 & "]")
  Console.WriteLine("Field6=[" & c3.Field6 & "]")
 End Sub

 'VBFixedString 属性の付いたフィールドを連結し、その文字列を返します。
 Public Function StructureToString(Of T As Structure)(ByVal x As T) As String
  Dim sb As New StringBuilder()
  For Each f As FieldInfo In GetType(T).GetFields()
   Dim o() As Object = f.GetCustomAttributes(GetType(VBFixedStringAttribute), False)
   If o.Length <> 0 Then
    Dim attr As VBFixedStringAttribute = DirectCast(o(0), VBFixedStringAttribute)
    Dim s As String = Strings.Left(CStr(f.GetValue(x)) & StrDup(attr.Length, ChrW(0)), attr.Length)
    sb.Append(s)
   End If
  Next
  Return sb.ToString()
 End Function

 '指定された文字列を、VBFixedString 属性の付いたフィールドに順次格納した値を返します。
 Public Function StructureFromText(Of T As Structure)(ByVal s As String) As T
  'RuntimeHelpers.GetObjectValue 対策の為、ValueType で受ける
  Dim newValue As ValueType = StructureFromText
  Dim pos As Integer = 1
  For Each f As FieldInfo In newValue.GetType().GetFields()
   Dim attributes() As Object = f.GetCustomAttributes(GetType(VBFixedStringAttribute), False)
   If attributes.Length <> 0 Then
    Dim attr As VBFixedStringAttribute = DirectCast(attributes(0), VBFixedStringAttribute)
    Dim value As String = Strings.Mid(s & StrDup(attr.Length, ChrW(0)), pos, attr.Length)
    f.SetValue(newValue, value)
    pos += attr.Length
   End If
  Next
  Return DirectCast(newValue, T)
 End Function
End Module
引用返信 編集キー/
■65021 / inTopicNo.33)  Re[23]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (16回)-(2013/01/30(Wed) 09:18:07)
上記のソース、早速確認させていただきました。
VB6メインでやってきたので、
(Of T As Structure)(ByVal s As String) As T
こういう記述ができるのが知らず、ちょっとこれから勉強です。

以下、実行すると出力された文字列はこのようになりましたが
間違ってないでしょうか?
2か所が気になる感じですが・・・。

ccc=[短い=== CCC ===

Field3=[短いField4=[zzz]



=== AAA ===
aaa=[XX]
bbb=[YYY]
ccc=[ZZZZZ]
[XXYYYZZZZZ]
zzz=[XXY]
yyy=[YYZZZ]
xxx=[ZZ]
=== 長さ調整前 ===
aaa=[Long Text]
bbb=[全+半]
ccc=[短い]
=== 長さ調整後 ===
aaa=[Lo]
bbb=[全+半]
ccc=[短い=== CCC ===
Field1=[Lo]
Field2=[全+半]
Field3=[短いField4=[zzz]
Field5=[YYYYY]
Field6=[xx]

引用返信 編集キー/
■65022 / inTopicNo.34)  Re[24]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ shu (159回)-(2013/01/30(Wed) 09:41:01)
No65021 (コンバート後に悩む人 さん) に返信
> 上記のソース、早速確認させていただきました。
> VB6メインでやってきたので、
> (Of T As Structure)(ByVal s As String) As T
> こういう記述ができるのが知らず、ちょっとこれから勉強です。
>
> 以下、実行すると出力された文字列はこのようになりましたが
> 間違ってないでしょうか?
> 2か所が気になる感じですが・・・。
>
> ccc=[短い=== CCC ===
>
> Field3=[短いField4=[zzz]
>
短い場合にChrW(0)でnullを不足分足しているので
改行も含めnull以降の文字列が表示されていない為ですね。
その動きで正しいかはコンバート後に悩む人 さんが判断すると良いと思います。
引用返信 編集キー/
■65025 / inTopicNo.35)  Re[24]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ 魔界の仮面弁士 (140回)-(2013/01/30(Wed) 10:00:34)
No65021 (コンバート後に悩む人 さん) に返信
> 以下、実行すると出力された文字列はこのようになりましたが
> Field3=[短いField4=[zzz]

WriteLine メソッドで出力しているのに、改行が失われていますよね。
これは、不足した桁位置に ChrW(0) の文字を補っているためです。

そのため、Debug.WriteLine のように、NULL 文字の表示に対応していない
環境に出力すると、後続文字が切り捨てられたかのように見える場合がありますが、
実際には、VBFixedString 属性で指定した桁数分の文字列が格納されています。

「c3.Field3.Length」や「c3.Field3.Replace(ChrW(0), "@"c)」などとすると、
実際には正しく 5 文字分のデータになっていることが確認できるかと思います。



もし、NULL 文字ではなく空白を補いたい場合には、StrDup に渡す文字を2箇所とも、
「ChrW(0)」ではなく「" "c」に変更してみてください。
引用返信 編集キー/
■65027 / inTopicNo.36)  Re[25]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ 魔界の仮面弁士 (141回)-(2013/01/30(Wed) 10:29:37)
2013/01/30(Wed) 10:52:20 編集(投稿者)

No65022 (shu さん) に返信
> 短い場合にChrW(0)でnullを不足分足しているので
> 改行も含めnull以降の文字列が表示されていない為ですね。

shu さん、フォローありがとうございます。m(_ _)m


> その動きで正しいかはコンバート後に悩む人 さんが判断すると良いと思います。

VB6 の固定長文字列型の場合、初期値は NULL パディングですが、
代入時には空白埋めで補完されます。それを踏まえたうえで、
コンバート後に悩む人さんが都合の良い形に補正してください。


以下、No64971 の VB6 コードからの発展です。
Let ステートメントの行によって、不足桁の内容が異なることに注意してください。


Option Explicit

Type typAAAA
 aaaa As String * 2  '2文字(≠2バイト)
 BBBB As String * 3  '3文字(≠3バイト)
 cccc As String * 5  '5文字(≠5バイト)
 dddd As String * 7  '7文字(≠7バイト)
End Type

Type typBBBB
 strB As String * 100
End Type

Sub Main()
 Dim aaaa As typAAAA
 Dim BBBB As typBBBB

 aaaa.aaaa = "あい"   '2文字(≠2バイト)
 aaaa.BBBB = "xYz"   '3文字(≠3バイト)
 aaaa.cccc = "VB6.0"  '5文字(≠5バイト)
 aaaa.dddd = "[13579]" '7文字(≠7バイト)

 '初期値は NULL パディングされているため、
 '不足桁数には、vbNullChar すなわち ChrW(0) が入っている。
 LSet BBBB = aaaa
 Debug.Print Replace(BBBB.strB, vbNullChar, "@")

 '短い文字列を代入した場合、不足桁には空白が補われるため、
 '不足桁数には、" " すなわち ChrW(32) が入っている。
 Let BBBB.strB = ""
 LSet BBBB = aaaa
 Debug.Print Replace(BBBB.strB, vbNullChar, "@")

 '最初に、"=" の文字で埋めているため、
 '不足桁数には、"=" の文字が入っている。
 Let BBBB.strB = String(100, "=")
 LSet BBBB = aaaa
 Debug.Print Replace(BBBB.strB, vbNullChar, "@")
End Sub


なお、上記では説明のため、意図的に Let ステートメントを用いていますが、
Let は通常、省略されるべきキーワードです。
(「Let BBBB.strB = x」は「BBBB.strB = x」と同義です)
引用返信 編集キー/
■65028 / inTopicNo.37)  Re[25]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (17回)-(2013/01/30(Wed) 10:29:44)
No65025 (魔界の仮面弁士 さん) に返信
> そのため、Debug.WriteLine のように、NULL 文字の表示に対応していない
> 環境に出力すると、後続文字が切り捨てられたかのように見える場合がありますが、
> 実際には、VBFixedString 属性で指定した桁数分の文字列が格納されています。
> 「c3.Field3.Length」や「c3.Field3.Replace(ChrW(0), "@"c)」などとすると、
> 実際には正しく 5 文字分のデータになっていることが確認できるかと思います。

上記、確認してみたところ確かにそのとおりでした。
VB.netからこのような動作になったのか、6.0からこの動作だったか
ちょっとうろ覚えですが、勉強不足でした。


> もし、NULL 文字ではなく空白を補いたい場合には、StrDup に渡す文字を2箇所とも、
> 「ChrW(0)」ではなく「" "c」に変更してみてください。

「" "c」のほうが理想どおりの動作となったので
こちらのほうで進めてみます。

さらに以下の大きい構造体を作成して、2つの構造体の内容を結合してみました。

Public Structure DDD
<VBFixedString(1000)> Public Field As String
End Structure

Dim d4 As DDD = StructureFromText(Of DDD)(StructureToString(a3) & StructureToString(b3))

Console.WriteLine("=== DDD ===")
Console.WriteLine("Field=[" & d4.Field & "]")

=== DDD ===
Field=[Lo全+半短い zzzYYYYYxx ]

結果は、理想通りでよかったのですが
これの逆ができませんでした。
StructureFromTextに渡すときに、どこから読み込むのか開始位置をつけたメソッドを別建てで作る感じでしょうか?



引用返信 編集キー/
■65029 / inTopicNo.38)  Re[26]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ 魔界の仮面弁士 (142回)-(2013/01/30(Wed) 10:48:03)
2013/01/30(Wed) 10:53:52 編集(投稿者)

No65028 (コンバート後に悩む人 さん) に返信
> VB.netからこのような動作になったのか、6.0からこの動作だったか
> ちょっとうろ覚えですが、勉強不足でした。

VB6 の場合の動作について No65027 で補足しておきました。参考にしてみてください。


> 結果は、理想通りでよかったのですが
1000文字分の行の書き込みなので、返信画面や図表モード投稿時の表示が大変なことになっている…。(^^;


> StructureFromTextに渡すときに、どこから読み込むのか開始位置をつけたメソッドを別建てで作る感じでしょうか?
今のコードのままであれば、
 s = StructureToString(d4)
 'Dim a5 As AAA = StructureFromText(Of AAA)(s)
 Dim a5 As AAA = StructureFromText(Of AAA)(Mid(s, 1))
 Dim b5 As BBB = StructureFromText(Of BBB)(Mid(s, Len(a3)))
などですかね。もちろん、読み込み開始箇所を指定可能なオーバーロードを作っても良いですが。


それにしても、改めて見てみると、
 StructureToString
 StructureFromText
って、我ながら命名に対称性が無いですね…。
「String」か「Text」か、統一するべきでした。
引用返信 編集キー/
■65032 / inTopicNo.39)  Re[27]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (18回)-(2013/01/30(Wed) 11:33:34)
No65029 (魔界の仮面弁士 さん) に返信
> 2013/01/30(Wed) 10:53:52 編集(投稿者)
>
> ■No65028 (コンバート後に悩む人 さん) に返信
>>VB.netからこのような動作になったのか、6.0からこの動作だったか
>>ちょっとうろ覚えですが、勉強不足でした。
>
> VB6 の場合の動作について No65027 で補足しておきました。参考にしてみてください。

ありがとうございます。
後ほど、参考にさせていただきます。

> 1000文字分の行の書き込みなので、返信画面や図表モード投稿時の表示が大変なことになっている…。(^^;

今気付いたのですが、もうちょっと考慮して貼り付けるべきでした・・・。
すみません。。


> 今のコードのままであれば、
>  s = StructureToString(d4)
>  'Dim a5 As AAA = StructureFromText(Of AAA)(s)
>  Dim a5 As AAA = StructureFromText(Of AAA)(Mid(s, 1))
>  Dim b5 As BBB = StructureFromText(Of BBB)(Mid(s, Len(a3)))
> などですかね。もちろん、読み込み開始箇所を指定可能なオーバーロードを作っても良いですが。

以下のようにしないと、先頭に空白1文字存在してずれてb5に格納されてしまいました。
Dim b5 As BBB = StructureFromText(Of BBB)(Mid(s, Len(a5) + 1))


> それにしても、改めて見てみると、
>  StructureToString
>  StructureFromText
> って、我ながら命名に対称性が無いですね…。
> 「String」か「Text」か、統一するべきでした。

最後の整理に、Stringに統一してみようかと思いました。
StructureToString
StructureFromString

引用返信 編集キー/
■65033 / inTopicNo.40)  Re[28]: 【VB2005、VB.NET】構造体のコピーについて
 
□投稿者/ コンバート後に悩む人 (19回)-(2013/01/30(Wed) 11:57:26)
2013/01/30(Wed) 11:57:58 編集(投稿者)
開始位置のパラメータを追加した別建てのメソッドを作成しました。
以下のような感じでしょうか。

#前述の話題ですが、Midだから+1ですよね。substringでしたら0スタートなので+1の心配はしなくてよいということをすっかり失念してました。


        Dim a5 As AAA = StructureFromText(Of AAA)(strW)
        Dim b5 As BBB = StructureFromText(Of BBB)(strW, Len(a5))


    '指定された文字列を、VBFixedString 属性の付いたフィールドに順次格納した値を返します。
    Public Function StructureFromText(Of T As Structure)(ByVal str As String, ByVal intStart As Integer) As T
        'RuntimeHelpers.GetObjectValue 対策の為、ValueType で受ける
        Dim newValue As ValueType = StructureFromText
        Dim pos As Integer = 1
        For Each f As FieldInfo In newValue.GetType().GetFields()
            Dim attributes() As Object = f.GetCustomAttributes(GetType(VBFixedStringAttribute), False)
            If attributes.Length <> 0 Then
                Dim attr As VBFixedStringAttribute = DirectCast(attributes(0), VBFixedStringAttribute)
                Dim value As String = Strings.Mid(str & StrDup(attr.Length, " "c), intStart + pos, attr.Length)
                f.SetValue(newValue, value)
                pos += attr.Length
            End If
        Next
        Return DirectCast(newValue, T)
    End Function

引用返信 編集キー/

<前の20件 | 次の20件>
トピック内ページ移動 / << 0 | 1 | 2 >>

管理者用

- Child Tree -