none
C#からvb.netへ変換したい RRS feed

  • 質問

  • 皆さん、

    お疲れ様です。

    下記に代替データストリーム取得C#ソースをvb.netへ変換したいんです。お願い致します。変換するToolを使いましたが上手く動かないです。問題あるやつは

    [DllImport("kernel32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool BackupRead(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [MarshalAs(UnmanagedType.Bool)] bool bAbort, [MarshalAs(UnmanagedType.Bool)] bool bProcessSecurity, ref IntPtr lpContext);

    ここです。

    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using System.ComponentModel;
    using Microsoft.Win32.SafeHandles;
    using System.IO;


    namespace TestReading
    {
        class Program
        {
            static void Main(string[] args)
            {
                foreach (string path in args)
                {
                    Console.WriteLine(path + ":");
                    foreach (StreamInfo stream in FileStreamSearcher.GetStreams(new FileInfo(path)))
                    {
                        if (stream.Name != null)
                        {
                            Console.WriteLine("\t{0}\t{1}\t{2}", path + "" + stream.Name, stream.Type, stream.Size);
                        }
                    }
                }
                Console.ReadKey();
            }
        }

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        public struct Win32StreamID
        {
            public StreamType dwStreamId;
            public int dwStreamAttributes;
            public long Size;
            public int dwStreamNameSize;
        }

        public enum StreamType
        {
            Data = 1,
            ExternalData = 2,
            SecurityData = 3,
            AlternateData = 4,
            Link = 5,
            PropertyData = 6,
            ObjectID = 7,
            ReparseData = 8,
            SparseDock = 9
        }

        public struct StreamInfo
        {
            public StreamInfo(string name, StreamType type, long size)
            {
                Name = name;
                Type = type;
                Size = size;
            }
            public readonly string Name;
            public readonly StreamType Type;
            public readonly long Size;
        }

        public class FileStreamSearcher
        {
            [DllImport("kernel32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool BackupRead(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [MarshalAs(UnmanagedType.Bool)] bool bAbort, [MarshalAs(UnmanagedType.Bool)] bool bProcessSecurity, ref IntPtr lpContext);
            [DllImport("kernel32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool BackupSeek(SafeFileHandle hFile, uint dwLowBytesToSeek, uint dwHighBytesToSeek, out uint lpdwLowByteSeeked, out uint lpdwHighByteSeeked, ref IntPtr lpContext);
            public static IEnumerable<StreamInfo> GetStreams(FileInfo file)
            {
                const int bufferSize = 4096;
                using (FileStream fs = file.OpenRead())
                {
                    IntPtr context = IntPtr.Zero;
                    IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
                    try
                    {
                        while (true)
                        {
                            uint numRead;
                            if (!BackupRead(fs.SafeFileHandle, buffer, (uint)Marshal.SizeOf(typeof(Win32StreamID)), out numRead, false, true, ref context)) throw new Win32Exception();
                            if (numRead > 0)
                            {
                                Win32StreamID streamID = (Win32StreamID)Marshal.PtrToStructure(buffer, typeof(Win32StreamID));
                                string name = null;
                                if (streamID.dwStreamNameSize > 0)
                                {
                                    if (!BackupRead(fs.SafeFileHandle, buffer, (uint)Math.Min(bufferSize, streamID.dwStreamNameSize), out numRead, false, true, ref context))
                                        throw new Win32Exception();
                                    name = Marshal.PtrToStringUni(buffer, (int)numRead / 2);
                                }
                                yield return new StreamInfo(name, streamID.dwStreamId, streamID.Size);
                                if (streamID.Size > 0)
                                {
                                    uint lo, hi;
                                    BackupSeek(fs.SafeFileHandle, uint.MaxValue, int.MaxValue, out lo, out hi, ref context);
                                }
                            }
                            else break;
                        }
                    }
                    finally
                    {
                        Marshal.FreeHGlobal(buffer);
                        uint numRead;
                        if (!BackupRead(fs.SafeFileHandle, IntPtr.Zero, 0, out numRead, true, false, ref context)) throw new Win32Exception();
                    }
                }
            }
        }
    }


    • 編集済み Luky9 2019年5月23日 5:32
    2019年5月23日 5:10

回答

  • 下記に代替データストリーム取得C#ソースをvb.netへ変換したいんです。お願い致します。

    コードの翻訳が目的ではなく、代替データストリームの読み書きが出来れば良いだけならば、Trinet.Core.IO.Ntfs を使うと簡単かと思います。

    上記を使えば、C:\Downloads\ フォルダー内のファイル群から Zone.Identifier の代替データストリームを列挙するために下記のようなコードを利用できます。

    Imports System.IO
    Imports Trinet.Core.IO.Ntfs
    
    Module Module1
        Sub Main()
            For Each fi In New DirectoryInfo("C:\Downloads").GetFiles()
                ' 
                ' For Each x In fi.ListAlternateDataStreams()
                '    Console.WriteLine(x.FullPath)
                ' Next
                ' 
                Dim ads = fi.GetAlternateDataStream("Zone.Identifier")
                If ads.Exists Then
                    Using reader As New StreamReader(ads.OpenRead())
                        Dim okCancel = MsgBox(reader.ReadToEnd(), MsgBoxStyle.Information Or MsgBoxStyle.OkCancel, fi.Name)
                        If okCancel = MsgBoxResult.Cancel Then
                            Return
                        End If
                    End Using
                End If
            Next
        End Sub
    End Module

    2019年5月23日 7:33
  • ググって調べただけなのでハズレかもしれませんが・・・

    [return: MarshalAs(UnmanagedType.Bool)] c# to vb をキーワードにググると以下の記事がヒットします。

    CA1414:ブール型の P/Invoke 引数を MarshalAs に設定します
    https://docs.microsoft.com/ja-jp/visualstudio/code-quality/ca1414-mark-boolean-p-invoke-arguments-with-marshalas?view=vs-2019

    そこで、以下の画像で赤枠で囲った部分で C# と VB の切り替えができますので、切り替えてサンプルコードの違いを見ると参考になると思われます。

    • 回答としてマーク Luky9 2019年5月29日 2:22
    2019年5月23日 6:05
  • そもそも、FileInfo.Exists が True であるかどうかを確認してください。存在しないファイルのストリームは取り出せません。

    "C:\Workspace\~" と
    "\\?\C:\Workspace\~" の違いはご存知ですよね?

    'For Each f1 In New DirectoryInfo("\\?\C:\Workspace\CopyOK2").GetFiles("*.txt")
    For Each f1 In New DirectoryInfo("C:\Workspace\CopyOK2").GetFiles("*.txt")
        Dim f2 As New FileInfo(f1.FullName)
        Console.WriteLine("Length   : '{0}'", f1.FullName.Length)
        Console.WriteLine("f1 = f2  : '{0}'", f1.FullName = f2.FullName)
        Console.WriteLine("f1.Exists: '{0}'", f1.Exists)
        Console.WriteLine("f2.Exists: '{0}'", f2.Exists)
    Next

    • 回答としてマーク Luky9 2019年5月29日 2:22
    2019年5月24日 7:22

すべての返信

  • > 変換するToolを使いましたが上手く動かないです。

    「変換するTool」とは何か、「上手く動かない」とは具体的にどういうことか(例: ○○○を期待して、△△△をやったが、結果は□□□になってしまう)を書けませんか?
    2019年5月23日 5:21
    • 回答としてマーク Luky9 2019年5月23日 5:33
    • 回答としてマークされていない Luky9 2019年5月23日 6:36
    2019年5月23日 5:23
  • 上の私の列につけた「回答としてマーク」は外しておいてください。マークがついているとこのスレッドを見てくれる人が減って回答を得にくくなりますので。
    2019年5月23日 5:48
  • お返事ありがとうございます。Code Converter C# to/from VB.NETというToolを使いました。

    問題ある点は下記のあたりです。

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

    DllImport("kernel32.dll")>
        <MarshalAs(UnmanagedType.Bool)>
        Private Shared Function BackupSeek(ByVal hFile As SafeFileHandle, ByVal dwLowBytesToSeek As UInteger, ByVal dwHighBytesToSeek As UInteger, <Out> ByRef lpdwLowByteSeeked As UInteger, <Out> ByRef lpdwHighByteSeeked As UInteger, ByRef lpContext As IntPtr) As Boolean
        End Function

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

    <MarshalAs(UnmanagedType.Bool)>でエラーになっています。

    2019年5月23日 5:50
  • ググって調べただけなのでハズレかもしれませんが・・・

    [return: MarshalAs(UnmanagedType.Bool)] c# to vb をキーワードにググると以下の記事がヒットします。

    CA1414:ブール型の P/Invoke 引数を MarshalAs に設定します
    https://docs.microsoft.com/ja-jp/visualstudio/code-quality/ca1414-mark-boolean-p-invoke-arguments-with-marshalas?view=vs-2019

    そこで、以下の画像で赤枠で囲った部分で C# と VB の切り替えができますので、切り替えてサンプルコードの違いを見ると参考になると思われます。

    • 回答としてマーク Luky9 2019年5月29日 2:22
    2019年5月23日 6:05
  • 指摘通りに修正しました。エラーはならないですが値は取得できないみたいです。

    <DllImport("kernel32.dll")>
        Friend Shared Function BackupRead(ByVal hFile As SafeFileHandle, ByVal lpBuffer As IntPtr, ByVal nNumberOfBytesToRead As UInteger, <Out> ByRef lpNumberOfBytesRead As UInteger,
            <MarshalAs(UnmanagedType.Bool)> ByVal bAbort As Boolean,
            <MarshalAs(UnmanagedType.Bool)> ByVal bProcessSecurity As Boolean, ByRef lpContext As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
        End Function

    2019年5月23日 6:13
    • 不用意な型変換などの可能性を排除するため、*.vb ファイルの先頭に「Option Strict On」を記述しておいてください。
    • BackupRead の戻り値が True / False のいずれであるかを確認してください。
    • BackupRead が False だった場合、関数の呼び出しが失敗していることを意味します。エラーの理由を確認するため、さらに Marshal.GetLastWin32Error() メソッドを呼び出してエラーコードを確認してみてください。
    • BackupRead が True を返す場合、API の呼び出し自体は成功していることになりますので、引数として渡している内容を再確認してみてください。
    2019年5月23日 6:58
  • BackupReadで問題なさそうです。

    Yield New StreamInfo(name, streamID.dwStreamId, streamID.Size)で渡して

    Shared Sub Main(ByVal args As String())
                For Each stream As StreamInfo In FileStreamSearcher.GetStreams(New FileInfo(path))
                    Console.WriteLine("Type:{0} Size:{1}", stream.Type, stream.Size)
                Next
    Next

    入るときに値は0になっています。Debugして確認したらStreamInfoに値もちゃんと取得しています。

    2019年5月23日 7:27
  • 下記に代替データストリーム取得C#ソースをvb.netへ変換したいんです。お願い致します。

    コードの翻訳が目的ではなく、代替データストリームの読み書きが出来れば良いだけならば、Trinet.Core.IO.Ntfs を使うと簡単かと思います。

    上記を使えば、C:\Downloads\ フォルダー内のファイル群から Zone.Identifier の代替データストリームを列挙するために下記のようなコードを利用できます。

    Imports System.IO
    Imports Trinet.Core.IO.Ntfs
    
    Module Module1
        Sub Main()
            For Each fi In New DirectoryInfo("C:\Downloads").GetFiles()
                ' 
                ' For Each x In fi.ListAlternateDataStreams()
                '    Console.WriteLine(x.FullPath)
                ' Next
                ' 
                Dim ads = fi.GetAlternateDataStream("Zone.Identifier")
                If ads.Exists Then
                    Using reader As New StreamReader(ads.OpenRead())
                        Dim okCancel = MsgBox(reader.ReadToEnd(), MsgBoxStyle.Information Or MsgBoxStyle.OkCancel, fi.Name)
                        If okCancel = MsgBoxResult.Cancel Then
                            Return
                        End If
                    End Using
                End If
            Next
        End Sub
    End Module

    2019年5月23日 7:33
  • お返事ありがとうございます。

    私の目的はあるファイルの代替データストリームの代替データストリームの名とサイズを取得したいです。ストリームはZone.Identiferを含めてすべてを取得したいです。そのときソースをどうやって書けば良いですか。お時間あれば詳しい説明して頂きませんか。

    宜しくお願い致します。

    2019年5月23日 8:03
  • 入るときに値は0になっています。

    上記のどの行を通過する際に、どの値が 0 になっていたのでしょうか?

    Debugして確認したらStreamInfoに値もちゃんと取得しています。

    StreamInfo 構造体の Type フィールドには、何が入っていましたか?

    2019年5月23日 8:07
  • Trinet.Core.IO.Ntfsを使って実装して方が簡単ですね。ありがとうございます。

    一つたげ質問いいですか。今はx.FullPathにパスが入っています。これと同じ様な代替データストリーム名を取得方法がありますか。

    例えば「C:\Downloads\Test.txt:3:$DATA」しゃなくて「Test.txt:3:$DATA」を取得したいです。

    2019年5月23日 8:23
  • Trinet.Core.IO.Ntfsを使って実装して方が簡単ですね。ありがとうございます。

    ありがたいことに、Trinet.Core.IO.Ntfs では名前付きストリームの読み取りだけでなく、削除や加工も可能です。(Append や Truncate は NG)

    ただし現時点では、このライブラリで扱える情報が代替データストリームすなわち無名 $DATA ストリームに限定されている点に注意してください。(当初の質問が『代替データストリーム取得』であることから、このことは特に問題にはならないと思いますが)

    'Imports Trinet.Core.IO.Ntfs
    If x.StreamType = FileStreamType.AlternateDataStream Then
    

    Trinet.Core.IO.Ntfs.FileStreamType 列挙型や、先の C# サンプルコードの StreamType 列挙型は、WIN32_STREAM_ID 構造体の dwStreamId に相当します。

    BackupRead API からは、今回目的としている 名前付き $DATA ストリーム(BACKUP_ALTERNATE_DATA = 4)だけでなく、ファイル本体を指す無名 $DATA ストリーム(BACKUP_DATA = 1)やセキュリティデータ(BACKUP_SECURITY_DATA = 3)なども取り出せます。

    そのため元の C# コードでは、FileStreamSearcher からそれらすべてが列挙されてしまうため、受信側で『 if (stream.Name != null) 』のようにして、名前の無いストリームを読み捨てるようになっています。

    しかし Trinet.Core.IO.Ntfs の場合は、名前付き $DATA ストリーム以外は列挙しないように実装されています。

    今はx.FullPathにパスが入っています。これと同じ様な代替データストリーム名を取得方法がありますか。

    下記のようにして取得できます。

    Dim text As String = fi.Name & ":" & x.Name & ":$DATA"
    If 256 <= text.Length AndAlso Not text.StartsWith("\\?\") Then
        text = "\\?\" & text
    End If

    ただし Trinet.Core.IO.Ntfs だけを用いるのであれば、このような加工は不要かと思います。このライブラリの各種メソッドに渡す streamName 値は、x.Name の部分だけであり、末尾の :$DATA 指定が求められることは無いでしょう。

    2019年5月23日 10:42
  • Trinet.Core.IO.Ntfs は下記のストリームタイプを全て取得ことできるのは間違いないでしょうか。

    :$ATTRIBUTE_LIST

    :$BITMAP

    :$DATA

    :$EA

    :$EA_INFORMATION

    :$FILE_NAME

    :$INDEX_ALLOCATION

    :$INDEX_ROOT

    :$LOGGED_UTILITY_STREAM

    :$OBJECT_ID

    :$REPARSE_POINT


    • 編集済み Luky9 2019年5月24日 0:18
    2019年5月24日 0:17
  • alternate data streams のみが取得対象ですね。
    2019年5月24日 1:02
  • お返事ありがとうございます。

    ファイル名をちょっと長いで作成したら下記のエラー発生されました。(指定してファイルパスは259バイト超えたらだめでした。)

    For Each x In fi.ListAlternateDataStreams この行でエラーになりました。(System.IO.DirectoryNotFoundException)

    パス 'C:\Workspace\Cop\00000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333.txt' の一部が見つかりませんでした。

    因みに環境はWin10です。下記に設定もしました。

    https://blogs.msdn.microsoft.com/jeremykuhne/2016/07/30/net-4-6-2-and-long-paths-on-windows-10/ 






    • 編集済み Luky9 2019年5月24日 5:28
    2019年5月24日 1:52
  • そもそも、FileInfo.Exists が True であるかどうかを確認してください。存在しないファイルのストリームは取り出せません。

    "C:\Workspace\~" と
    "\\?\C:\Workspace\~" の違いはご存知ですよね?

    'For Each f1 In New DirectoryInfo("\\?\C:\Workspace\CopyOK2").GetFiles("*.txt")
    For Each f1 In New DirectoryInfo("C:\Workspace\CopyOK2").GetFiles("*.txt")
        Dim f2 As New FileInfo(f1.FullName)
        Console.WriteLine("Length   : '{0}'", f1.FullName.Length)
        Console.WriteLine("f1 = f2  : '{0}'", f1.FullName = f2.FullName)
        Console.WriteLine("f1.Exists: '{0}'", f1.Exists)
        Console.WriteLine("f2.Exists: '{0}'", f2.Exists)
    Next

    • 回答としてマーク Luky9 2019年5月29日 2:22
    2019年5月24日 7:22
  • Dim fi = New FileInfo("C:\Workspace\tes\00000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333.txt")
                If fi.Exists Then

         ’処理

        End If

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

    上記の様な存在チェックしたらFalseでした。本当はこのパスに指定してファイルは存在しています。原因はファイルパスは259バイト超えたら確認できないことです。テスト用ファイルパスは260バイトでした。どうすれば良いでしょうか。





    • 編集済み Luky9 2019年5月24日 7:50
    2019年5月24日 7:48
  • (その問題のための対策については、先の回答でも少し触れていたのですけれどね)

    直前に貴殿が投稿されたURL 内でも言及されていますように、絶対パス名の先頭にプレフィックスを付与してみてください。なお、UNC の場合は \\?\UNC\ です。

    もしくは上位のディレクトリからパスを辿るようにすることでも、Exists = True となりえますが、この方法だと AlternateDataStreamExists や GetAlternateDataStream には反応しても、ListAlternateDataStreams が空エントリになる可能性があるので、やはりプレフィックスを用いる方が望ましいです。

    2019年5月24日 8:43
  • お返事ありがとうございます。確認してみます。本当に助かりました。
    2019年5月24日 10:02