お世話になります。
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を使って簡単にできたのですが・・・
実現するコードを教えていただけないでしょうか?
Structureに、ToStringメソッドを実装すれば良いのでは。
# そういう事ではないのかな?
それと、SizeConst:=1 を期待するのであれば、
メンバのデータ型を As String にするのではなく、
As Char で定義するのも手かと。
魔界の仮面弁士 さん
お返事ありがとうございます。
恥ずかしながら、実装というのがイマイチよくわからないのです・・・
MSDNの「インターフェイスの実装」関連を読んでみたのですが、
例が難しすぎて・・・
恥をしのんでお願いします。
もしよろしければ、上の自分の構造体に、ToStringメソッドを
実装するコード(方法)を教えていただけないでしょうか?
> 恥ずかしながら、実装というのがイマイチよくわからないのです・・・
「実装」という言葉を、国語辞典的に言えば
『部品や機能を作成して、それを取り付けること』
となります。英語で言えば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()
のように呼び出すのでも良いかと。
魔界の仮面弁士 さん
詳しい説明、ありがとうございます。
もうちょっとだけ教えてください^^;
''/////////////// 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で実現する方法を教えていただけないでしょうか?
> 逆も、
> 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はさほど詳しいわけではないので、間違っているかも知れません。
# 何か問題があれば、フォローをお願いします。>識者の方
魔界の仮面弁士 さん
回答ありがとうございました。
自分の望む動作をしました。
.net初心者なもので、今後も何かと質問するかもしれませんが
その時はよろしくお願いします^^;