echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

C#でCOMを使ってみた。その3

何か、前回から随分間が空いてしまいました。
実家に帰ってたり、仕事のことで悩んでたりで、色々ありまして…すいません。

さて、DirectSoundの件ですが、とりあえず一通りネイティブなDirectXを使用したコードも完成して、コンパイルも通った。
あとは実行&デバッグだと思い、デバッグ実行させたら、こんな実行時エラーが。

failed: System.InvalidCastException : 型 'idirectSoundCaptureBuffer8' の COM オブジェクトをインターフェイス型 'IDirectSoundCaptureBuffer8' にキャストできません。
IID '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}' が指定されたインターフェイスのCOM コンポーネント上での QueryInterface 呼び出しのときに次のエラーが発生したため、この操作に失敗しました: インターフェイスがサポートされていません
(HRESULT からの例外: 0x80004002 (E_NOINTERFACE))。

キャスト出来ない?
ていうか、インスタンス作成&キャスト自体はこれよりだいぶ前の処理で、問題なく成功しているのに…

とりあえず、

  • 念のためインスタンス作成&キャスト箇所のIID間違いやポインタ確保&解放をチェックしたけど、全く問題なし。
  • レジストリのあるキーの値が壊れているとそういうエラーが発生するらしいけど、それも問題なし。
  • メソッドに[STAThread]属性をつけると直る事があるらしいけど、特に変化なし。(ただ、結果的にはこれに関連する事項だったんですが。)

で、ちょっとハマっていたんですが、下記ブログにそのものズバリの答えが書いてありました。感謝感激です!
architect360.apricot-jp.com

なるほど、要は「COMオブジェクトは(例えクラスメンバ変数などにしたとしても)作成したスレッドとは別のスレッドでそのまま使用することは出来ない」ということですね。
僕のプログラムでは、COMオブジェクトのインスタンスをメインスレッドで作成して、それを別スレッドの音声キャプチャ処理でそのまま使用していたのですが、それが原因だったようです。

なので、音声キャプチャ処理のスレッド内でキャプチャ開始直前にCOMオブジェクトを作成するように変更したら、このエラーは出なくなりました。

まあ、この後も色々あるんですけどね。それはまた後日。


しかし、仕事…というか会社、どうしようなあ。さっさと行動すべきかなあ。
もちろん、ITエンジニア自体を辞めるつもりなんて、さらさらありませんけどね。

C#でCOMを使ってみた。番外編

とりあえず、今日から夏休みです。
取得するのに、相当紆余曲折がありましたが…
(というか、話せば話すほど社長は僕は水と油だということを感じる。色々良くないことも多くなってきたし、試用期間までが潮時、って言うか、それすらも持たないだろうな…)

…てな話はともかく、今回は番外編というか、本当に備忘録のような内容です。


C#C++の型

ネイティブC++の関数を呼び出すとき、ちゃんとC#側で対応する引数の型を指定しないといけないけど、主な型の対応はこんな感じ。(とりあえず、関係しそうなものだけ)

C++C#
WORDshort
DWORDuint
LPSTRstring
LPWSTR
LPCSTR
LPCWSTR
void*IntPtr
HANDLE
LPARAM
WPARAM
HRESULTint
MMRESULT


ネイティブC++関数呼び出し時の引数指定

MSDNなどに記載してあるネイティブC++関数呼び出し時の汎用的なルール。
※あくまで「汎用的」であって、これに当てはまらないケースも当然ある。

C++での引数指定C#での引数指定
純粋な「型名」のみ(例:DWORD、WORD)対応する型の「値渡し」(ref、outキーワードなし)
接頭語「LP」+「型名」(例:LPWORD、LPDWORD)対応する型の「参照渡し」(ref、outキーワードあり)
ポインタ渡し(例:LPWORD*、LPDWORD*)IntPtr型渡し
※コールバック関数を指定する場合は「C#でコールバック関数を使う」を参照のこと。


配列のポインタ

関数に構造体配列のポインタを渡す場合、例えばC++だと

STRUCTURE[] structure = new STRUCTURE[2];
func(&structure);

みたいな書き方でOKだが、C#で同じことをやろうとして、例えば

STRUCTURE[] structure = new STRUCTURE[2];
int str_size = Marshal.SizeOf(structure[0]) + Marshal.SizeOf(structure[1]);
IntPtr ptr_structure = Marshal.AllocCoTaskMem(str_size);
Marshal.StructureToPtr(structure, ptr_structure, false);
func(ptr_structure);

とやっても、コンパイルエラーになってしまう。

で、これをうまくやる方法を調べたのだが、結局は原始的な方法かも知れないが、1要素目のptr_structureの値と各要素のサイズを計算して

STRUCTURE[] structure = new STRUCTURE[2];
int str_size = Marshal.SizeOf(structure[0]) + Marshal.SizeOf(structure[1]);
IntPtr ptr_structure = Marshal.AllocCoTaskMem(str_size);
Marshal.StructureToPtr(structure[0], ptr_structure, false);
Marshal.StructureToPtr(structure[1], (IntPtr)((int)ptr_structure + Marshal.SizeOf(structure[0])), false);

みたいにするしか無いみたい。(もちろん、要素数が多いならループなどを使用するべき。)


構造体の「union」をC#で実装する方法

例えば、DirectSoundなどで使用する、WAVEFORMATEXTENSIBLE構造体は、

typedef struct {
  WAVEFORMATEX  Format;
  union {
    WORD  wValidBitsPerSample;
    WORD  wSamplesPerBlock;
    WORD  wReserved;
  } Samples;
  DWORD   dwChannelMask; 
  GUID    SubFormat;
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;

となっている。

この構造体の「union」に該当するキーワードはC#にはないが、「StructLayout」と「FieldOffset」の指定をうまく使うことで、構造体のunionと同じことを行うことが出来る。

// FieldOffsetを自分で指定するので、StructLayoutはLayoutKind.Explicitにしないといけない。
[StructLayout(LayoutKind.Explicit)]
private struct WAVEFORMAT_EXTENSIBLE
{
    // wValidBitsPerSample,wSamplesPerBlock,wReservedのFieldOffsetを同じにして「union」と同じことを実現している。
    [FieldOffset(0)]
    public WAVEFORMATEX Format;
    [FieldOffset(20)]
    public ushort wValidBitsPerSample;
    [FieldOffset(20)]
    public ushort wSamplesPerBlock;
    [FieldOffset(20)]
    public ushort wReserved;
    [FieldOffset(22)]
    public uint dwChannelMask;
    [FieldOffset(26)]
    public Guid SubFormat;
}


…すいません。ちょっと手抜き感が否めないですが、今回はこれでご勘弁を。

C#でCOMを使ってみた。その2

どうしても今月中に役所へ行かなければならないので、申し訳ないと思いながらも12日に休暇申請したら「正直、よくそんな面の厚いことが出来るな、という感じだけどね」みたいな事を言われた。
バイ、これは早急に身の振り方を考えないと。


…というわけで、その1の続きです。


COMインターフェースのインスタンス作成・キャスト

「コクラスが持つインターフェースにアクセスする」場合や「定義したCOMインターフェースと互換性がある」場合は、メソッドなどを実行しなくても、キャストを実行するだけでCOMインターフェースのインスタンスを作成できます。

DirectSoundCapture8Class DSC = new DirectSoundCapture8Class();
IDirectSoundCapture8 iDSC = (IDirectSoundCapture8)DSC;

ただキャストするだけではエラーになる場合は(ネイティブ側のCOMインターフェースポインタを受け取る場合など)、MarshalクラスのGetTypedObjectForIUnknown()メソッドの戻り値をキャストすればOKです。(これを発見するのにすごく時間がかかった…)

public static object GetTypedObjectForIUnknown(
  IntPtr pUnk,    // ネイティブ側のCOMインターフェースポインタ
  Type t          // (COMインターフェースなどの)型
)


// ptr_iDSCB8にネイティブ側のCOMインターフェースポインタが格納されているものとする。
IDirectSoundCaptureBuffer8 iDSCB8 = (IDirectSoundCaptureBuffer8)Marshal.GetTypedObjectForIUnknown(ptr_iDSCB8, typeof(IDirectSoundCaptureBuffer8));


ネイティブ側のCOMインターフェースポインタの取得

ネイティブ側のCOMインターフェースポインタ取得ですが、

1.MarshalクラスのQueryInterface()メソッドを使う方法
C++での方法ですね。

public static int QueryInterface(
    IntPtr pUnk,    // 取得したいCOMインターフェースの取得をサポートしているCOMインターフェースのポインタ
    ref Guid iid,   // 取得したいCOMインターフェースのIID
    out IntPtr ppv  // 取得したいCOMインターフェースのポインタが格納される
)


// ptr_iDSCB8にIDirectSoundCaptureBuffer8インターフェースポインタが入っていて、
// 取得したIDirectSoundNotify8インターフェースポインタをptr_iDSN8に格納したい場合。
// guidにはIDirectSoundNotify8インターフェースポインタのIIDが格納されているものとする。
int getNotifyResult = Marshal.QueryInterface(ptr_iDSCB8, ref guid, out ptr_iDSN8);
IDirectSoundNotify8 iDSN8 = (IDirectSoundNotify8)Marshal.GetTypedObjectForIUnknown(ptr_iDSN8, typeof(IDirectSoundNotify8));


2.普通にインターフェースポインタを取得できるネイティブ側のメソッドを使用する。

// (例)DirectSoundのDirectSoundCaptureCreate8()メソッドを呼び出した場合。
// guidにIDirectSoundCapture8インターフェースのIIDを渡すと、正しく処理が行われた場合、ptr_iDSC8に
// IDirectSoundCapture8インターフェースポインタが格納される。(第三引数はnull固定。)
int createCaptureResult = DirectSoundCaptureCreate8(ref guid, out ptr_iDSC8, null);
IDirectSoundCapture8 iDSC8 = (IDirectSoundCapture8)Marshal.GetTypedObjectForIUnknown(ptr_iDSC8, typeof(IDirectSoundCapture8));

う~ん、ざっと説明してきましたが、分かりにくい内容になってしまったような…申し訳ないです。


【追記】
冒頭の件、別に「5連休にしたい!」とかそういう意図ではなく「(どうせ有休で休む人が多いんだし、他の人にあわせて)休むときは休む、働くときは働く」っていう考えだったからこの日にしたんだけど…
それを「面の皮が厚い」とか「他の日にしない理由が見つからない」とか言われるのであれば、真剣に考えないとねえ。

C#でCOMを使ってみた。

ここにも少し書いたんですが、結局ネイティブなDirectXC#からうんたらかんたらする必要があったので、備忘録がてらメモ。

さて、DirextXはCOMという事ですが、C#でCOMを使う場合、以下の手順が必要です。

COM定義の変換

COMオブジェクトをC#で使用する方法の一つに「COM定義の変換」がありますが、これは「コクラス」を作成することで対応できます。
コクラスの作成は、例えばDirectXの「DirectSoundCapture8」を使用する場合、

[Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx")]
[ComImport]
private class DirectSoundCapture8Class
{
}

というように、[ComImport]や[Guid]属性を記載したクラスを作成すればOK。(Guidの値は、ヘッダーファイル(今回はdsound.h)などから調べる必要があります。)

COMインターフェースの変換

で、COMインターフェースはというと、例えばIDirectSoundCapture8インターフェースの場合、

[Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IDirectSoundCapture8
{
    [PreserveSig]
    int CreateCaptureBuffer(ref DSCBUFFERDESC pcDSCBufferDesc, out IntPtr ppDSCBuffer, [MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter);
    [PreserveSig]
    int GetCaps(ref DSCCAPS pDSCCaps);
    [PreserveSig]
    int Initialize(ref Guid pcGuidDevice);
}

のように、[Guid]や[InterfaceType]属性を指定したインターフェースを作成すればOK。

あと、これらのメソッドがエラーとなる値を返した場合、デフォルト設定では例外(System.COMException)が発生するので(例外のエラーコードで結果が分かる)、それを回避する場合は[PreserveSig]属性を付けてメソッドの戻り値としてエラーコードを受け取るようにします。

なお

  • メソッドは「IUnknown」及び「IDispatch」メンバ以外の全てのメソッドを定義しなければならない。
  • メソッドの記載順は必ずヘッダファイルと同じ順番でなければならない(MSDNなどの記載順とは異なる)

ので注意。



…すいません。ちょっと時間がないので、続きはまた後日に。


【追記】
今日、VS起動中にPCがフリーズしたので再起動したら、メインの.csファイルが真っ白に…
ていうか、何とかならないのかな、これ。VSの自動バックアップって、保存した瞬間にバックアップがなくなるから、保存後にフリーズするとどうしようもないし。

…やはり、こまめにバックアップとるしか無いのか?というより、そんなに頻繁にフリーズするPCが問題?

スキルアップ

スキルアップについて、気になる記事があった。

paiza.hatenablog.com

 

約7割の方が「プライベートでの勉強時間は週5時間以上」ですか。

てことは、どれだけ残業しても、帰宅後1時間は勉強に費やしてるって事ですよね。

また週末にまとめてって言う人は、土・日どちらかは大半を勉強に費やしているって事ですね。

 

僕自身は業務終了後には身も心もクタクタになっているので(オッサンになった証拠です)週末に行っているのですが、土日って何だかんだで用事が入って、満足に勉強が出来ない事も多いです。

一人暮らしだと、日用品の買い出しとかも少なく無いですし…

 

というか、この「勉強時間の確保」って、この業界に入ってからずっと僕のテーマだったりします。

これまでずっと毎日終電近くまで働いてたので、平日に勉強時間なんて取れず、週末は休まないと体が持たない(ていうか、休んでも持たない…というより、それが原因で病気になって8年間苦しんだわけですが。)ので勉強時間がほとんど取れない、でも勉強しないと将来食っていけない…というジレンマが。

 

しかも僕自身はスキルアップの勉強はやらないと…というより、むしろ「やりたい」人なんです。

元々プログラミング大好き、技術書を読むのも大好きという人間なので、もっとプログラミングの勉強がしたいのに…というジレンマをいつも抱えています。

 

ていうか、最近は「週5で働いたら勉強時間が取れない!週4勤務とか出来ないかな…」なんてことも、最近良く考えます。(時間の見つけ方が下手なだけかもしれませんが…)

 

でもやっぱり、先述のページにもあるように、仕事とプライベートの相乗効果というか、仕事が充実している人はプライベートも充実するだろうし、逆もまた然り。

プライベートの時間にやりたいことが出来て、それを仕事に昇華出来れば、絶対プラスになるだろうから、多少無理しても、勉強する時間も増やすべきなのかもしれないですが…

それでスキルを磨ければ、例えば今の働き方に不満・疑問があっても、自信を持って次の一歩を踏み出せるでしょうし。(ていうか、出来れば早めに理想を実現するために進むべき道を見つけたい。)

 

さっきのページで勉強時間が「週20時間以上」って人の時間の活用法、ぜひ勉強したいものです。(ていうか週20時間以上って、ちょっとした勤務時間のような。)

 

 

…てな事を、今日Java FXの勉強をしながら思いました。

(ていうか、スクールやら研修やら勉強やらで結構Javaってやってきたはずなんですが、なぜか「業務」として携わったことが一度もないんですよね。)

C#でコールバック関数を使う

今までManaged DirectXを扱ってきたけど、訳あってUnmanagedなコードも扱う必要が出てきた。
で、キャプチャデバイスの列挙のため「DirectSoundCaptureEnumerate」関数をコールする必要があったのだけど、MSDN見たら、

HRESULT WINAPI DirectSoundCaptureEnumerate(
  LPDSENUMCALLBACK lpDSEnumCallback,
  LPVOID lpContext
);


pDSEnumCallback
DSEnumCallback 関数のアドレス。この関数は、システムにインストールされている各 DirectSoundCapture オブジェクトに対して呼び出される。

pContext
ユーザー定義コンテキストのアドレス。列挙コールバック関数が呼び出されるたびに、このコンテキストが関数に渡される。

で、コールバック関数ってどう設定するの?
って事で調べたのでメモ。

と言っても、特別難しいわけではなかった。
…ていうか、MSDNにズバリそのまま書いてあった。
https://msdn.microsoft.com/ja-jp/library/843s5s5x(v=vs.110).aspx

まあざっくりと言ってしまえば、

手順1.デリゲートを宣言する
手順2.そのデリゲート型の変数を使用箇所で作成し
手順3.実際のコールバック関数の中身を作成し
手順4.コールバック関数(のポインタなど)を引数として必要とする関数(今回はDirectSoundCaptureEnumerate)に手順2で作成した変数を引数として渡す。

でOK。

コード:

using System;
using System.Runtime.InteropServices;   // これがデリゲート型宣言で必要	

// using Microsoft.DirectX;
// using Microsoft.DirectX.DirectSound;

namespace Sample
{
    public unsafe class SampleClass
    {
        [DllImport("dsound.dll")]
        private extern static int DirectSoundCaptureEnumerate(DSEnumCallback cb, void* lpContext);

        private delegate bool DSEnumCallback(Guid* guid, string desc, string module, void* lpContext); // 手順1

        public SampleClass()
        {
            DSEnumCallback myCallBack = new DSEnumCallback(SampleClass.deviceEnum);    // 手順2
            int retVal = DirectSoundCaptureEnumerate(myCallBack, (void*)0);            // 手順4
        }

        // DSEnumCallback関数は、デバイス列挙を続けるならtrue、終了するならfalseを返す。
        private static bool deviceEnum(Guid* guid, string desc, string module, void* lpContext)    // 手順3
        {
            if (null != guid) { return false; }
            return true;
        }
    }

…ていうか、やはりポインタやら何やら、嫌な言葉がいっぱい出てくるようになった。
これも、C++から逃げ続けてきたツケ、なのかも。

Managed DirectXを動かす その3

その2で書いた通り、次は録音ですね。

※下記サイトを参考にさせて頂きました。感謝です。
Managed DirectSoundを使ってマイクから音声を録音してみる2

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Runtime.InteropServices;
using System.Threading;	
using System.IO;	
using System.Windows.Forms;
using System.Diagnostics;

using Microsoft.DirectX;
using Microsoft.DirectX.DirectSound;

namespace Sample2
{
    public class CaptureSound : IDisposable
    {
        private WaveFormat fmt;
        private string file_path;
        private bool isRecording;
        private Device device;
        private Capture capture;

        private int notifySize;
        private static int notifyPos = 0;
        private const int NUMBER_RECORD_NOTIFICATIONS = 16;

        private BufferPositionNotify[] PositionNotify = new BufferPositionNotify[NUMBER_RECORD_NOTIFICATIONS];
        private CaptureBuffer captureBuffer;
        CaptureBufferDescription captureBufferDescription;

        private Thread notifyThread;
        private AutoResetEvent autoResetEvent;
        private Notify notify;

        BinaryWriter binaryWriter;
        private int dataLength = 0;

        // コンストラクタ
        public CaptureSound ()
        {
            this.fmt = new WaveFormat();
            this.fmt.FormatTag = WaveFormatTag.Pcm;
            this.fmt.SamplesPerSecond = 44100;
            this.fmt.BitsPerSample = 16;
            this.fmt.Channels = 1;
            this.fmt.BlockAlign = (short)(this.fmt.Channels * (this.fmt.BitsPerSample / (short)8));
            this.fmt.AverageBytesPerSecond = this.fmt.BlockAlign * this.fmt.SamplesPerSecond;

            this.isRecording = false;
            this.file_path = @"C:\Sample2\dummy.wav";
        }

        // 録音開始(ボタンクリック時など。'owner'は呼び出し元フォームなど。
        public bool startRecord(Form owner)
        {
            if (isRecording) { return false; }

            this.device = new Device();
            this.device.SetCooperativeLevel(owner, CooperativeLevel.Priority);
            CaptureDevicesCollection capDev = new CaptureDevicesCollection();

            Guid cap_guid = getGuid(capDev);
            if (Guid.Empty == cap_guid) { return "録音デバイスがありません。"; }

            this.capture = new Capture(cap_guid);
            this.isRecording = true;

            RecWave();
            CreateCapBuffer();
            this.captureBuffer.Start(true);

            return true;

        }

        // *.wavファイルのヘッダー部分を書く
        private void RecWave()
        {
            char[] Riff = { 'R', 'I', 'F', 'F' };
            char[] Wave = { 'W', 'A', 'V', 'E' };
            char[] Fmt = { 'f', 'm', 't', ' ' };
            char[] Data = { 'd', 'a', 't', 'a' };
            short padding = (short)this.fmt.FormatTag;
            int formatLength = 0x10;
            int length = 0;
            short shBytePerSample = (short)((this.fmt.BitsPerSample / (short)8) * this.fmt.Channels);

            try
            {
                this.binaryWriter = new BinaryWriter(new FileStream(this.file_path, FileMode.Create));
                this.binaryWriter.Write(Riff);
                this.binaryWriter.Write(length);
                this.binaryWriter.Write(Wave);
                this.binaryWriter.Write(Fmt);
                this.binaryWriter.Write(formatLength);
                this.binaryWriter.Write(padding);
                this.binaryWriter.Write(this.fmt.Channels);
                this.binaryWriter.Write(this.fmt.SamplesPerSecond);
                this.binaryWriter.Write(this.fmt.AverageBytesPerSecond);
                this.binaryWriter.Write(shBytePerSample);
                this.binaryWriter.Write(this.fmt.BitsPerSample);
                this.binaryWriter.Write(Data);
                this.binaryWriter.Write((int)0);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
        }

        // キャプチャバッファの作成
        void CreateCapBuffer()
        {
            this.notifySize = this.fmt.AverageBytesPerSecond / NUMBER_RECORD_NOTIFICATIONS;
            this.notifySize -= this.notifySize % this.fmt.BlockAlign;

            try
            {
                this.captureBufferDescription = new CaptureBufferDescription();
                this.captureBufferDescription.BufferBytes = notifySize * NUMBER_RECORD_NOTIFICATIONS;  //waveFormat.AverageBytesPerSecond;
                this.captureBufferDescription.WaveMapped = false;
                this.captureBufferDescription.ControlEffects = false;
                this.captureBufferDescription.Format = this.fmt;

                this.captureBuffer = new CaptureBuffer(this.captureBufferDescription, this.capture);
            }
            catch (BadFormatException e)
            {
                MessageBox.Show(e.ErrorString);
                return;
            }

            // RecWave();
            notifyThread = new Thread(new ThreadStart(CapThreadProc));
            notifyThread.Start();

            this.autoResetEvent = new AutoResetEvent(false);
            this.notify = new Notify(this.captureBuffer);

            for (int i = 0; i < NUMBER_RECORD_NOTIFICATIONS; i++)
            {
                PositionNotify[i].Offset = this.notifySize * (i + 1) - 1;
                PositionNotify[i].EventNotifyHandle = this.autoResetEvent.Handle;
            }
            notify.SetNotificationPositions(PositionNotify, NUMBER_RECORD_NOTIFICATIONS);
        }

        // 実際に音声をキャプチャして、*.wavファイルのデータ部分に書き出す処理
        public void CapThreadProc()
        {
            // Debug.WriteLine(this.isRecording.ToString());

            while (this.isRecording)
            {
                this.autoResetEvent.WaitOne(Timeout.Infinite, true);
                int offset = 0;
                int readPos, capturePos;
                this.captureBuffer.GetCurrentPosition(out capturePos, out readPos);
                int lockSize = this.notifySize - offset;
                capturePos = this.notifySize * notifyPos;

                if (lockSize < 0) { lockSize += captureBufferDescription.BufferBytes; }

                lockSize -= (lockSize % 2);
                if (0 == lockSize) { return; }

                byte[] captureData = (byte[])captureBuffer.Read(capturePos, typeof(byte), LockFlag.None, lockSize);

                this.binaryWriter.Write(captureData, 0, captureData.Length);
                this.dataLength += captureData.Length;

                notifyPos++;
                notifyPos %= NUMBER_RECORD_NOTIFICATIONS;
            }
        }

        // 「プライマリ・デバイス」以外の録音デバイスのGUIDを取得する
        private Guid getGuid(CaptureDevicesCollection _capDev)
        {
            for (int i = 0; i < _capDev.Count; i++)
            {
                Guid c_guid = _capDev[i].DriverGuid;
                string c_description = _capDev[i].Description;

                if (c_guid != System.Guid.Empty && c_description != "") { return c_guid; }
            }

            return Guid.Empty;
        }

        // 録音停止処理
        public void stopRecord()
        {
            if (!this.isRecording)
            {
                MessageBox.Show("録音していません。", Define.CLASS_NAME);
                return;
            }

            this.captureBuffer.Stop();
            this.notifyThread.Abort();

            // *.wavファイルのヘッダー部分の「ファイルサイズ - 8」を書き込む処理
            int dataSizeMinusEight = this.dataLength + 44 - 8;
            this.binaryWriter.Seek(4, SeekOrigin.Begin);
            this.binaryWriter.Write(dataSizeMinusEight);

            // *.wavファイルのヘッダー部分の「実データのサイズ」を書き込む処理
            this.binaryWriter.Seek(40, SeekOrigin.Begin);
            this.binaryWriter.Write(this.dataLength);

            this.binaryWriter.Close();
            this.isRecording = false;

            MessageBox.Show("録音終了。", Define.CLASS_NAME);
        }

        // 録音中かどうかのフラグを返す(呼び出し元フォーム等で使用する)
        public Boolean getIsRecording()
        {
            return this.isRecording;
        }

        // デストラクタ
        public void Dispose() { }
    }
}

しかし、コーディング技法というか、リーダブルなコードの書き方、もっと勉強しなくては…
この本は買って、読んでいるんだけど…
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)