VB.netにて、構造体の中身をString変数にコピーするには?

解決


カイ  2004-05-08 10:19:32  No: 83276  IP: [192.*.*.*]

お世話になります。
VB.net2003を使用しています。


Public Structure STRC_ABC
        <VBFixedString(1), MarshalAs(UnmanagedType.ByValTStr, SizeConst:=1)> Dim strA As String
        <VBFixedString(1), MarshalAs(UnmanagedType.ByValTStr, SizeConst:=1)> Dim strB As String
        <VBFixedString(1), MarshalAs(UnmanagedType.ByValTStr, SizeConst:=1)> Dim strC As String
End Structure

Dim ABC As STRC_ABC
Dim strValue As String = Space(Len(ABC))

ABC.strA = "a"
ABC.strB = "b"
ABC.strC = "c"

ここで、ABCの内容をstrValueにコピーしたいのです。
結果として、strValue = "abc"を得たいのです。
VB6とかだと、RtlMoveMemoryを使って簡単にできたのですが・・・

実現するコードを教えていただけないでしょうか?

編集 削除
魔界の仮面弁士  2004-05-08 13:59:49  No: 83277  IP: [192.*.*.*]

Structureに、ToStringメソッドを実装すれば良いのでは。

# そういう事ではないのかな?

編集 削除
魔界の仮面弁士  2004-05-08 14:03:17  No: 83278  IP: [192.*.*.*]

それと、SizeConst:=1 を期待するのであれば、
メンバのデータ型を As String にするのではなく、
As Char で定義するのも手かと。

編集 削除
カイ  2004-05-08 15:47:20  No: 83279  IP: [192.*.*.*]

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

恥ずかしながら、実装というのがイマイチよくわからないのです・・・
MSDNの「インターフェイスの実装」関連を読んでみたのですが、
例が難しすぎて・・・

恥をしのんでお願いします。
もしよろしければ、上の自分の構造体に、ToStringメソッドを
実装するコード(方法)を教えていただけないでしょうか?

編集 削除
魔界の仮面弁士  2004-05-08 17:32:46  No: 83280  IP: [192.*.*.*]

> 恥ずかしながら、実装というのがイマイチよくわからないのです・・・
「実装」という言葉を、国語辞典的に言えば
  『部品や機能を作成して、それを取り付けること』
となります。英語で言えばimplement(ation)でしょうか。

で、今回の場合は
  『メソッド(VBではFunctionプロシージャ)を定義する』
という程度の意味で書きました。

つまり、構造体に変数メンバ(strA〜strC)を定義するだけでなく、
それらを連結した文字列を返すようなメソッドを
「Function ToString() As String」
などとして定義してみてはどうですか、という事です。

VB6の時は、APIを使って結合されたとの事ですが、変数3つを連結する程度の
処理なら、APIを持ち出す必要も無いかな?という判断です。

> 実装するコード(方法)を教えていただけないでしょうか?
現時点のコードで、
  strValue = ABC.ToString()
を実行すると、ABCのデータ型、例えば『〜〜.Form1+STRC_ABC』のような
文字列が渡されますよね。

で。私の「StructureにToStringメソッドを実装する」という発言は、
  Public Structure STRC_ABC
           :
    Overrides Function ToString() As String
      Return strA & strB & strC
    End Function
  End Structure 
のような定義を行う、という意味を想定していました。

こうすると、先ほどの
  strValue = ABC.ToString()
で『abc』が返されるようになりますし、
  Trace.WriteLine(ABC)
  Debug.WriteLine(ABC)
  S = String.Format("{0}", ABC)
などとした時にも、『abc』が返される事になります。

元のToStringの動作を変更したくないなら、他の名前(例えばKAI)にして
    Public Structure STRC_ABC
              :
        Function KAI() As String
            Return strA & strB & strC
        End Function
    End Structure
などと定義しておき、それを
    strValue = ABC.KAI()
のように呼び出すのでも良いかと。

編集 削除
カイ  2004-05-10 10:04:21  No: 83281  IP: [192.*.*.*]

魔界の仮面弁士  さん
詳しい説明、ありがとうございます。
もうちょっとだけ教えてください^^;

''///////////////  VB6  //////////////////////
Declare Sub MemCopy Lib "kernel32" Alias "RtlMoveMemory" (hpvDest As Any, hpvSource As Any, ByVal cbCopy As Integer)

Type STRC_TEST
        Dim strA As String * 1
        Dim strB As String * 2
        Dim strC As String * 3
        Dim strD As String * 4
        Dim strE As String * 5
        Dim strF As String * 6
        Dim strG As String * 1
        Dim strH As String * 2
        Dim strI As String * 3
        Dim strJ As String * 4
        Dim strK As String * 5
        Dim strL As String * 6
End Type

Dim strValue As String
Dim TEST As STRC_TEST

''例えば、各メンバに以下のような値を入れたとして、
TEST.strA = "A"
TEST.strB = "BB"
TEST.strC = "CCC"
TEST.strD = "DDDD"
TEST.strE = "EEEEE"
TEST.strF = "FFFFFF"
TEST.strG = "G"
TEST.strH = "HH"
TEST.strI = "III"
TEST.strJ = "JJJJ"
TEST.strK = "KKKKK"
TEST.strL = "LLLLLL"

strValue = Space(Len(TEST))

''ここで、
Call MemCopy(strValue , TEST , Len(TEST))
''を実行すると、
strValue = "ABBCCCDDDDEEEEEFFFFFFGHHIIIJJJJKKKKKLLLLLL"

''また、逆も行いたいので、

Dim TEST2 As STRC_TEST

''と新たに定義し、メンバに何も入っていない状態で、

Call MemCopy(TEST2 , strValue , Len(TEST2))

''を実行すると、

TEST2.strA = "A"
TEST2.strB = "BB"
TEST2.strC = "CCC"
TEST2.strD = "DDDD"
TEST2.strE = "EEEEE"
TEST2.strF = "FFFFFF"
TEST2.strG = "G"
TEST2.strH = "HH"
TEST2.strI = "III"
TEST2.strJ = "JJJJ"
TEST2.strK = "KKKKK"
TEST2.strL = "LLLLLL"

''になります

''/////////////////// End VB6 ///////////////////////////
これをVB.netで実現したいのです。

構造体内に関数を実装することで、構造体 > 文字列  は
魔界の仮面弁士さんのおっしゃるとおり、構造体内に関数を実装して、
その関数で結合処理をするのもいいのですが、VB6でメモリコピー一発で済んでたのを
VB.netでも実現できないかなーと。

逆も、
TEST2.strA = Mid(strValue , 1 , 1)
TEST2.strB = Mid(strValue , 2 , 2)
TEST2.strC = Mid(strValue , 4 , 3)
.
.
.
とやっていくのも、気が遠くなるような作業(上記で慣れてしまった身としては)なので・・・^^;
構造体&メンバ数が沢山あるプログラムなもので・・・

構造体 > 文字型変数    文字列変数 > 構造体  を、VB6みたくメモリコピーのような感じで、
.netで実現する方法を教えていただけないでしょうか?

編集 削除
魔界の仮面弁士  2004-05-11 01:37:40  No: 83282  IP: [192.*.*.*]

> 逆も、
> TEST2.strA = Mid(strValue , 1 , 1)
代入用のメソッドやプロパティ、あるいはパラメータ付きコンストラクタを用意するとか…。

> 構造体&メンバ数が沢山あるプログラムなもので・・・
メンバを配列やコレクションにして管理するとか、あるいは、CallByNameやリフレクションなどを使って、各メンバをループで処理するとか…。


> メモリコピーのような感じで、.netで実現する方法を教えていただけないでしょうか?
フィールドが「値型」のみで構成されていれば、
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
    Public Structure STRC_ABC
        Dim strA As Char
        Dim strB As Char
        Dim strC As Char
        Private Declare Ansi Sub RtlMoveMemory Lib "kernel32" _
            (ByVal Destination As String, ByRef Source As STRC_ABC, ByVal Length As Integer)
        Public ReadOnly Property Text() As String
            Get
                Dim Size As Integer = Marshal.SizeOf(Me)
                Text = Space(Size)
                RtlMoveMemory(Text, Me, Size)
            End Get
        End Property
    End Structure
などとしておいて、それを以下のように利用できます。

    Dim ABC As STRC_ABC
    ABC.strA = "a"c   '文字列(String)ではなく、文字(Char)を指定するために、
    ABC.strB = "b"c   '右辺が「"X"c」のような「文字リテラル」になっている事に注意。
    ABC.strC = "c"c   'ただし、Option Strict Offの時に限っては、「"X"」のような、
                      '「文字列リテラル」を指定しても、コンパイルする事ができます。
    Dim strValue As String = ABC.Text
    MessageBox.Show(strValue)


しかし、フィールドに文字列が含まれている場合には、Marshal.Copyメソッドを使うにしても、RtlMoveMemory APIを使うにしても、少々面倒な手続きが必要なようです。

VB6でも、固定長ではないString型を含むユーザー定義型は、LSetステートメントによる代入ができない事になっていましたよね。

'---- VB6(このコードはエラーになります) ----
  Private Type T1
    'S As String * 4
    S As String
  End Type

  Private Type T2
    'S As String * 4
    S As String
  End Type
----
    Dim A As T1
    Dim B As T2
    A.S = "TEST"
    LSet B = A
'---- VB6 ----

VB6の頃は、String型を「固定長文字列型」にしたり「Byte型の静的配列」にしたりする事で対応できたのですが、VB.NETではそれらがサポートされていません。
これはつまり、マーシャリングの為に、既存のコードに修正を加える必要がある、という事になります。

さて本題。

最初の質問では、Stringフィールドに<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=X)>を指定して固定長文字列を表されていたようですが、ByValTStr だと、Null終端文字列を意味する事になるため、期待通りの結果にはならないでしょう。

http://www.microsoft.com/japan/msdn/library/ja/cpref/html/frlrfsystemruntimeinteropservicesunmanagedtypeclasstopic.asp
》 .NET Framework の ByValTStr 型は、構造体内の C スタイルの
》 固定サイズ文字列 (char s[5] など) と同様に機能します。
》 マネージ コードでのこの動作は、Microsoft Visual Basic 6.0 の
》 動作 (終端が null でない。例: MyString As String * 5) とは
》 異なります。


で。終端Nullを含まない文字列にしたい場合には、Char配列を使う事で代用できます。
たとえば、VB6でいうところの「String * 1」のイメージにするのであれば、
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
    Public Structure STRC_ABC
        <MarshalAs(UnmanagedType.ByValArray, ArraySubType:=UnmanagedType.ByValTStr, SizeConst:=1), VBFixedArray(1)> Dim strA() As Char
        <MarshalAs(UnmanagedType.ByValArray, ArraySubType:=UnmanagedType.ByValTStr, SizeConst:=1), VBFixedArray(1)> Dim strB() As Char
        <MarshalAs(UnmanagedType.ByValArray, ArraySubType:=UnmanagedType.ByValTStr, SizeConst:=1), VBFixedArray(1)> Dim strC() As Char
        Private Declare Ansi Sub RtlMoveMemory Lib "kernel32" _
            (ByVal Destination As String, ByRef Source As STRC_ABC, ByVal Length As Integer)
        Public ReadOnly Property Text() As String
            Get
                Dim Size As Integer = Marshal.SizeOf(Me)
                Text = Space(Size)
                RtlMoveMemory(Text, Me, Size)
            End Get
        End Property
    End Structure
という感じです。

ただしこの場合、フィールドの定義が As String から As Char() に変更されていますので、
     ABC.strA = "a"
ではなく、
     ABC.strA = "a".ToCharArray()
か、もしくは
     ABC.strA = New Char() {"b"c}
などとしないと、Option Strict On時にコンパイルが通らないことになります。
メモリコピーの動作を優先させようとすると、既存コードへの影響が大きくなってしまうかも知れませんよ。

# 私自身は、.NETはさほど詳しいわけではないので、間違っているかも知れません。
# 何か問題があれば、フォローをお願いします。>識者の方

編集 削除
カイ  2004-05-11 09:48:12  No: 83283  IP: [192.*.*.*]

魔界の仮面弁士  さん

回答ありがとうございました。
自分の望む動作をしました。

.net初心者なもので、今後も何かと質問するかもしれませんが
その時はよろしくお願いします^^;

編集 削除