VB.NETの構造化例外処理と非構造化例外処理の話
どうせプログラミング言語でブログを書いても、基本的に既出だし、私が書いてもわかりにくいだけで皆さんのためにならないんだろうなと思います。
でも、今回の記事には自信があります。自身があるというのは、調べた中であまり見かけない話題だからです。
なぜ触れれらていないのか私にはわかりません。しかし、この機会にブログのアクセスアップ あっごめんなさい嘘です…
というわけで今回は、 VB.NET の構造化例外処理と非構造化例外処理について、色々と調べた結果をまとめてみます。
非構造化例外処理
まず非構造化例外処理とはなんぞやというところから話をしたいと思います。
非構造化例外処理とは、ざっくり言うと On Error ~
と Err
オブジェクトを用いた例外処理です。
Visual Basic がまだ .NET化する前、有名なところでいえば VB6 等の時代に使われた文法を踏襲し、 VB.NET での例外を処理できるように改造された仕組みです。
未だに Office などのマクロを組むために使用されている VBA は、 VB6 の構文がほぼそのまま残っているため、当時の仕様のまま使うことができます。
簡単な例を見れば、この構文の方向性がなんとなくわかると思います。
Module Module1 Sub Main() On Error GoTo ErrProc1 Debug.Print("A通りますよー") On Error GoTo ErrProc2 Debug.Print("B通りますよー") On Error GoTo ErrProc3 Debug.Print("C通りますよー") Err.Raise(5) On Error Resume Next Debug.Print("D通りますよー") On Error GoTo 0 Debug.Print("おわり") Exit Sub ErrProc1: Debug.Print("ErrProc1でっせ") Exit Sub ErrProc2: Debug.Print("ErrProc2でっせ" & vbNewLine & Err.Number & ":" & Err.Description) ErrProc3: Debug.Print("ErrProc3でっせ") Resume Next End Sub End Module
要するに、 エラー処理をラベルで指示する というものです。この構文は、現在ではほぼ 禁忌 とされています。
なぜ禁忌かというと、まず一点は、スパゲッティコードになるから。あからさまに GoTo
を使う構文なので、それはまあ仕方ありません。
そしてもう一点。これは、世にも恐ろしい Resume
という機能があるからです。
Resume
は、エラーが起きた場所から処理をやり直すという意味の命令です。 エラーが起きても、元の正常処理を続きから再開できるというとても強力で忌々しい命令です。
確かに、使ってもさほど影響がない部分というのはもちろんありますが、闇雲に多用してしまうとあとからどうしようもないコードが生まれてしまうので、現在では意図的に避けられています。
詳しい説明はここでは省きますので、上記のコードがどのように動作するかを簡単にまとめます。
- ケース1
- エラー場所 -
MsgBox "A通りますよー"
の直後 - 出力結果
A通りますよー
ErrProc1でっせ
- エラー場所 -
- ケース2
- エラー場所 -
MsgBox "B通りますよー"
の直後 - 出力結果
A通りますよー
B通りますよー
ErrProc2でっせ
5:プロシージャの呼び出し、または引数が不正です。
ErrProc3でっせ
C通りますよー
D通りますよー
おわり
- エラー場所 -
- ケース3 -
MsgBox "C通りますよー"
の直後- 出力結果
A通りますよー
B通りますよー
C通りますよー
ErrProc3でっせ
D通りますよー
おわり
- 出力結果
- ケース4 -
MsgBox "D通りますよー"
の直後 A通りますよー
B通りますよー C通りますよー
D通りますよー
おわり
※なお、ケース2中のエラーメッセージは、例外の種類で異なります。
構造化例外処理
On Error
に対して、もっとオブジェクトな指向の例外処理があります。おなじみ Try
Catch
Finally
です。
「構造化例外処理」の名の通り、文法の構造的に例外処理が保証される仕組みです。
これは完全に C# の流れに押されて出現したもので、 VB6 などの時代には存在しませんでした。 (C# に押されたというか .NET 化した影響?)
今では Java や ECMAScript (またはその派生) などあらゆる言語に同様の構文が定義されており、非常に扱いやすい構文だと思います。
Module Module1 Sub Main() Try Debug.Print("A通りますよー") Try Debug.Print("B通りますよー") Try Debug.Print("C通りますよー") Try Debug.Print("D通りますよー") Catch Throw New Exception() End Try Debug.Print("おわり") Catch Debug.Print("ErrProc3でっせ") Throw New Exception() End Try Catch ex As Exception Debug.Print("ErrProc2でっせ" & vbNewLine & ex.Message) End Try Catch Debug.Print("ErrProc1でっせ") Finally Debug.Print("おわり") End Try End Sub End Module
構造化するということは、つまりブロックをネストするという意味ですので、先程のコードよりかなりインデントが深くなっています。
また、非構造化例外処理の Resume
機能に該当する機能は一切ないですし、任意に例外処理の実行順を入れ替えることもできません。
一見できることが制限されたように思いますが、この制限があるからこそ、スパゲッティコードが生まれにくいという恩恵をうけることができます。
- ケース1
- エラー場所 -
MsgBox "A通りますよー"
の直後 - 出力結果
A通りますよー
ErrProc1でっせ
おわり
- エラー場所 -
- ケース2
- エラー場所 -
MsgBox "B通りますよー"
の直後 - 出力結果
A通りますよー
B通りますよー
ErrProc2でっせ
プロシージャの呼び出し、または引数が不正です。
おわり
- エラー場所 -
- ケース3
- エラー場所 -
MsgBox "C通りますよー"
の直後 - 出力結果
A通りますよー
B通りますよー
C通りますよー
ErrProc3でっせ
ErrProc2でっせ
プロシージャの呼び出し、または引数が不正です。
おわり
- エラー場所 -
- ケース4
- エラー場所 -
MsgBox "D通りますよー"
の直後 - 出力結果
A通りますよー
B通りますよー
C通りますよー
D通りますよー
ErrProc3でっせ
ErrProc2でっせ
プロシージャの呼び出し、または引数が不正です。
おわり
- エラー場所 -
※なお、ケース2,3,4中のエラーメッセージは、例外の種類で異なります。
構造化例外処理と非構造化例外処理の共存
これら2つの文法は、お互いに排他的に使わなければなりません。噛み砕いて言えば、 同時には使えません 。
厳密には、同じプロシージャ内、いわば一つのメソッド内に両方記述してはならないという言語仕様になっています。
たとえ Try
Finally
文のみを使用して Catch
を使っていなくても、 On Error
を書いた時点でコンパイルエラーになります。
本当に?
あまり知られていない構造化例外処理
上記では、構造化例外処理として Try
Catch
Finally
を紹介しました。
最近まで、私はこれ以外の構造化例外処理を知りませんでした。
次の記事では、もう一つ、構造化例外処理を利用したコードを紹介しています。
少々古めの記事ですが、読んでみましょう。私は自分の目を疑いましたよ。
VB.NET には Using
という便利な構文があります。 IDisposable
インターフェイスを実装したクラスのインスタンスに対し、構文上でその破棄を保証するものです。
これも、 VB6 や VBA には無い機能です。
この構文は、コンパイラを通すとなんと、 Try
Finally
句へ変貌するというのです 。嘘みたい。
実際、 C# にも同様の機能を持つ using
というステートメントがありますが、このリファレンスには次のような説明がされています。
using ステートメント (C# リファレンス) | Microsoft Docs
using ステートメントを使うと、オブジェクトでのメソッドの呼び出し中に例外が発生した場合でも Dispose が必ず呼び出されます。 オブジェクトを try ブロックに配置し、finally ブロックで Dispose を呼び出しても、同じ結果が得られます。実際には、コンパイラは using ステートメントをこのように変換します。 前のコード例は、コンパイル時に次のコードに展開されます (オブジェクトのスコープの範囲を定義する中かっこが加えられています)。
{ Font font1 = new Font("Arial", 10.0f); try { byte charset = font1.GdiCharSet; } finally { if (font1 != null) ((IDisposable)font1).Dispose(); } }
C# がこのようにコンパイルするのですから、 VB も必然的に同じような動作になるのでしょうね。Try
Finally
を使うのですから、 Using
も構造化例外処理の仲間となっていまいます。
何がおかしいのか
先程の私の紹介でもそうでしたが、改めて言語仕様やリファレンスをよく読むと、構造化例外に Using
が出てきていません。
むしろ、
Finally セクション内のコードは、Catch ブロック内のコードが実行されたかどうかに関係なく、常に最後 (エラー処理ブロックがスコープを失う直前) に実行されます。 Finally セクションには、クリーンアップ コード (ファイルを閉じたりオブジェクトを解放したりするコードなど) を配置します。 例外をキャッチする必要はないけれども、リソースをクリーンアップする必要がある場合、Finally セクションではなく、Using ステートメントを使用します。 詳細については、「Using ステートメント (Visual Basic)」を参照してください。
とあり、 Using
は構造化例外ではないかのような書き方がされています 。
じゃあ、 On Error
と Using
は同時に使えるのか? という疑問が生まれます。
こうなったら、実際どうなるのか試すしかありませんね。
テストコード
Module Module1 Sub Use_Try_Finally() Dim objA As DisposableObject = New DisposableObject("A") Try Dim objB As DisposableObject = New DisposableObject("B") Try Dim objC As DisposableObject = New DisposableObject("C") Try Debug.Print("すべてインスタンス化されました") Catch ex As Exception Debug.Print(ex.Message) Finally objC.Dispose() End Try Catch ex As Exception Debug.Print(ex.Message) Finally objB.Dispose() End Try Catch ex As Exception Debug.Print(ex.Message) Finally objA.Dispose() End Try Debug.Print("すべてDisposeされました") End Sub Sub Use_Using_OnError() On Error Resume Next Using objA As DisposableObject = New DisposableObject("A") Using objB As DisposableObject = New DisposableObject("B") Using objC As DisposableObject = New DisposableObject("C") Debug.Print("すべてインスタンス化されました") If Err.Number <> 0 Then Debug.Print(Err.Description) End Using If Err.Number <> 0 Then Debug.Print(Err.Description) End Using If Err.Number <> 0 Then Debug.Print(Err.Description) End Using Debug.Print("すべてDisposeされました") End Sub Sub Main() Debug.Print("--- Use_Try_Finally -------------") Use_Try_Finally() Debug.Print("--- Use_Using_OnError -----------") Use_Using_OnError() End Sub End Module
え?これだけじゃ実行できない?
そうです。 DisposableObject
を実装します。
なんで別クラスにしたかといえば、このクラスのほうが短くかけるので、例外とかの挙動を書きやすいかなと思ったんですね。
うん。察してくれ。
通常の挙動を見てみる
では、まず通常の挙動から。 DisposableObject
の実装は次のとおりです。
''' <summary> IDisposeableを継承するオブジェクト </summary> Public Class DisposableObject Implements IDisposable ''' <summary> インスタンスの名前 </summary> Private myName As String ''' <summary> Disposeがすでに呼ばれたか </summary> Private disposedValue As Boolean ''' <summary> DisposableObject をインスタンス化します </summary> ''' <param name="name">インスタンスのわかり易い名前</param> Public Sub New(name As String) myName = name Debug.Print(myName & "がインスタンス化されました") End Sub 'Privateにして隠蔽 Private Sub New() End Sub '内部用Disposeメソッド Protected Overridable Sub Dispose(disposing As Boolean) If Not disposedValue Then If disposing Then Debug.Print(myName & "がDisposeされました") End If End If disposedValue = True End Sub ''' <summary> リソースを破棄します </summary> Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) End Sub End Class
インスタンス時にインスタンスの名前を表示して、Disposeするときも表示するだけです。
これを実行すると、次のような結果が得られます。
— Use_Try_Finally ————-
Aがインスタンス化されました
Bがインスタンス化されました
Cがインスタンス化されました
すべてインスタンス化されました
CがDisposeされました
BがDisposeされました
AがDisposeされました
すべてDisposeされました
— Use_Using_OnError ———–
Aがインスタンス化されました
Bがインスタンス化されました
Cがインスタンス化されました
すべてインスタンス化されました
CがDisposeされました
BがDisposeされました
AがDisposeされました
すべてDisposeされました
普通ですね。
例外時の挙動を見てみる
次に例外を発生させてみます。 DisposableObject
の実装は次のとおりです。
''' <summary> IDisposeableを継承するオブジェクト </summary> Public Class DisposableObject Implements IDisposable ''' <summary> インスタンスの名前 </summary> Private myName As String ''' <summary> Disposeがすでに呼ばれたか </summary> Private disposedValue As Boolean ''' <summary> DisposableObject をインスタンス化します </summary> ''' <param name="name">インスタンスのわかり易い名前</param> Public Sub New(name As String) myName = name 'Cのときは例外 If myName = "C" Then Throw New Exception("例外!!!!") Debug.Print(myName & "がインスタンス化されました") End Sub 'Privateにして隠蔽 Private Sub New() End Sub '内部用Disposeメソッド Protected Overridable Sub Dispose(disposing As Boolean) If Not disposedValue Then If disposing Then Debug.Print(myName & "がDisposeされました") End If End If disposedValue = True End Sub ''' <summary> リソースを破棄します </summary> Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) End Sub End Class
インスタンスを作るときに名前が「C」だった場合、エラーになるというだけの分岐が一つ増えただけです。
これでどうなるでしょうか。
— Use_Try_Finally ————-
Aがインスタンス化されました
Bがインスタンス化されました
例外がスローされました: ‘System.Exception’ (vb_using_test.exe の中)
例外がスローされました: ‘System.Exception’ (vb_using_test.exe の中)
例外!!!!
BがDisposeされました
AがDisposeされました
すべてDisposeされました
— Use_Using_OnError ———–
Aがインスタンス化されました
Bがインスタンス化されました
BがDisposeされました
AがDisposeされました
すべてDisposeされました
例外の情報が・・・消えた!
Try-Catch
のほうは、問題なく例外を捕捉できています。
OnError Resume Next
と Using
のほうは、エラーを出すロジックはいくつもあるというのに、それらのどこでも例外の情報が捕捉できていません。
これは一大事です。
例外を補足できるようにする
後者で例外を補足できるようにするためには、 Use_Using_OnError
を次のように修正する必要があります。
Sub Use_Using_OnError() On Error Resume Next Using objA As DisposableObject = New DisposableObject("A") Using objB As DisposableObject = New DisposableObject("B") Using objC As DisposableObject = New DisposableObject("C") Debug.Print("すべてインスタンス化されました") If Err.Number <> 0 Then Debug.Print(Err.Description) End Using If Err.Number <> 0 Then Debug.Print(Err.Description) End Using If Err.Number <> 0 Then Debug.Print(Err.Description) End Using If Err.Number <> 0 Then Debug.Print(Err.Description) Debug.Print("すべてDisposeされました") End Sub
先ほどとのコードの違いは、 最後の End Using
のあとに例外処理を入れているだけ です。
これで、どのような結果になるでしょうか。
— Use_Using_OnError ———–
Aがインスタンス化されました
Bがインスタンス化されました
BがDisposeされました
AがDisposeされました
例外!!!!
すべてDisposeされました
やっと例外が取得できました。
先ほどのコードを比較すると、 Resume Next
と Using
を併用したコード内で例外が発生したとき、一番外側の Using
の外側でないと取得できないという結果になりました。
なお、 On Error Goto
を使用しても、 Resume Next
同様、一番外側の Using
の外側まで抜けたあと GoTo
先へジャンプするようになっています。
まとめ
結果的に何が言いたいかといえば、
Using
は 構造化例外処理 であり、 On Error
と併用できないはずが なぜかできでしまう
ということです。そして、
併用してしまうと 必ず一番外側の Using
まで抜けてしまう
のです。さらに、
この情報が どのリファレンスにも明記されていない のです 。
というか、ネットで探しても全く出てこない。
英語が読めないので英語のサイトは全く読んでいないのですが、少なからず日本語のサイトではこの現象を取り上げているサイトが少ない。というかない。
一応、私が実際に出会った問題として書いておきますが、この現象について参考になるサイトを知っている方がいたら、ぜひ教えていただきたいと思います。
長文になってしまいましたが、最後までお付き合い頂きありがとうございました。
ちなみに私は Form.ShowDialog()
するとき using
使ってるコード見ると指が勝手に Delete キーを あっごめんなさい嘘です…
AVT-C878 というキャプチャボードが認識してくれない話
ついに買いました。Splatoon2に向けて。
巷で “最強” とも称されるキャプチャボード
随分と物騒な噂ですが この程、Nintendo Switchをつなげて録画をしようとこんなものを購入しました。
AVerMedia Live Gamer Portable 2 AVT-C878 ゲームの録画・ライブ配信用キャプチャーデバイス DV422
- 出版社/メーカー: AVERMEDIA
- 発売日: 2016/11/09
- メディア: Personal Computers
- この商品を含むブログを見る
こちら。USBでつないで、1080p/60fpsで録画ができるという素晴らしいもの。
という謳い文句を 鵜呑みにして 買いました。
前から何度も店に足を運んではいろんなキャプチャボードを見てきましたが、値段的にも扱い方的にもこれがいいのかなと思って購入しました。
ガチなボードだとパソコンを起動しないといけないという手間もありますしね、これがいいかなと。
何度も言います。 これがいいかな、と思って。
思い込みで買うのは本当に怖いものですね。
人生の99%は思い込み―――支配された人生から脱却するための心理学
- 作者: 鈴木敏昭
- 出版社/メーカー: ダイヤモンド社
- 発売日: 2015/05/29
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
買ってつなげて初めて分かるもの
PC録画の推奨環境が 「Core i5-3xxx 以上 / NVIDIA GeForce GTX 650同等以上」 とあるので、結構なスペックが要求されるようです。
私が持っているPCの中ではメイン機だけが使えそう。
OS: Windows10
CPU: Core i7-6700
メモリ: 16GB
グラボ: NVIDIA GTX 1050
使えそうというか、これ絶対動くでしょ、って感じで甘く見てました。
……。
認識しない 。
PCとUSBでつなぐと、電源が入る。HDMIパススルーが機能して、普通にゲームも遊べる。
しかし、PC側にはなんの反応もない。いや、実際には少しだけ認識したけど、認識できないデバイスと言われ、ドライバにも黄色い三角マーク。
仕方なくドライバを削除して再起動。とりあえず標準ドライバを使うとWebカメラとして認識するみたいなので、それでやってみようと試みる。
が、何分経っても青点滅(準備中)が消えない。何度抜き差ししてもだめ。全く認識しない。
死亡フラグが立ちました! (宝島社文庫) (宝島社文庫 C な 5-1)
- 作者: 七尾与史
- 出版社/メーカー: 宝島社
- 発売日: 2010/07/06
- メディア: 文庫
- 購入: 4人 クリック: 532回
- この商品を含むブログ (44件) を見る
Ubuntuならどうか
サブ機(サーバー)のUbuntuにつなげてみて、Webカメラとして使えたら使おうと考えた。
グラボが載っていないので、もし認識したら安めのものを買ってきてつけようかとも考えた。
しかし、全く同じ状態が続く。青点滅が消えない。
挿した瞬間、HDDアクセスを示すLEDランプが一瞬光るので繋がった認識はしてるんだろうけど、そこから先動いてるように見えない。
OBSとかも全く反応してくれない。
カードリーダも使えない
AVT-C878はカードリーダーモードも備えていて、普通にmicroSDの中身を読み書きできるそう。
なのに それも動いてくれない 。やっぱり青点滅が消えない。
完全にUSBでPCとやり取りができていない。AVT-C787側も初期化が終わらないってことは、完全にハンドシェイクが切れてる。万事休す。
無理やりFWアップデート走らせたせい?その前から現象はあるけど…
ファームウェアが古いせいなのかなとか思って、microSDにデータをダウンロードして無理やりアップデート。
マニュアルにはカードリーダモードで転送するよう書いてあったけど、前述の通り使えないので、普通に別のカードリーダで転送。
入れて起動すると自動的にアップデートが始まって、 ゆっくり青く点滅 するらしい。が、実際にやってみるとなぜか 素早く青く点滅 していた。
アップデートが終わると自動でランプが消灯すると書いてあって、実際しばらくしたら消えたので、たぶんアップデートできてるはず。
実際バージョン情報見るには、後述のソフトがAVT-C878を認識しないといけないから、結局どうなったのかわからないんだけど…。
- アーティスト: クリフォード・ジョーダンClifford Jordan
- 出版社/メーカー: THINK! REOCRDS
- 発売日: 2017/07/05
- メディア: CD
- この商品を含むブログを見る
RECentral すら起動しない
連携ソフトというか、無料で配布されているキャプチャソフト兼コンフィグレーションソフトの RECentral なるものがあるのですが。
これ、起動すると5秒もしないうちに落ちる。
ウィンドウは出てくるけど、全く何も使えない。
それどころかボードを認識している様子すらない(Windowsが認識してないから当たり前か…)
調べてみると確かに、同じような現象に陥っている人はいるみたい。再インストールすれば治る人もいるけど、私みたいに全く治らない人もいそう。
単体録画は正常
これだけPC録画がボロボロなのに、単体録画はしれっと何事もなかったかのようにできている。
空のmicroSDを挿して、PCのUSBから電源だけ拝借する。MOVなので画質は粗めだけど、解像度はちゃんと1080p出てる。
ハードエンコードなので圧縮とか高度なことはしてくれないのですぐ容量なくなってしまうけど、とりあえず当面は使えるレベル。
とはいえもともとPC録画を目的に買ったから本末転倒って感じがする。。。
結局
しばらく単体モードでやりますが、そのうちお金に余裕ができたら別のボード買うかもね…。うーん辛い。
安全・サイン8 熱中飴 塩辛すっぱいレモン味 1kg(約200粒入り) CN3007-L
- 出版社/メーカー: つくし工房
- メディア: その他
- この商品を含むブログを見る
お兄ちゃん!そこは MemoryStream の出番だよ!
タイトルは釣りです(お約束)
MemoryStream のススメ
みなさん、 System.IO.MemoryStream
使っていますか。私はよく使いますよ。
MemoryStream Class (System.IO) | Microsoft Docs
リアクティブプログラミングだったり、Java の Stream API だったり、いろんな Stream がありますが、今回はC#の MemoryStream
に注目してみます。
MemoryStream のイロハ
そもそもC#には Stream
クラスがあり、 MemoryStream
はその派生クラスです。同じような派生クラスには FileStream
や CryptoStream
があります。
似て非なるものですが、これらは共通して データを順次読み出したり、順次格納したりできる という特徴を持っています。
たとえば FileStream
は、ディスク上のファイルを読み書きするクラスです。ファイルを開くと FileStream
にはそのファイルサイズと カーソル位置 が保持されます。
カーソル位置は Stream
のデータを操作する位置 を表します。これを使うと、例えばファイルの80バイト目から60バイト分読み出したい、といった場合に、カーソル位置を80バイト目に移動させ、そこから60バイト分読み出すことができます(同時にカーソルも60バイト動くため、次に60バイトを読み出すと140バイト目から60バイトを読み出します)。
これは、次のように書くことができます。
// ファイル名 を元に FileStream を作成 var stream = new FileStream("C:/hoge.txt", FileMode.Open); // 80バイト目から60バイト読み出す var puts = new byte[60]; stream.Position = 80; stream.Read(puts, 0, 60);
同様に MemoryStream
も、ある byte[]
のサイズ(配列の長さ)とカーソル位置が保持されます。
そして、例えばある byte[]
の80バイト目から60バイト分読み出したい、といった場合に、カーソル位置を80バイト目に移動させ、そこから60バイト分読み出すことができます。
これは、次のように書くことができます。
var array = new byte[300]; // --- ここに本来はデータの操作が入る --- // // array を元に MemoryStream を作成 var stream = new MemoryStream(array); // 80バイト目から60バイト読み出す var puts = new byte[60]; stream.Position = 80; stream.Read(puts, 0, 60);
なお、以下の配列へのアクセスをするコードで、同様の puts
を得られます。
var array = new byte[300]; // --- ここに本来はデータの操作が入る --- // // 80バイト目から60バイト読み出す var puts = new byte[60]; for (int i = 0; i < 60; i++) puts[i] = array[80 + i];
え、じゃあ何に使うん 。
MemoryStream は byte[]
へのアクセスを簡単にします。本当に?
ひどい夢を見ました。すべての byte[]
配列へのアクセスを、 MemoryStream
を通じて行うようにするという、お達しが出たのです。まったく、とんだ災難です。このプロダクトは文字列や数値としては扱えないデータが山ほどあり(画像や音声、もしかしたら地球外生命体のDNAの解析結果かもしれない)、それらはすべて byte[]
で表すことになっています。だから、それら全てのアクセスを、 MemoryStream
に置き換えなければなりません。たった1要素の読み込みでさえ、長ったらしく2,3行を書き連ねなければならないのです。
実際にそんなことがあるはずはありません。安心してください。 ところで次の例を見てくれ、こいつをどう思う?
// AESで暗号化するためのオブジェクトを初期化 var aes = new AesManaged(); aes.GenerateIV(); aes.GenerateKey(); // MemoryStreamを作成 var memStream = new MemoryStream(); // CryptoStreamを作成 var cryStream = new CryptoStream(memStream, aes.CreateEncryptor, CryptoStreamMode.Write); // CryptoStreamに書き込み cryStream.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5); // MemoryStreamから読み出し var read = byte[3]; memStream.Position = 1; memStream.Read(read, 0, 3);
AesManaged Class (System.Security.Cryptography) | Microsoft Docs
CryptoStream Class (System.Security.Cryptography) | Microsoft Docs
ちょっと難解ですが、 ある MemoryStream
を参照する CryptoStream
にデータを書き込むと、 MemoryStream
に暗号化されたデータが書き込まれる コードです。
上記の例では read
に暗号化されたデータの一部が代入されることになります。
さてこれをちょっとだけ改変しましょう。
// AESで暗号化するためのオブジェクトを初期化 var aes = new AesManaged(); aes.GenerateIV(); aes.GenerateKey(); // FileStreamを作成 var filStream = new FileStream("C:/hoge.enc", FileMode.Create); // CryptoStreamを作成 var cryStream = new CryptoStream(filStream , aes.CreateEncryptor, CryptoStreamMode.Write); // CryptoStreamに書き込み cryStream.Write(new byte[] { 1, 2, 3, 4, 5 }, 0, 5);
だいたい想像がつくと思いますが、これは ある FileStream
を参照する CryptoStream
にデータを書き込むと、 FileStream
に暗号化されたデータが書き込まれる(暗号化されたデータがファイルに書き込まれる) コードです。
驚くべきことに、このコードを先ほどのコードと比べると、 MemoryStream
を FileStream
にすり替えただけなのです。
MemoryStream は世界を救う
つまり、 MemoryStream
は、 byte[]
を FileStream
、すなわち 変数操作とファイル操作と同等に扱えるようにするクラス ということなのです。
C# では、とくにデータの変換系の処理を Stream
で行うような風潮があるように見えます。
例えば暗号化や、巨大なバイナリファイルの符号化など。JSONのシリアライズにも Stream
クラスを引数に受けるメソッドを用います。
こうすることで、対象がファイルでもメモリでも、同じ操作でデータを扱えるという素晴らしい恩恵を享受することができます。
単体テストもコードの再利用もどんとこい、な機能ですね。
さいごに
素人が生意気にすみませんでした 。ちょっと魔がさして書き始めたら収拾がつかなくなってしまいました。申し訳ありません。反省はしていません。
この間 JSON をC#のクラスにシリアライズしたりデシリアライズするときに MemoryStream
を使う機会があったのですが、正直な話
なんでクラスで管理できる情報量をわざわざ MemoryStream
で書く必要があるのか、変数でいいのではないか
と思いながらバリバリ書いてたんですね。
そしてふと、思いついたんですよね。JSON ってファイルの可能性があるよなぁ、と。
自分の中ではとても面白い発見だったので、ついつい長ったらしく書いてしまいました。本当に申し訳ありませんでした。
最後まで見てくださって本当にありがとうございました。
P.S.
本来書きたかったネタもとりあえず置いておきます。 MemoryStream
の Read
メソッドが超絶使いにくい件について。
public static class MemoryStreamExtention { public static byte[] ReadAllBytes(this System.IO.MemoryStream target) { byte[] ret = new byte[target.Length]; target.Position = 0; target.Read(ret, 0, (int)target.Length); return ret; } }
.NET core コンソールアプリで文字化けするときの対処法
Main
メソッドの一番頭にこれを書く。
Console.OutputEncoding = Console.OutputEncoding;
何をしているのか
.NET core コンソールアプリでは、規定で Console.OutputEncoding
に System.Text.UTF8Encoding
のインスタンスが入っています。
しかし、コンソール側(Windowsでデバッグした場合は規定でコマンドプロンプト)がこれをまだ検知していない状態のため、UTF-8でないコードページで表示をしています。
そのため、一度プロパティのsetterを通す必要があります。
…という推測を立てただけです。すみません。
Ubuntuのtaskselを使って、ラクしてKVM環境を作る
Linuxで楽しよう。Linuxを楽しもう。(名言っぽく言ってみるだけ)
ふと tasksel
でxubuntu desktopを選ぼうとすると、そこにそれらしき項目があったので、使ってみた。その時のメモ。
基本、シェルはbashでroot権限で行きます。 sudo
はつけないので、 su
したくない方は sudo
をつけて実行してください。
PCが仮想化に対応しているか確認する
KVMは完全仮想化をCPUの仮想化技術で実現しているようなので、次のコマンドで確認する。
# egrep -c '(vmx|svm)' /proc/cpuinfo
出力が1以上なら仮想化できます。
パッケージをインストールする
とりあえずKVM環境を動かすためのライブラリ群を入れます。次のコマンドを打ちます。
# tasksel
GUIっぽい画面が出てくるので、一覧から「Virtual Machine Host」に スペースキー でチェックを入れ、 エンターキー で続行します。
あとは待つだけ。これが終わると、仮想化に必要なものがほとんど入ります。
ただし、GUIで設定できる「virt-manager」はあいにく入れてくれないので、これだけは手動で入れます。
# apt install virt-manager
これがあれば、Virtual BoxみたいにGUIで簡単に操作ができるようになります。
ネットワークインターフェイス(NIC)をブリッジする
なんか、皆さんこぞってブリッジしているので、ブリッジしたほうが幸せなのかな?と半信半疑でブリッジしました。
一応パッケージをチェック。
# apt list | grep bridge-utils
[インストール済み] と末尾に表示されていなければ、インストールします。
# apt install bridge-utils
次に、ブリッジするよう /etc/network/interfaces
を編集
Ubuntuは最近、規定のNICのインターフェイス名が環境によって変わるようになったので、適宜読み替えてください。
auto enp0sXXXX iface enp0sXXXX inet manual auto br0 iface br0 inet static address 192.168.XXX.XXX network 192.168.XXX.0 netmask 255.255.255.0 gateway 192.168.XXX.XXX dns-nameservers XXX.XXX.XXX.XXX bridge-ports enp0sXXXX bridge-stp off
元あった規定NICのインターフェイスの設定はすべてコメントアウトか削除かしておきます。その設定を、br0側にすべて書きます。
この後、再起動して ifconfig
でbr0側にIPアドレスなどが定義されていることを確認。
参考サイト
aptでインストールした残骸を確認する
完璧に備忘録ですが。
パッケージの一覧を取得する
# apt list
パッケージの一覧が表示されます。インストール済みかそうでないかにかかわらず、データベースに登録してあるパッケージはすべて出ます。
たぶんこの一覧の中からパッケージの情報を取り出すんだと思います。
設定が残る
上記のコマンドで表示した一覧で、末尾に次のような表示がされることがあります。
この「設定が残存」があると、再インストール時に --update
フラグとかが自動でつけられたりして、クリーンインストールはしないようです。
設定を消す
# apt purge <パッケージ名> # apt remove --purge <パッケージ名>
どちらでもいいみたい、
Ubuntu16.10でIPを固定しDNSを指定するのに、2時間かけました。
Ubuntuのことをあまり知らずに挑むからダメなんですよね。
とはいえ、IPを固定するくらいはいくらシステムの根幹に近いカスタマイズとはいえ、もうちょっと簡単でもいいと思うんですよ…
変更した箇所
- /etc/network/interfaces
- /etc/hosts
- /etc/NetworkManager/NetworkManager.conf
- /etc/resolvconf/resolv.conf.d/tail
いちおうこれらは確実に変更してあります。
ほかにも触ったところあったかもしれないけど、毎回Ubuntuマシン触るときに悩まされる種でもありイライラしていたので覚えていない…。これだからブログ向いてないんですよね。知ってます。
※なお、これから同じような設定をされる方は、ほかの方の記事やしっかりとした情報を一通り見てから実際に設定されることを強くお勧めします。
/etc/network/interfaces
auto lo ifacw lo inet static
が初期設定。これを次のように変更します。
auto enpX iface enpX inet static address <固定するアドレス XXX.XXX.XXX.XXX> netmask <サブネットマスク XXX.XXX.XXX.XXX> gateway <デフォルトゲートウェイ XXX.XXX.XXX.XXX> dns-nameservers <DNSサーバ XXX.XXX.XXX.XXX>
enpX
はNICのアドレスのようなもので、 ifconfig
コマンドで確認ができます。
lo
から enpX
に変更するので、 ifconfig
を走らせたときに lo
と同じような位置に出てくるやつがそれです。
なおDNSサーバプライマリとセカンダリをスペースで区切って指定できるようです。
/etc/hosts
変更する意味があるかと言われれば、もしかしたらないかもしれないですが、一応変更します。
127.0.0.1 localhost 127.0.1.1 <コンピュータ名> # The following lines are disirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback ・ ・ ・
最後のほうは面倒なので端折りました。
ここに出てくる 127.0.1.1
を変更します。
127.0.0.1 localhost <固定するアドレス> <コンピュータ名> # The following lines are disirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback ・ ・ ・
/etc/NetworkManager/NetworkManager.conf
悪党を倒しに行きます(?)
[main] plugins=ifupdown,keyfile,ofono dns=dnsmasq [ifupdown] managed=false
これを、以下のように直します。
[main] plugins=ifupdown,keyfile,ofono #dns=dnsmasq [ifupdown] managed=false
これで悪党は目覚めないことでしょう。きっと。
/etc/resolvconf/resolv.conf.d/tail
最後に強制的にDNSを変えます。もう最終手段です。
このファイルは、私の環境では入っていなかったため、自分で作りました。
そして以下のように記述します。
nameserver <DNSサーバ XXX.XXX.XXX.XXX (プライマリ)> nameserver <DNSサーバ XXX.XXX.XXX.XXX (セカンダリ)>
果たして /etc/network/interfaces に記述した dns-nameservers
に意味はあったのだろうか。
そして最後、 resolv.conf を生成します。
$ sudo resolvconf -u
再起動
最後に再起動すれば、IPアドレスは固定になり、DNSサーバも変わっているはず。
時間がかかった。
結局、表題の通りすべての帳尻合わせするため 2時間 以上はかけました。はい。
IPアドレスを固定するために /etc/interfaces を書き換えている方がたくさんいたのですが、それだけだとなぜかうまくいかなくて。
試行錯誤を重ねてやっと固定された感じです。つらい。
やっぱりここらへんはCentOSのほうが楽なんでしょうかね。。。