DebuggerStepThrough属性

デバッガの制御系のDebuggerStepThroughAttributeです。
デバッガでステップインの抑制を行います。
この属性がついているクラス、構造体、メソッド、プロパティがあると、ステップインが実行されず、ステップオーバーと同じ動きになります。

using System.Diagnostics;
 
class Program
{
    [DebuggerStepThroughAttribute]
    static void Print()
    {
        Debug.WriteLine("Print Method");
    }
 
    static void Main(string[] args)
    {
        Print();
        Debug.WriteLine("Test");
    }
}

[属性なしの結果]

[属性ありの結果]

Conditional属性

最近、C#のAttributeについてはまってたりしてます。
なので、いろいろ調べてみた。
まずは、代表(?)的なAttributeのConditionalAttribute。

戻り値なし(void)のメソッドに、属性を付加します。
この属性がついていると、プロジェクトのプロパティのビルド、条件付きコンパイルシンボルに定義で、処理の有効/無効が可能になります。

標準で、DEBUGとTRACEの定義はチェックボックスでON/OFFできるようになってます。
それ以外は自分で記述!

using System.Diagnostics;
 
[Conditional("TestConditional")]
static void Print()
{
    Debug.WriteLine("Print Method");
}
 
static void Main(string[] args)
{
    Print();
    Debug.WriteLine("Test");
}

[実行結果(定義なし)]


[実行結果(定義あり)]


こんな感じ

コマンドマップ

ふと、思いました。

class Program
{
    static void ActionA()
    {
        Console.WriteLine("Action A.");
    }
 
    static void ActionB()
    {
        Console.WriteLine("Action B.");
    }
 
    static void ActionC()
    {
        Console.WriteLine("Action C.");
    }
 
    static void Main(string[] args)
    {
        do
        {
            // コマンド入力
            Console.Write("Input Cmd> ");
            string cmd = Console.ReadLine();
            switch (cmd)
            {
                case "A":
                    // Aコマンドを実行
                    ActionA();
                    break;
 
                case "B":
                    // Bコマンドを実行
                    ActionB();
                    break;
 
                case "C":
                    // Cコマンドを実行
                    ActionC();
                    break;
                default:
                    break;
            }
        }
        while (true);
    }
}

上記のサンプルような場合、コマンド増えるごとに、判断する箇所が増えていく・・・
もっと、シンプルに簡単にならないものか?と。

シンプルかつ、簡単にできるなら、後からの追加変更とか容易だし、修正によるバグも少なくなるのでは??
という建前の元、考えてみました。
本当は、コードの量が多く汚くなりそうなんで、嫌だなって単なる我が儘です(笑)

/// <summary>
/// コマンドMap
/// </summary>
static Dictionary<string, Action> _actionMap = new Dictionary<string, Action>();
 
static void Main(string[] args)
{
    // コマンドを登録
    _actionMap.Add("A", new Action(ActionA));
    _actionMap.Add("B", new Action(ActionB));
    _actionMap.Add("C", new Action(ActionC));
 
    do
    {
        // コマンド入力
        Console.Write("Input Cmd> ");
        string cmd = Console.ReadLine();
        if (_actionMap.ContainsKey(cmd))
        {
            // コマンド実行
            _actionMap[cmd]();
        }
    }
    while (true);
}

Actionは、.Net3.5で定義されているdelegateのGenericです。
戻り値なしで、引数4個までなら、デフォルトであります。

要は、コマンドと実行メソッドをDictionaryで管理し、実行時にコマンドをキーにして、実行メソッドを呼び出すという仕組み。
これで、増えた場合は、対象のメソッドを作成し、Dictionaryに追加する。消す場合は、Dictionaryの追加部分を削除するだけで、対応できる。
呼び出す部分も、増えていかず、きれいさっぱりに!(笑)

別途、専用にdelegateを定義して行えば、.Net2.0でも、戻り値ありも、引数ありも可能です。
ただし、コマンドの引数、戻り値が共通になっている必要がありますが。

WPFxUPnP 2 その4

最後に、WPFでの画面です。

バイスのListを作成して、

public class UPnPLightList : ObservableCollection<UPnPLightDevice>
{
}

バイスのリストを、依存プロパティとして出して、バインドできるようにしています。
あとは、それぞれ、イベントハンドラを設定して、操作できるようにしています。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }
 
    // デバイス検索オブジェクト
    DeviceFinder deviceFinder = new DeviceFinder();
 
    // ロードイベントハンドラ
    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        deviceFinder.AddEvent += (addDevive) =>
            {
                // デバイス追加
                try
                {
                    LightList.Add(new UPnPLightDevice(addDevive));
                }
                catch (Exception) { }
            };
 
        deviceFinder.RemoveEvent += (removeDeviceUDN) =>
            {
                // デバイス削除
                try
                {
                    foreach (UPnPLightDevice one in LightList)
                    {
                        if (one.Device.UniqueDeviceName == removeDeviceUDN)
                        {
                            LightList.Remove(one);
 
                            Marshal.ReleaseComObject(one.Device);
                            break;
                        }
                    }
                }
                catch (Exception) { }
            };
 
        // デバイス検索開始
        deviceFinder.FindDeviceAsync("urn:schemas-upnp-org:device:DimmableLight:1");
    }
 
    // ライトデバイスリスト
    public static readonly DependencyProperty LightListProperty = DependencyProperty.Register("LightList",
                                                                      typeof(UPnPLightList),
                                                                      typeof(MainWindow),
                                                                      new PropertyMetadata(new UPnPLightList()));
    // ライトデバイスリスト
    public UPnPLightList LightList
    {
        get { return (UPnPLightList)this.GetValue(LightListProperty); }
        set { this.SetValue(LightListProperty, value); }
    }
 
    // Closeイベントハンドラ
    private void OnClosed(object sender, EventArgs e)
    {
        deviceFinder.CancelFindDeviceAsync();
    }
 
    // ONボタンクリックイベントハンドラ
    private void LightOn_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            UPnPLightDevice dev = (UPnPLightDevice)(DeviceList.SelectedItem);
            dev.SetTarget(true);
        }
        catch (Exception) { }
    }
 
    // OFFボタンクリックイベントハンドラ
    private void LightOff_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            UPnPLightDevice dev = (UPnPLightDevice)(DeviceList.SelectedItem);
            dev.SetTarget(false);
        }
        catch (Exception) { }
    }
 
    // 状態取得ボタンクリックイベントハンドラ
    private void LightStatus_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            UPnPLightDevice dev = (UPnPLightDevice)(DeviceList.SelectedItem);
            ResultStatus.Text = (dev.GetStatus() ? "ON" : "OFF");
        }
        catch (Exception) { }
    }
}

OnLoadedで、UPnPバイスの検索をかけて、追加・削除に応じて、作成したデバイスリストを生成して、追加します。
すると、デバイスが見つかった場合、インスタンスを作成し、リストに登録。
WPFフレームワークにより、リストに登録され表示されるようになります。
後は、イベントハンドラで、対象のインスタンスを操作して、UPnPバイスを操作します。

XAMLは、こんな感じ。

<Window x:Class="UPnPLightController.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="UPnP Light Controller" Height="250" Width="200" x:Name="Main"
        ResizeMode="NoResize" Loaded="OnLoaded" Closed="OnClosed">
    <StackPanel>
        <ComboBox x:Name="DeviceList" IsReadOnly="True"
                  ItemsSource="{Binding Path=LightList,ElementName=Main}"
                  DisplayMemberPath="Device.FriendlyName"/>
        <StackPanel Orientation="Horizontal">
            <Button x:Name="LightOn" Width="50" Content="ON" Click="LightOn_Click" />
            <Button x:Name="LightOff" Width="50" Content="OFF" Click="LightOff_Click" />
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <Button x:Name="GetStatus" Width="50" Content="NOW" Click="LightStatus_Click" />
            <TextBlock x:Name="ResultStatus" Width="50" />
        </StackPanel>
        <Ellipse Height="100" Width="100" Margin="15"
                 IsEnabled="{Binding Path=SelectedItem.Status, ElementName=DeviceList}">
            <Ellipse.Style>
                <Style TargetType="{x:Type Ellipse}">
                    <Setter Property="Fill" Value="LightGray" />
                    <Style.Triggers>
                        <Trigger Property="IsEnabled" Value="True">
                            <Setter Property="Fill" Value="Yellow"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Ellipse.Style>
        </Ellipse>
    </StackPanel>
</Window>

これで、完成。

WPFxUPnP 2 その3

WPFxUPnP 2 その3

久しぶりに、UPnPのデバイスについて・・・。

まずは、UPnPのライトのデバイスクラスを作成します。

public class UPnPLightDevice : INotifyPropertyChanged
{
    public UPnPDevice Device { get; set; }
    public UPnPService SwitchPowerService { get; set; }

    public UPnPLightDevice(UPnPDevice pDevice)
    {
        try
        {
            Device = (UPnPDevice)Marshal.GetUniqueObjectForIUnknown(Marshal.GetIUnknownForObject(pDevice));

            SwitchPowerService = Device.Services["urn:upnp-org:serviceId:SwitchPower:1"];
            SwitchPowerService.AddCallback(this);
        }
        catch (Exception)
        {
            if (SwitchPowerService != null)
            {
                Marshal.ReleaseComObject(SwitchPowerService);
            }
            if (Device != null)
            {
                Marshal.ReleaseComObject(Device);
            }
            throw;
        }
    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    bool _Status = false;
    public bool Status
    {
        get { return _Status; }
        set
        {
            _Status = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Status"));
            }
        }
    }

コンストラクタで、DeviceFinderで見つけたUPnPDeviceを残し、そこから、urn:upnp-org:serviceId:SwitchPower:1サービスを取得します。
後は、ライトの値をプロパティとして、出して、値が変わるとINotifyPropertyChangedを用いて、WPFへ変更通知を送るようにしています。

そして、urn:upnp-org:serviceId:SwitchPower:1のアクションをそれぞれ定義します。

    public void SetTarget(bool status)
    {
        object[] inarg = new object[1] { (status ? 1 : 0) };
        object[] outarg = new object[1];
        SwitchPowerService.InvokeAction("SetTarget", inarg, outarg);
    }

    public bool GetTarget()
    {
        object[] inarg = new object[1];
        object outarg = new object();
        SwitchPowerService.InvokeAction("GetTarget", inarg, ref outarg);
        return (bool)(((object[])outarg)[0]);
    }

    public bool GetStatus()
    {
        object[] inarg = new object[1];
        object outarg = new object();
        SwitchPowerService.InvokeAction("GetStatus", inarg, ref outarg);
        return (bool)(((object[])outarg)[0]);
    }

あと、SwitchPowerService.AddCallback(this);で、urn:upnp-org:serviceId:SwitchPower:1サービスの状態変更通知を貰えるように、設定します。
AddCallbackで指定するには、IUPnPServiceCallbackインターフェースを継承する必要があります。
よって、クラス定義が次のようになります。

public class UPnPLightDevice : IUPnPServiceCallback, INotifyPropertyChanged
{
・・・(省略)・・・
    #region IUPnPServiceCallback
    public void ServiceInstanceDied(UPnPService pus)
    {
        Marshal.ReleaseComObject(pus);
    }

    public void StateVariableChanged(UPnPService pus, string pcwszStateVarName, object vaValue)
    {
        try
        {
            if (pcwszStateVarName == "Status")
            {
                Status = ((bool)vaValue);
            }
        }
        catch (Exception) { }
        finally
        {
            Marshal.ReleaseComObject(pus);
        }
    }
    #endregion
}

可変引数は、マーシャリングできるのか!?

ということで、今回は、C/C++で作成されたDLLの可変引数関数は、C#からマーシャリングできるのかを議題にやってみようと思う。

まず、C/C++のDLLのコード。
これは単純に、コンソールに出力するだけの簡単な関数にしてみました。(つまり、printf関数です)
宣言のヘッダーは、至って普通の宣言です。

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifdef PRINT_EXPORTS
#define PRINT_API __declspec(dllexport)
#else // PRINT_EXPORTS
#define PRINT_API __declspec(dllimport)
#endif // PRINT_EXPORTS

#ifdef __cplusplus 
extern "C"
{
#endif // __cplusplus

void PRINT_API WINAPI PrintStringW(LPCWSTR lpszFormat, ...);
void PRINT_API WINAPI PrintStringA(LPCSTR lpszFormat, ...);

#ifdef __cplusplus 
}
#endif // __cplusplus

実装は、普通にprintf関数を呼び出しているだけです。

BOOL APIENTRY DllMain( HMODULE hModule,
                    DWORD  ul_reason_for_call,
                    LPVOID lpReserved
                     )
{
    UNREFERENCED_PARAMETER(hModule);
    UNREFERENCED_PARAMETER(ul_reason_for_call);
    UNREFERENCED_PARAMETER(lpReserved);
    return TRUE;
}

void PRINT_API WINAPI PrintStringW(LPCWSTR lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);

    vwprintf(lpszFormat, args);

    va_end(args);
}
 
void PRINT_API WINAPI PrintStringA(LPCSTR lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);

    vprintf(lpszFormat, args);

    va_end(args);
}

では、マーシャリングにトライです。

関数の定義をそのまま、マーシャリングの定義に置き換えると次のようになります。

[DllImport("print.dll")]
extern static void PrintString(string format, params object[] args);

で、次にのように呼び出すと・・・

PrintString("Print %d\n", 1);

1と表示されません。

マーシャリングはFrameworkが対応の型にあわせて、値を変換し、関数を呼び出してくれているためで、
利用側が使用する型を不明の場合(可変引数など)、どのように変換して良いのか不明のため、
うまく変換できずに、値を渡しているため、おかしな値になっています。
なので、呼び出しにintを渡すのであれば、定義を次のようにすると正しく、表示されるようになります。

[DllImport("print.dll")]
extern static void PrintString(string format, int num);

結論、利用する引数の数だけ正しく宣言すれば、動作可能。

おまけ。
呼び出しがprintf関数の場合、次のようにして、文字列にしてしまえば容易です。

[DllImport("print.dll")]
extern static void PrintString(string str);

static void PrintStringWrapper(string format, params object[] args)
{
    PrintString(string.Format(format, args));
}

PrintStringWrapper("Print {0}\n", 1);

フォーマット文字列はC#書式なので注意(笑)

ログ出力時の呼び出し元関数名を出力する

ログ出力など可変引数を利用して出力している場合、呼び出し元の関数名を出力は容易ではない。
そのために、ゴソゴソしてみた。
出力するログ出力側で、引数で渡せば容易だが、そんなのはめんどくさい!
そんなめんどくさをなくすサンプル。

まずは、__VA_ARGS__マクロ版。__VA_ARGS__は無い場合があるので、要注意。

#include <windows.h>

#define LOG_PRINT(x, ...)    CLogOut::Out(_T(__FUNCTION__), x, __VA_ARGS__)

class CLogOut
{
public:
    CLogOut(void) { };
    ~CLogOut(void) { };

    static void Out(LPCTSTR lpszFuncName, LPCTSTR lpszFormat, ...)
    {
        va_list args;
        va_start(args, lpszFormat);

        TCHAR szBuffer[1024] = { 0 };
        _vstprintf(szBuffer, lpszFormat, args);

        _tprintf(_T("[%s] %s\n"), lpszFuncName, szBuffer);
        va_end(args);
    };
};

int _tmain(int argc, _TCHAR* argv[])
{
    LOG_PRINT(_T("Test"));
    LOG_PRINT(_T("many args %d %s %c"), 1, _T("Test"), 'C');
    return 0;
}

__VA_ARGS__マクロが無い場合は、C++のクラスをうまく利用すると、いい感じにできる。
その場合は、こんな感じ。

#include <windows.h>

#define LOG_PRINT	CLogOut::CLogOutHelper(_T(__FUNCTION__)).Out

class CLogOut
{
public:
    class CLogOutHelper
    {
    public:
        CLogOutHelper(LPCTSTR szFuncName)
        {
            m_szFuncName = szFuncName;
        };

        void Out(LPCTSTR lpszFormat, ...)
        {
            va_list args;
            va_start(args, lpszFormat);
            CLogOut::Out(m_szFuncName, lpszFormat, args);
            va_end(args);
        };

    private:
        LPCTSTR m_szFuncName;
    };


public:
    CLogOut(void) { };
    ~CLogOut(void) { };

    static void Out(LPCTSTR lpszFuncName, LPCTSTR lpszFormat, va_list args)
    {
        TCHAR szBuffer[1024] = { 0 };
        _vstprintf(szBuffer, lpszFormat, args);

        _tprintf(_T("[%s] %s\n"), lpszFuncName, szBuffer);
    };
};

int _tmain(int argc, _TCHAR* argv[])
{
    LOG_PRINT(_T("Test"));
    LOG_PRINT(_T("many args %d %s %c"), 1, _T("Test"), 'C');
    return 0;
}