echo("備忘録");

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

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)

Managed DirectXを動かす その2

これの続きで、Managed DirectXが無事動いたので、DirectSoundで*.wavファイルを再生する処理を書いてみました。
再生だけなら、すごいシンプルなんですね。
(不要な処理が多いかもしれません。すいません。)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

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

namespace Sample
{
    public partial class Form1 : Form
    {
        private Device dev;              // デバイス
        private SecondaryBuffer s_buf;   // セカンダリバッファ
           
        public Form1()
        {
            InitializeComponent();
        }

        // フォームロード時の処理
        private void Form1_Load(object sender, EventArgs e)
        {
            string path = @"C:\sample.wav";  // 再生する*.wavファイルのパス

      this.dev = new Device();
            this.dev.SetCooperativeLevel(this, CooperativeLevel.Priority);
            this.s_buf = new SecondaryBuffer(path, dev);
        }

        // 再生時の処理(「再生」ボタンクリックなど)
        private void button1_Click(object sender, EventArgs e)
        {     
            // 再生位置を一番最初に設定して、再生
            this.s_buf.SetCurrentPosition(0);
            this.s_buf.Play(0, BufferPlayFlags.Default);
        }

        // 停止時の処理(「停止」ボタンクリックなど)
        private void button2_Click(object sender, EventArgs e)
        {
            // デバイスとセカンダリバッファの破棄
            this.s_buf.Dispose();
            this.dev.Dispose();
        }
    }
}

さて、次はキャプチャですかね。

会社不適合者

さて、こっちに来て3週間、仕事開始から2週間以上が経ちました。

おかげさまで「新しい場所」って言う意味では「住めば都」では無いですが、少しづつ慣れてきました。

また新天地はエンジニアとしての仕事の量・幅も前より多く、来てよかったな、とも感じています。

 

でも、これが「会社」となると、今でも疑問に感じています。

 

「新天地まで来て、こんなことしたかったのかな」って。

 

そりゃ、この事があったり、他にも色々な事がありましたからね。

入社してから言うのもあれですが「やっぱり俺、会社員向いてないのかも」という疑問と常に向き合い、自問自答する毎日です。

(「会社員に向いてない」というのは今に始まった訳ではなく、過去にも幾度と無く経験しています。ただその時は闘病中だったことや会社員以外の働き方に自信が持てなかったこともあり、悪く言えば「逃げてた」感がありました。)

 

特に、会社員なら当然なのかもしれないのですが、やたら会社に迎合したり、上下関係などの「世渡り」みたいなことで細々と言われる事。

例えば「会社が白といえば白!」みたいな事を強要されるわけですよ。

 

まあ、会社員ならそれが当然なのかもしれませんが、それに苦痛を感じる以上、会社員として不適合なのかも、と最近になって思ってます。

 (だから、水面下では色々活動しているんですけどね。

あと、一つだけ確実なのは「IT業界を離れるつもりは全く無い」ということ。なんだかんだ言って、自分にとってこの仕事は天職だと思っています。)

 

でも「石の上にも3年」とか「お金を稼ぐとはそういうもんだ」と言うのも一理ありますし、実際1ヶ月も経っていないのに結論を出すのは早過ぎる、と言われると反論できないので、どうすればいいのか、まだまだ苦悩する日々は続きそうです。

 

ていうか、それが原因なんだろうなあ。

この腰の激痛は…

(ちなみに今は、会社を休んで、地元の病院でMRI取った直後です…)

 

Managed DirectXを動かす その1

※仕事でManaged DirectXを動かすまでに一苦労したので、備忘録的にメモ。

 

最近、仕事でManaged DirectXを使用することになったのですが…

全く動かない…というか、正常に動作しない。

 

とりあえず、やった事といえば、

  • Managed DirectX9.0cのインストール
  • DirectX SDKのインストール
  • Windows SDKのインストール
  • DirectX エンドユーザーランタイムのインストール

上記のこと。

ネットで情報としてあるのはこれくらいなので、これを1つずつ実行する度にC#.netのコードをビルドして実行、を行ったけど、いずれも実行後にプロジェクトがうんともすんとも言わなくなる。

 

かと言って、コードが間違っている事はありえない。(だって

 Device device = new Device();

っていう1行だけだし…)

 

「Windows10だともうDirectX9は動かないのかも」とも思いましたよ。ええ。

マイクロソフトも、もう推奨はしていないかもしれませんが…)

でも別で入手したC++のプログラムを動かしたら正常に動くし、もし本当にそうなら絶対ネットに類似情報がアップされてるはずだから、それはありえない。

でも上記コードが記載されたモジュールを読み込みした時点で確実に固まるから「ひょっとして、マシンの相性が悪い?」とか思ったりしたのですが、結局原因は分からず…

 

だったのですが、諸事情で1日会社を休んだのですが、ふと「読み込みの時点で落ちるって事は、インストールに失敗したのか?」と思い、ちょっと調べたら、すごいあっさり解決(まあ、会社休んだので、確認したのは翌日なんですが…)

 

まあ結論から言えば、DirectXうんぬんは全く関係なく、.net Frameworkの互換性の問題で、下記ページの内容を実施したらあっさり解決しました。

gifnksm.hatenablog.jp

 上記のように、「app.config」のstartupタグで「useLegacyV2RuntimeActivationPolicy=true」を指定すればOKです。

 

こういうハマっていた事が解決する快感ってのは、この仕事ならではではないでしょうか?(ていうか、もっと早く見つけろよ…)

 

ていうか、こういうドハマりする事って「ふと何気ない時に解決することが多い」って言いますが、本当にその通りだなあ、と思いました。

 

ていうか、入社してまだ2週間も経ってないのに、良く休めたなあ。