UWP・C#プログラマーのためのC++/CX基礎知識

UWPアプリはVisual C++でも開発ができますが、UWPの機能を活用するにはマイクロソフトの独自言語であるC++/CXでプログラムを組まなくてはいけません。C++/CXに頼らず、純粋なC++のみで開発するための「WindowsランタイムC++テンプレートライブラリ(WRL)」と呼ばれる手法も提供していますが、.NETの仕様を無理にC++に寄せているためか、非常に難解になっており、これを使うくらいなら、C++を.NETに寄せたC++/CXを習得した方がまだましです。

と、いうわけで、Windows IoT Coreプログラミングの総仕上げの前に、Arduino WiredアプリをGUIとして活用したい人のための、C#のこの仕様はC++/CXではどうなるのかの基本的なお約束を列挙したいと思います。

ただし、メインプログラムはC#で、C++/CXプログラムはランタイムライブラリとして最低限だけ使うことを前提としているため、一部仕様を意図的に歪曲して説明している箇所がありますので、この点にはご注意ください。

※サンプルコードの左側はC#、右側はC++/CXでの例となっています。また、説明に[C++]とあるものは、C++/CXではなく、C++本来の仕様であるか、C++が基になっていることを示しています。

1.[C++]名前空間は「::」で区切る
using System.Diagnostics;

namespace MyApp{
    public class foo{
        private void bar(){
            Debug.Write("test");
        }
    }
}
using namespace System::Diagnostics;

namespace MyApp{
    public ref class foo{
    private:
        void foo(){
            Debug::Write("test");
        }
    };
}


2.UWPクラスは「^」ポインタの変数に、ref newで作成する。ポインタなので、メンバへのアクセスはC++同様「->」を用いる。これにより作成されたクラスはガベージコレクタにより処理されるため、明示的な削除は基本的に不要。ただし、IDisposableインターフェイスから派生したクラスはdeleteで明示的に削除しないとリソースが残ってしまう可能性がある。
StreamSocket s = new StreamSocket();
s.Dispose();
StreamSocket^ s = ref new StreamSocket();
delete s;


3.[C++]null値はnullptrと記述。
s.Dispose();
s = null;
delete s;
s = nullptr;


4.[C++]コンパイル時に型が決定されるC#のvarはautoで代用可能。
var s = new StreamSocket();
auto s = ref new StreamSocket();


5.[C++]as演算子はdynamic_castである程度代用可能。
StreamSocket s = obj as StreamSocket;
if(s == null) throw new Exception();
StreamSocket^ s = dynamic_cast<StreamSocket^>(obj);
if(s == nullptr) throw ref new Exception();


6.文字列はPlatform::String^に直接代入するのであれば「L」などのUnicode宣言は不要(コンパイル時にUnicodeとして扱われる)。配列としての文字列の場合はStringクラスの新規作成で変換する。
string s = "This is a sample";
String^ s = "This is a sample";
char ss[] = "This is a sample";
s = ref new String(ss);


7.配列はArrayクラスで作成する。C言語配列からの変換も同様。Platform::ArrayReferenceクラスを使えば、配列アドレスの参照だけが登録されるため、リソースの節約になるが、オリジナルの配列が変更されると内部のデータが破壊される点に注意。
byte[] buf = new byte[100];
Array<byte>^ buf = ref new Array<byte>(100);

byte *cbuf = new byte[100];
buf = ref new Array<byte>(cbuf, 100);

DataWriter^ writer = ref new DataWriter();
writer->WriteBytes(ArrayReference<byte>(cbuf, 100));


8.[C++]ランタイムC++クラスにネイティブC++クラスを含めたり、その逆はできるが、ランタイムに準拠していないメソッドや変数は公開できないという規則があるため、publicやメソッド経由に関わらず、外部からクラス変数にアクセスすることは原則としてできない。現時点では変数をprivateにして、friendを使うことで対処できる。
ref class WinRTApp sealed
{
private:
    friend class NativeApp;
    NativeApp *napp;
};

class NativeApp : public NativeAppBase
{
private:
    friend ref class WinRTApp;
    WinRTApp^ srt;
};


9.イベントの追加は「+=」を使用する点まではC#と同じだが、メソッドは直接指定できず、引数の型を宣言したTypedEventHandlerクラスを経由する必要がある。
var amv = CoreApplication.MainView;
if (amv){
    var cw = amv.CoreWindow;
    if (cw){
        cw.KeyDown += OnKeyDown;
        cw.KeyUp += OnKeyUp;
    }
}
WinRTApp::WinRTApp()
{
    auto amv = CoreApplication::MainView;
    if (amv){
        auto cw = amv->CoreWindow;
        if (cw){
            cw->KeyDown += ref new TypedEventHandler<CoreWindow ^, KeyEventArgs^>(this, &WinRTApp::OnKeyDown);
            cw->KeyUp += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &WinRTApp::OnKeyUp);
        }
    }
}

void WinRTApp::OnKeyDown(CoreWindow ^w, KeyEventArgs ^e)
{
    e->Handled = true;
}


10.APIの非同期処理メソッドはconcurrency拡張で処理するのが一般的。非同期処理が完了すれば、create_taskで作成したタスクのthen内の関数(ここでは[C++]のラムダ式を使用)が実行される。さらにこの関数内で非同期処理が行われたときは、jQueryのようにthenをチェインすることで処理を継続させることが可能。このチェインを抜けるときは「task_canceled()」をthrowする。また、then()の代わりに、"t.wait();t.get();"を実行することでawaitと同等の処理ができる。
#include <ppltasks.h>
using namespace concurrency;

void GameInputDevice::Start()
{
    auto selector = HidDevice::GetDeviceSelector(0x1, 0x4);
    task c = create_task(DeviceInformation::FindAllAsync(selector));
    c.then([this](DeviceInformationCollection^ info){
        DeviceInformation ^di = nullptr;
        for (unsigned int i = 0; i < info->Size; i++){
            di = info->GetAt(i);
            if (wcsstr(di->Name->Data(), L"TNK Virtual")){
                continue;
            } else if(di != nullptr) {
                return HidDevice::FromIdAsync(di->Id, Windows::Storage::FileAccessMode::Read);
            }
        }

        throw task_canceled();
    }).then([this](HidDevice ^d){
        if (!d) return;
        device = d;
    });
}


管理人が習得済みであれば、情報は随時追加していきますので、「このC#のコードをC++/CXではどう書けばいいの?」という質問があれば、このツイートへのリプライかメールを送ってください。
 | 2016年11月4日