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

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

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

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


(過去ログ 109 を表示中)

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

■64946 / inTopicNo.1)  【VB2005、VB.NET】構造体のコピーについて
  
□投稿者/ コンバート後に悩む人 (1回)-(2013/01/24(Thu) 15:03:37)

分類:[.NET 全般] 

■VB6.0

Public AAAA As typAAAA
Type typAAAA
aaaa As String * 2
bbbb As String * 3
cccc As String * 5
dddd As String * 7
:
End Type

Public BBBB As typBBBB
Type typBBBB
strB As String * 100
End Type

Lset BBBB = AAAA

VB.NETからはLsetがなくなったから自力でやるようになりました。

■VB.NET

Public AAAA As typAAAA
Public Structure typAAAA
<VBFixedStringAttribute(2)> Public aaaa As String
<VBFixedStringAttribute(3)> Public bbbb As String
<VBFixedStringAttribute(5)> Public cccc As String
<VBFixedStringAttribute(7)> Public dddd As String
End Structure

Public BBBB As typBBBB
Public Structure typBBBB
<VBFixedStringAttribute(100)> Public strB As String
End Structure

With AAAA
.aaaa = Strings.Mid(BBBB.strB, 1, 2)
.bbbb = Strings.Mid(BBBB.strB, 3, 3)
.cccc = Strings.Mid(BBBB.strB, 6, 5)
.dddd = Strings.Mid(BBBB.strB, 11, 7)
End With


こんな感じになると思います。
※ここまで誤っていたら指摘お願いします。

では、これの逆はどのようになるか、VB6.0では


Lset AAAA = BBBB

単純に逆になるかと思います。

VB.NETでは・・・

AAAA.aaaaからAAAA.ddddの内容を文字結合して、BBBB.strBに代入するということになるのでしょうか。
構造体の内容が多い、またはいくつも構造体からBBBB.strBに代入だと、途方もない文字結合のコーディングになるので
何か良い方法がないかと調べていました。

魔界の仮面弁士さん、じゃんぬさん、またご教示できる方、おられましたらよろしくお願いします。

引用返信 編集キー/
■64947 / inTopicNo.2)  Re[1]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ howling (163回)-(2013/01/24(Thu) 16:23:37)
No64946 (コンバート後に悩む人 さん) に返信

こんにちわ。
私なんかが書くのはアレなんですが…
よくわかっていない部分もありますので、こちらからの質問も交えてお答えします。

やりたいことは以下で合ってますか?

・文字列の配列を4つ持った構造体を、別の文字列配列を1つ持った構造体にまとめる。
・逆に、文字列配列を1つ持った構造体から文字列の配列を4つ持った構造体へ戻す

もしこれで合っているならなんですが…
 文字列自体は+演算子で追加された文字列が新規に作成される実装になっていますので、
 4つの文字列の結合は、StrAll = StrA + StrB + StrC + StrDで可能です。

 4つの文字列の配列をまとめるだけの場合は、ArrayListを使用するのをオススメします。
 ArrayListのAddメソッドもしくはAddRangeメソッドを使えばそのArrayListに追加できます。
 取りだす場合は、配列と同じようにアクセスできますし、=演算子で新規に文字列も作られますので、

 C#であれば StrA = myArrayList[0];とか、
System.Collections.ArrayList myArrayList = new System.Collections.ArrayList();
myArrayList.GetRange(0, 2).ToArray(typeof(string)); //ArrayListから0個目と1個目のみのArrayListを切り出して配列にする
とかできます。
 VBとC#では、できることにほとんど変化は無いです。(むしろ、何かありましたっけ?)

で、これとは別にMidメソッドを使ってどうしてもやりたい、という場合は、
それはそれで別にありますので、こちらをどうぞ。

http://jeanne.wankuma.com/tips/vb.net/string/left.html
↑よく見たらじゃんぬさんのサイトですね。

サンプルコード提示できなくてすみません。
後は仮面弁士さんを待ちましょう(ぉ

引用返信 編集キー/
■64949 / inTopicNo.3)  Re[1]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ shu (146回)-(2013/01/24(Thu) 17:08:58)
No64946 (コンバート後に悩む人 さん) に返信

構造体定義:
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Public Structure typAAAA
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=2)> Public aaaa As String
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=3)> Public bbbb As String
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=5)> Public cccc As String
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=7)> Public dddd As String
    End Structure

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Public Structure typBBBB
        <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=100)> Public strB As String
    End Structure


コピー:
        Dim b = New typBBBB() With {.strB = "11222333334444444"}
        Dim ptr As New IntPtr()
        ptr = Marshal.AllocHGlobal(10000)   '<---ここの数字は適当
        Marshal.StructureToPtr(b, ptr, False)

        Dim a As New typAAAA
        a = CType(Marshal.PtrToStructure(ptr, GetType(Form1.typAAAA)), typAAAA)
        Marshal.FreeHGlobal(ptr)

こんな感じでどうでしょう?たぶん逆でも出来ると思います。

引用返信 編集キー/
■64951 / inTopicNo.4)  Re[2]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (2回)-(2013/01/24(Thu) 17:52:28)
言葉足らずな文面で申し訳ありませんでした。

やりたいことは、VB.NETにてtypAAAAの構造体からtypBBBBの構造体に
構造体のデータをコピーすることです。


> 文字列自体は+演算子で追加された文字列が新規に作成される実装になっていますので、
> 4つの文字列の結合は、StrAll = StrA + StrB + StrC + StrDで可能です。

上記のとおりなのですが、構造体の要素数は実際にはかなりの数ですので
文字列結合ですべてやっていくと、可読性が悪い、あまりコードとしてはよくないと思い
Lsetに代わるような簡潔に済む手法がないかどうか調査していました。

APIの「RtlMoveMemory」を使う手法もあるようですが上記の例で試したところ
Stringだからだめなのかうまくいきません。

shu様のご教示していただいた手法で試してみましたが、
Dim b = New typBBBB() With {.strB = "11222333334444444"}
のWithでエラーとなるようで若干書き換えてみて試したところ
a = CType(Marshal.PtrToStructure(ptr, GetType(Form1.typAAAA)), typAAAA)
で、突然終了してしまいました。

引用返信 編集キー/
■64952 / inTopicNo.5)  Re[3]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (3回)-(2013/01/24(Thu) 17:53:48)
記述途中で送信してしまいました。
もうちょっと調査、試行錯誤してみます。

という文面を追記し忘れました。すみません。

引用返信 編集キー/
■64953 / inTopicNo.6)  Re[4]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (4回)-(2013/01/24(Thu) 18:07:26)
下記のような感じでビルドエラーにもならず、実行時強制終了もならず成功はしました。
ですが、BBBB.strBに格納されているのは、AAAA.aaaa = "aaaa"のみの値だけでした。
AAAAのすべての内容がBBBB.strBに格納できてないので、もう少し試行錯誤してみます。


Public AAAA As typAAAA
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> Public Structure typAAAA
<MarshalAs(UnmanagedType.BStr, SizeConst:=4)> Public aaaa As String
<MarshalAs(UnmanagedType.BStr, SizeConst:=4)> Public bbbb As String
<MarshalAs(UnmanagedType.BStr, SizeConst:=4)> Public cccc As String
<MarshalAs(UnmanagedType.BStr, SizeConst:=4)> Public dddd As String
End Structure

Public BBBB As typBBBB
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> Public Structure typBBBB
<MarshalAs(UnmanagedType.BStr, SizeConst:=100)> Public strB As String
End Structure


AAAA.aaaa = "aaaa"
AAAA.bbbb = "bbbb"
AAAA.cccc = "1"
AAAA.dddd = "1324"


Call MemCopy(BBBB, AAAA, Len(AAAA))

引用返信 編集キー/
■64955 / inTopicNo.7)  Re[5]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (5回)-(2013/01/24(Thu) 18:14:57)
API部分記述漏れていたので追記します。すみません。

Public Declare Sub MemCopy Lib "kernel32" Alias "RtlMoveMemory" (ByRef hpvDest As typBBBB, ByVal hpvSource As typAAAA, ByVal cbCopy As Integer)


引用返信 編集キー/
■64956 / inTopicNo.8)  Re[6]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ howling (164回)-(2013/01/24(Thu) 18:43:46)
No64955 (コンバート後に悩む人 さん) に返信
むむむ?
shuさんのソース見ててどう見てもおかしな点が見当たらなかったので、
そのままコピって試してみました。

なんら問題なくコピーされましたが、何をどう変えたのでしょうか?

それから、今回の件はだいぶ勉強になりました。
C#で共用体が作れるなんて知らなかった…shuさんどうもです。>なんか調べたら出てきたので
引用返信 編集キー/
■64957 / inTopicNo.9)  Re[7]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (6回)-(2013/01/24(Thu) 19:09:31)
shuさんの手法で試したらうまく成功しました。
そこで、逆を試したところ以下の通りとなりました。

Dim b = New typBBBB() With {.strB = "11222333334444444"}
が、ステートメントの終わりを指定してください。という旨のビルドエラーとなるため
以下のように書き換えました。

Dim a As New typAAAA
Dim ptr As New IntPtr()

a.aaaa = "11"
a.bbbb = "222"
a.cccc = "33333"
a.dddd = "4444444"

ptr = Marshal.AllocHGlobal(10000) '<---ここの数字は適当
Marshal.StructureToPtr(a, ptr, False)

Dim b As New typBBBB
b = CType(Marshal.PtrToStructure(ptr, GetType(typBBBB)), typBBBB)
Marshal.FreeHGlobal(ptr)

bの内容をみると1のみしかセットされていませんでした。
a.aaaaの最初の1のみ設定された感じでしょうか。
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=2)> Public aaaa As String
は、実質1バイトとしてみなしてるようで、+1しなければならないようです。

bbbb.strB = "11222333334444444"
とならず
bbbb.strB = "1"
となるため
strBの宣言付近が問題あるのでしょうか。
もうちょっと考えてみます。


引用返信 編集キー/
■64958 / inTopicNo.10)  Re[8]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ soy (1回)-(2013/01/25(Fri) 05:20:11)
.net版のVBのバージョンは何ですか?
ビルドエラーの原因はそこにあるかも。
引用返信 編集キー/
■64963 / inTopicNo.11)  Re[8]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ 魔界の仮面弁士 (127回)-(2013/01/25(Fri) 10:51:46)
No64946 (コンバート後に悩む人 さん) に返信
> aaaa As String * 2
全角半角混在時のデータのやりとりには注意してください。
16bit版 VB の固定長文字列は、バイト数で管理されていて、
32bit版 VB の固定長文字列は、文字数で管理されています。

そのため、ユーザー定義型を固定長テキストの作成などに
使う場合、この違いが問題になることがあります。




No64957 (コンバート後に悩む人 さん) に返信
> Dim b = New typBBBB() With {.strB = "11222333334444444"}
> が、ステートメントの終わりを指定してください。という旨のビルドエラーとなるため

タイトルにある「VB2005」の記述を、shuさんが読み落とされていたのでしょう。多分。

New 宣言時に With 句による初期化を行えるようになったのは、
VB2008 (VB9.0) からです。また、As 句省略による型推論も同様です。

VB2005 (VB8.0)、および VB.NET 2002(VB7.0)、VB.NET 2003 (VB7.1) では
 Dim b As New typBBBB()
 b.strB = "11222333334444444"
のような構文で代用してみてください。
(なお 2003 環境では、括弧無しの「Dim b As New typBBBB」に補正されます)



> 以下のように書き換えました。
> Dim a As New typAAAA
> Dim ptr As New IntPtr()
これらは構造体なので、
 「Dim a As typAAAA」
 「Dim ptr As IntPtr」
でも十分です。New は無くとも構いません。

特に ptr の方については、どうせその後で
別の値を代入することになりますしね。


> strBの宣言付近が問題あるのでしょうか。
ByValTStr というものが、「NULL終端文字列」を意味するからです。

先の No64949 のコードとて、元のデータが
 b.strB = "112" & vbNullChar & "2333334444444"
だった場合、a.bbbb は「"2" & vbNullChar & "2"」ではなく、
「"2"」になってしまうことでしょう。




 a.aaaa = "11"
 a.bbbb = "222"
 a.cccc = "33333"
 a.dddd = "4444444"
 b.strB = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

 Dim binA(Marshal.SizeOf(a) - 1) As Byte
 Dim binB(Marshal.SizeOf(b) - 1) As Byte

 With GCHandle.Alloc(binA, GCHandleType.Pinned)
  Marshal.StructureToPtr(a, .AddrOfPinnedObject(), False)
  .Free()
 End With
 With GCHandle.Alloc(binB, GCHandleType.Pinned)
  Marshal.StructureToPtr(b, .AddrOfPinnedObject(), False)
  .Free()
 End With

 Debug.WriteLine("=== A ===")
 Debug.WriteLine(BitConverter.ToString(binA))
 Debug.WriteLine("=== B ===")
 Debug.WriteLine(BitConverter.ToString(binB))



【ANSIモード】
=== A ===
31-00-32-32-00-33-33-33-33-00-34-34-34-34-34-34-00
=== B ===
41-42-43-44-45-46-47-48-49-4A-4B-4C-4D-4E-4F-50-51-52-53-54-55-56-57-58-59-5A-00-00-(中略)-00-00-00-00


【Unicodeモード】
=== A ===
31-00-00-00-32-00-32-00-00-00-33-00-33-00-33-00-33-00-00-00-34-00-34-00-34-00-34-00-34-00-34-00-00-00
=== B ===
41-00-42-00-43-00-44-00-45-00-46-00-47-00-48-00-49-00-4A-00-4B-00-4C-00-4D-00-4E-00-4F-00-(中略)-00-00-00-00


各文字列の後ろに、Chr(0) 相当のバイナリが付与されているのが見えるでしょうか。


String ではなく Char 配列を使うことで回避する方法もありますが、
そもそもなぜ、構造体同士のデータ複製が必要なのでしょうか?

場合によっては、データ全体を Stream あるいは StringBuilder で管理しておき、
切り出す位置をコレクション等で管理しておく運用など、別の手法を模索したほうが
良いかもしれません。無理に旧VBの実装手法に追従させる必要は無いかと。
引用返信 編集キー/
■64965 / inTopicNo.12)  Re[9]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ shu (148回)-(2013/01/25(Fri) 14:18:24)
No64963 (魔界の仮面弁士 さん) に返信
> ■No64957 (コンバート後に悩む人 さん) に返信
>>Dim b = New typBBBB() With {.strB = "11222333334444444"}
>>が、ステートメントの終わりを指定してください。という旨のビルドエラーとなるため
>
> タイトルにある「VB2005」の記述を、shuさんが読み落とされていたのでしょう。多分。
まったくその通りです。失礼しました。


No64955 (コンバート後に悩む人 さん) に返信
NULL終端文字列しか扱えないようでやはり難しいのではないでしょうか?
byte配列にでもしておいて値の設定、値の取得処理を共通で行うものを用意されてはどうでしょう?



引用返信 編集キー/
■64967 / inTopicNo.13)  Re[10]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ 魔界の仮面弁士 (130回)-(2013/01/25(Fri) 17:26:15)
No64965 (shu さん) に返信
> NULL終端文字列しか扱えないようでやはり難しいのではないでしょうか?

先の回答にも少し書きましたが、String ではなく Char 配列を使うことで、
旧 VB の固定長文字列型に近い動作となり、NULL 文字を含むデータも扱えるようになります。

ただし今回の場合、typAAAA と typBBBB とでサイズが異なるため、
その差分の領域をどのように扱うかが問題となります。


下記のサンプルの場合、typBBBB 側は、VB6 でいうところの
As String * 100 の動作を想定しているため、先頭 17 文字は
"aabbbcccccddddddd" になりますが、その後ろに 83 文字分の
NULL文字を含んだ、「100 文字分の文字列」となります。


------------
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
Public Structure typAAAA
 <MarshalAs(UnmanagedType.ByValArray, SizeConst:=2)> Public aaaa() As Char
 <MarshalAs(UnmanagedType.ByValArray, SizeConst:=3)> Public bbbb() As Char
 <MarshalAs(UnmanagedType.ByValArray, SizeConst:=5)> Public cccc() As Char
 <MarshalAs(UnmanagedType.ByValArray, SizeConst:=7)> Public dddd() As Char
End Structure

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
Public Structure typBBBB
 <MarshalAs(UnmanagedType.ByValArray, SizeConst:=100)> Public strB() As Char
End Structure

------------
'A と B のサイズが異なるため、大きい方のサイズでバッファを用意する
Dim size As Integer = Math.Max( _
  Marshal.SizeOf(GetType(typAAAA)), _
  Marshal.SizeOf(GetType(typBBBB)))

Dim a As typAAAA
Dim b As typBBBB

'VB では、String と Char() を透過的に変換できる。
a.aaaa = "aa"
a.bbbb = "bbb"
a.cccc = "ccccc"
a.dddd = "ddddddd"

'配列を作成すると、各項目には自動的にゼロがセットされるため、
'このままコピーすると、文字列のない 18 文字目以降の場所には、
'NULL 文字がセットされたものとして扱われる
Dim bin(size - 1) As Byte
With GCHandle.Alloc(bin, GCHandleType.Pinned)
 Dim pBin As IntPtr = .AddrOfPinnedObject()
 Marshal.StructureToPtr(a, pBin, False)
 b = DirectCast(Marshal.PtrToStructure(pBin, GetType(typBBBB)), typBBBB)
 .Free()
End With
------------
引用返信 編集キー/
■64968 / inTopicNo.14)  Re[11]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (7回)-(2013/01/25(Fri) 17:45:43)
亀レスですみません。
魔界の仮面弁士さん、shuさんご協力ありがとうございます。

>場合によっては、データ全体を Stream あるいは StringBuilder で管理しておき、
>切り出す位置をコレクション等で管理しておく運用など、別の手法を模索したほうが
>良いかもしれません。無理に旧VBの実装手法に追従させる必要は無いかと。

確かにそのとおりだと思います。
あまりソースを改変しないことを目的にやるとしたらというところで検討していました。
StringBuilderがいいのかもしれないので、今後念頭に入れたいと思います。
今はもう少し、この手法で調査・試行錯誤してみたいと思っています。

上記の魔界の仮面弁士さんの例を試したところうまくいったのですが、
肝心なことを記述してませんでした。すみません。

全角文字も混在するので、上記の例では
Marshal.StructureToPtr(a, pBin, False)
で、「埋め込まれた配列インスタンスがレイアウトで宣言された長さと一致しないため、型をマーシャリングできませんでした。」
とエラーになりました。

ここまでで思ったのが、VB6.0からのソースにとらわれずに
StringBuilderでやるしか方法がないのでしょうか。


引用返信 編集キー/
■64971 / inTopicNo.15)  Re[12]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ 魔界の仮面弁士 (132回)-(2013/01/25(Fri) 20:32:08)
No64968 (コンバート後に悩む人 さん) に返信
> 全角文字も混在するので、上記の例では

VB6 同様、文字列は Unicode で管理されるため、
先の構造体は、『文字数』ベースで管理されることになります。

VB6 で LSet した場合も、あくまでも「文字数単位」で
複写されますよね。それと同じことです。

'-----------------
'------ VB6 ------
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バイト)

 '先頭17文字(≠17バイト)が置き換わる
 LSet BBBB = aaaa

 'どこが NULL 文字か分かるよう、@に置き換えて表示
 Debug.Print Replace(BBBB.strB, vbNullChar, "@")
End Sub


'--------------------
'------ VB2005 ------
' 構造体定義は No64967 を参照

Dim a1 As typAAAA
Dim b1 As typBBBB

a1.aaaa = "文字"   '2文字
a1.bbbb = "VB6"    '3文字
a1.cccc = "[A&Z]"  '5文字
a1.dddd = "1234567"  '7文字

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)
 b1 = DirectCast(Marshal.PtrToStructure(pBin, GetType(typBBBB)), typBBBB)
 .Free()
End With

'どこが NULL 文字か分かるよう、@に置き換えて表示
Console.WriteLine(Replace(b1.strB, vbNullChar, "@"))


Dim a2 As typAAAA
Dim b2 As typBBBB

'100 文字分セットしておくことを忘れずに
b2.strB = Strings.Left("漢字&半角を含むDATAです。" & StrDup(100, vbNullChar), 100)

With GCHandle.Alloc(bin, GCHandleType.Pinned)
 Dim pBin As IntPtr = .AddrOfPinnedObject()
 Marshal.StructureToPtr(b2, pBin, False)
 a2 = DirectCast(Marshal.PtrToStructure(pBin, GetType(typAAAA)), typAAAA)
 .Free()
End With

Console.WriteLine(Replace(a2.aaaa, vbNullChar, "@"))
Console.WriteLine(Replace(a2.bbbb, vbNullChar, "@"))
Console.WriteLine(Replace(a2.cccc, vbNullChar, "@"))
Console.WriteLine(Replace(a2.dddd, vbNullChar, "@"))
'------------


> 「埋め込まれた配列インスタンスがレイアウトで宣言された長さと一致しないため、型をマーシャリングできませんでした。」
低レベルのコピー命令を使う以上、データの長さは常に意識しておかねばなりません。

構造体をコピーしたいけれど、文字数単位では無く、Shift_JIS 相当の
バイナリ幅で処理して欲しい…というのであれば、各フィールドを
Char 配列ではなく、Byte 配列で管理する必要があります。

そのかわり、入出力操作のたびに、Encoding 変換の手間が
かかってしまうので、操作としては面倒になってしまいます。
(VB6 でいえば、StrConv で Unicode/ANSI 変換を毎回行うイメージです)

これが VB2008 であれば、String と Byte() を変換するための
拡張メソッドを作ることで、だいぶ楽にできるのですけれどね…。


> ここまでで思ったのが、VB6.0からのソースにとらわれずに
> StringBuilderでやるしか方法がないのでしょうか。

それはどうでしょう。方法自体は、他にもいろいろあると思いますよ。

手間を惜しまないのであれば、固定長文字列型に相当するクラスや属性を
自作するという手法もあるでしょう。その固定長型で、CType 演算子なども
オーバーロードさせておけば、VB6 の動作に、かなり近い物を
用意できるかも知れません。工数に見合うかどうかは別ですが。

まぁ、最終的にどの方法を選ぶかは、御自身の判断ということで。


とはいえ、StringBuilder だと「文字数」での管理になってしまいます。
もしも Shift_JIS バイナリとしてのバイト位置で管理したいなら、
MemoryStream や Byte() などで管理したほうが都合が良いかもしれません。


Dim sjis As Encoding = Encoding.GetEncoding(932)

Dim stm As New MemoryStream()

Dim w As New StreamWriter(stm, sjis)
'w.Write("答") '2バイト
'w.Write("Q&A") '3バイト
'w.Write("質問?") '5バイト
'w.Write("Answer!") '7バイト
w.Write("答Q&A質問?Answer!")


Dim bin() As Byte
w.Flush()
stm.Position = 0
With New BinaryReader(stm)
bin = .ReadBytes(stm.Length)
End With

'この切り出し位置の一覧を「Dictionary(Of 位置情報)」などで
'管理しておくと、多少は管理しやすくなるかも。
Console.WriteLine(sjis.GetString(bin, 0, 2))
Console.WriteLine(sjis.GetString(bin, 2, 3))
Console.WriteLine(sjis.GetString(bin, 5, 5))
Console.WriteLine(sjis.GetString(bin, 10, 7))
引用返信 編集キー/
■64976 / inTopicNo.16)  Re[12]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ shu (149回)-(2013/01/28(Mon) 08:04:42)
2013/01/28(Mon) 15:09:26 編集(投稿者)
No64968 (コンバート後に悩む人 さん) に返信
> 確かにそのとおりだと思います。
> あまりソースを改変しないことを目的にやるとしたらというところで検討していました。
> StringBuilderがいいのかもしれないので、今後念頭に入れたいと思います。
> 今はもう少し、この手法で調査・試行錯誤してみたいと思っています。
あまりソースを改変しないことを目的とする場合、多少大き目の追加を行うとよい場合があります。

例えば以下のようなクラスClass1を作成してそこから派生したClassA,ClassBは簡単な実装でもとの
構造体と似たような動作をさせることができるかと思います。Class1の実装を直せば希望の動きが実装
出来るのではないでしょうか?参考にしてみて下さい。要件によっては前述したようにByte配列を用いると
良いと思います。


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

    Public Sub New(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 As String
        Get
            Return _InnerValue
        End Get
        Set(value As String)
            Dim NewValueLength = value.Length
            Dim MyValueLength = _InnerValue.Length
            Select Case NewValueLength
                Case Is < MyValueLength
                    _InnerValue = value & New String(" "c, MyValueLength - NewValueLength)
                Case MyValueLength
                    _InnerValue = value
                Case Else
                    _InnerValue = value.Substring(0, MyValueLength)
            End Select
        End Set
    End Property

    Public Property Value(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(value As String)
            Dim FieldStart As Integer = _FieldStarts(Index)
            Dim FieldLength As Integer = _FieldLengths(Index)

            Dim NewValue As New 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(Dest As Class1)
        Dest.InnerValue = Me.InnerValue
    End Sub
End Class

Public Class ClassA
    Inherits Class1

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

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

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

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

    Public Property ddddd As String
        Get
            Return MyBase.Value(3)
        End Get
        Set(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(value As String)
            MyBase.Value(0) = value
        End Set
    End Property

End Class


# ValueのSet部分を修正

引用返信 編集キー/
■64977 / inTopicNo.17)  Re[13]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (8回)-(2013/01/28(Mon) 14:20:31)
No64971 (魔界の仮面弁士 さん) に返信
> '--------------------
> '------ VB2005 ------
> ' 構造体定義は No64967 を参照
>
> Dim a1 As typAAAA
> Dim b1 As typBBBB
>
> a1.aaaa = "文字"   '2文字
> a1.bbbb = "VB6"    '3文字
> a1.cccc = "[A&Z]"  '5文字
> a1.dddd = "1234567"  '7文字
>
> 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)
>  b1 = DirectCast(Marshal.PtrToStructure(pBin, GetType(typBBBB)), typBBBB)
>  .Free()
> End With
>
> 'どこが NULL 文字か分かるよう、@に置き換えて表示
> Console.WriteLine(Replace(b1.strB, vbNullChar, "@"))


下記のソース試したところ、意図とした動きをしてよかったので
typBBBBに格納するべき、typAAAAのほかに複数の構造体を用意して試すのが最終目的でもあるため
確認しようと考えています。

複数構造体をtypBBBBにコピーする場合は、
>  Marshal.StructureToPtr(a1, pBin, False)
>  b1 = DirectCast(Marshal.PtrToStructure(pBin, GetType(typBBBB)), typBBBB)
上記のあたりを変えるのかと思いましたが
すみません、ちょっと考えつきませんでした。
どのように記述するのか教えていただけないでしょうか。

---構造体宣言部分追加---
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
Public Structure typCCCC
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=2)> Public aaaa() As Char
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=3)> Public bbbb() As Char
End Structure


<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
Public Structure typDDDD
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=2)> Public aaaa() As Char
End Structure

---構造体代入部分追加---
c1.aaaa = "11"
c1.bbbb = "22"
d1.aaaa = "あ"


引用返信 編集キー/
■64978 / inTopicNo.18)  Re[13]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ コンバート後に悩む人 (9回)-(2013/01/28(Mon) 14:28:57)
No64976 (shu さん) に返信

魔界の仮面弁士さんの返信でも書きましたが
複数の構造体(typeAAAA、typeCCCC、typDDDD)をtypBBBBにコピーすることが
最終的な目標となるため、この手法だとクラスファイルを構造体分必要になります。
実際には15個くらいの構造体の数となりますから、ちょっと非現実的かなと思いました。

と・・・思いつつもせっかくご教示していただいたソースを試さないのももったいないので
考えてみたのですが、使い方がわかりませんでした・・・。
Copyメソッドの引数はコピーしたいクラスを渡すのでしょうか?
また、Valueの使い方はどのように使うのでしょうか?
すみません、教えてください。



引用返信 編集キー/
■64981 / inTopicNo.19)  Re[14]: 【VB2005、VB.NET】構造体のコピーについて
□投稿者/ shu (150回)-(2013/01/28(Mon) 15:15:20)
No64978 (コンバート後に悩む人 さん) に返信
> ■No64976 (shu さん) に返信
>
> 魔界の仮面弁士さんの返信でも書きましたが
> 複数の構造体(typeAAAA、typeCCCC、typDDDD)をtypBBBBにコピーすることが
> 最終的な目標となるため、この手法だとクラスファイルを構造体分必要になります。
> 実際には15個くらいの構造体の数となりますから、ちょっと非現実的かなと思いました。
Class1の部分は1個でよくClassA, ClassBの部分だけなので15個作るだけですみます。


> と・・・思いつつもせっかくご教示していただいたソースを試さないのももったいないので
> 考えてみたのですが、使い方がわかりませんでした・・・。
> Copyメソッドの引数はコピーしたいクラスを渡すのでしょうか?
> また、Valueの使い方はどのように使うのでしょうか?
> すみません、教えてください。
>


Dim a As New ClassA()
Dim b As New ClassB()


a.aaaa = "11"
a.bbbb = "222"
a.cccc = "33333"
a.ddddd = "4444444"
a.Copy(b)
とすれば
b.strBが11222333444・・・スペース・・・
となり

b.strB = "abcdefghijklmnopqrstuvwxyz"
b.Copy(a)
とすれば
a.aaaa = "ab"
a.bbbb = "cde"
a.cccc = "fghij"
a.ddddd = "klmnopq"
となります。

前記コードは一部修正しました。

引用返信 編集キー/
■64983 / inTopicNo.20)  Re[14]: 【VB2005、VB.NET】構造体のコピーについて
 
□投稿者/ 魔界の仮面弁士 (134回)-(2013/01/28(Mon) 15:34:35)
No64977 (コンバート後に悩む人 さん) に返信
> typBBBBに格納するべき、typAAAAのほかに複数の構造体を用意して試すのが最終目的でもあるため
> 確認しようと考えています。

テキスト以外にバイナリも含むなら、構造体ではなく MemoryStream で保持し、
BinaryReader / BinaryWriter や StreamReader / StreamWriter で
バイト位置を指定しながら読み書きする手もあります。


> 上記のあたりを変えるのかと思いましたが
> すみません、ちょっと考えつきませんでした。
理解度確認のため、先のコードを少々説明しておきます。

まず、最初の「Dim bin(size - 1) As Byte」については良いでしょうか。
構造体が確保するであろう最大サイズを用意しています。

バイト配列の代わりに、Marshal.AllocHGlobal を使う方法もありますが、
AllocHGlobal だと、確保したメモリの中身はクリアされていないため、
初期値が不定となります。バイト配列として用意しておけば、
各項目の初期値は 0 になっています。


次に、そのメモリに対して、構造体データをコピーしていきます。

With GCHandle.Alloc(bin, GCHandleType.Pinned)
 Dim pBin As IntPtr = .AddrOfPinnedObject()
 Marshal.StructureToPtr(コピー元変数, pBin, False)
 コピー先変数 = DirectCast(Marshal.PtrToStructure(pBin, GetType(コピー先の型)), コピー先の型)
 .Free()
End With


Pinned 指定は、データをコピーしている間、メモリの再配置が
行われないようするためのものです。ここでは、配列 bin を固定し、
そのハンドルを pBin As IntPtr に取得しています。

Marshal.StructureToPtr は、構造体の中身を IntPtr に書き込むもの、
Marshal.PtrToStructure は、IntPtr の内容を構造体として読み込むものです。

最後に Free を呼び出し、固定していた配列のハンドルを解放します。


> どのように記述するのか教えていただけないでしょうか。
複数の構造体があるなら、メモリバッファの 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 に戻します。


もしくは、個々の構造体ごとのバイナリ配列を用意したのち、
それを Array.Copy 等で繋いでから、最終結果の構造体に
PtrToStructure するという手法もあります。
引用返信 編集キー/

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

管理者用

- Child Tree -