トップ回答者
C#からvb.netへ変換したい

質問
-
皆さん、
お疲れ様です。
下記に代替データストリーム取得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
回答
-
下記に代替データストリーム取得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
- 編集済み 魔界の仮面弁士MVP 2019年5月23日 7:39
- 回答としてマーク Luky9 2019年5月29日 2:22
-
ググって調べただけなのでハズレかもしれませんが・・・
[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
-
そもそも、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
すべての返信
-
お返事ありがとうございます。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)>でエラーになっています。
-
ググって調べただけなのでハズレかもしれませんが・・・
[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
-
指摘通りに修正しました。エラーはならないですが値は取得できないみたいです。
<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 -
- 不用意な型変換などの可能性を排除するため、*.vb ファイルの先頭に「Option Strict On」を記述しておいてください。
- BackupRead の戻り値が True / False のいずれであるかを確認してください。
- BackupRead が False だった場合、関数の呼び出しが失敗していることを意味します。エラーの理由を確認するため、さらに Marshal.GetLastWin32Error() メソッドを呼び出してエラーコードを確認してみてください。
- BackupRead が True を返す場合、API の呼び出し自体は成功していることになりますので、引数として渡している内容を再確認してみてください。
- 編集済み 魔界の仮面弁士MVP 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に値もちゃんと取得しています。
-
下記に代替データストリーム取得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
- 編集済み 魔界の仮面弁士MVP 2019年5月23日 7:39
- 回答としてマーク Luky9 2019年5月29日 2:22
-
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 指定が求められることは無いでしょう。
-
お返事ありがとうございます。
ファイル名をちょっと長いで作成したら下記のエラー発生されました。(指定してファイルパスは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
-
そもそも、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
-
Dim fi = New FileInfo("C:\Workspace\tes\00000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333.txt")
If fi.Exists Then’処理
End If
-------------------------------------------------------------------------------------------------------------------
上記の様な存在チェックしたらFalseでした。本当はこのパスに指定してファイルは存在しています。原因はファイルパスは259バイト超えたら確認できないことです。テスト用ファイルパスは260バイトでした。どうすれば良いでしょうか。
- 編集済み Luky9 2019年5月24日 7:50
-
(その問題のための対策については、先の回答でも少し触れていたのですけれどね)
直前に貴殿が投稿されたURL 内でも言及されていますように、絶対パス名の先頭にプレフィックスを付与してみてください。なお、UNC の場合は \\?\UNC\ です。
もしくは上位のディレクトリからパスを辿るようにすることでも、Exists = True となりえますが、この方法だと AlternateDataStreamExists や GetAlternateDataStream には反応しても、ListAlternateDataStreams が空エントリになる可能性があるので、やはりプレフィックスを用いる方が望ましいです。