C++ での Windows ランタイム コンポーネントの作成
この記事では、C++ を使って Windows ランタイム コンポーネントを作成する方法を示します。このコンポーネントは、JavaScript (または C#、Visual Basic、C++) を使ってビルドした Windows ストア アプリから呼び出すことができる DLL です。そのようなコンポーネントをビルドする理由はいくつかあります。
複雑な操作または負荷の高い操作で C++ のパフォーマンスを得る。
既に作成されテストされている既存のコードを再利用する。
JavaScript プロジェクトまたは .NET プロジェクト、および Windows ランタイム コンポーネント プロジェクトを含むソリューションをビルドすると、JavaScript プロジェクト ファイルとコンパイル済み DLL が 1 つのパッケージとしてマージされます。これを、シミュレーター内のローカルでデバッグしたり、テザリングされたデバイス上でリモートでデバッグしたりできます。また、拡張 SDK としてコンポーネント プロジェクトだけを配布することもできます。詳細については、「方法: ソフトウェア開発キットを作成する」を参照してください。
一般的に、C++ コンポーネントをコーディングする場合、他の .winmd パッケージとの間でデータをやり取りする抽象バイナリ インターフェイス (ABI) の境界を除いて、標準の C++ ライブラリと組み込み型を使用します。境界では、Windows ランタイム 型と、それらの型の作成と操作をサポートする Visual C++ の特別な構文を使用します。さらに、Visual C++ コードでは、コンポーネントから発生させて JavaScript、Visual Basic、または C# で処理できるイベントの実装に delegate、event などの型を使用します。新しい Visual C++ 構文の詳細については、「Visual C++ の言語リファレンス (C++/CX)」を参照してください。
大文字小文字の区別と名前付け規則
JavaScript
JavaScript では大文字と小文字が区別されます。したがって、これらの大文字小文字の区別の規則に従う必要があります。
C++ 名前空間とクラスを参照する場合、C++ の側と同じ大文字小文字の区別を使用します。
メソッドを呼び出す場合、メソッド名が C++ の側で大文字になっていても Camel 形式の大文字小文字の区別を使用します。たとえば、C++ のメソッド GetDate() は JavaScript から getDate() として呼び出す必要があります。
アクティブ化可能なクラス名や名前空間名には、UNICODE 文字を含めることはできません。
.NET
.NET 言語では、各言語の通常の大文字と小文字の規則が適用されます。
オブジェクトのインスタンス化
Windows ランタイム 型のみ ABI の境界を越えて渡すことができます。コンパイラは、コンポーネントのパブリック メソッドでの戻り値の型または戻りパラメーターが std::wstring などの型である場合、エラーを発生させます。Visual C++ コンポーネント拡張 (C++/CX) の組み込み型には、int、double などの通常のスカラーと、その typedef である int32、float64 などがあります。詳細については、「型システム (C++/CX)」を参照してください。
C++
// ref class definition in C++
public ref class SampleRefClass sealed
{
// Class members...
// #include <valarray>
public:
double LogCalc(double input)
{
// Use C++ standard library as usual.
return std::log(input);
}
};
JavaScript
//Instantiation in JavaScript (requires "Add reference > Project reference")
var nativeObject = new CppComponent.SampleRefClass();
.NET
//Call a method and display result in a XAML TextBlock
var num = nativeObject.LogCalc(21.5);
ResultText.Text = num.ToString();
C++ の組み込み型、ライブラリ型、および Windows ランタイム 型
アクティブ化可能なクラス (ref クラスとも呼ばれます) は、JavaScript、C#、Visual Basic などの他の言語からインスタンス化できるクラスです。他の言語から使用できるようにするには、コンポーネントに 1 個以上のアクティブ化可能なクラスを含める必要があります。
Windows ランタイム コンポーネントには、複数のアクティブ化可能なパブリック クラスだけでなく、コンポーネント内部でのみ認識される他のクラスも含めることができます。すべてのパブリック クラスが、コンポーネントのメタデータ ファイルと同じ名前を持つ同じルート名前空間に存在する必要があります。JavaScript への表示が意図されていない C++ の型に [WebHostHidden] 属性を適用します。
クライアント コードは、他のクラスと同様に、new キーワード (Visual Basic の場合は New) を使用してコンポーネントのインスタンスを作成します。
アクティブ化可能なクラスは public ref class sealed として宣言する必要があります。ref クラスのキーワードは、Windows ランタイムと互換性のある型としてクラスを作成するようにコンパイラに指示し、sealed キーワードはクラスの継承ができないことを指定します。現在、Windows ランタイムは汎用の継承モデルをサポートしていません。限定的な継承モデルによってカスタム XAML コントロールの作成をサポートしています。詳細については、「Ref クラスと構造体 (C++/CX)」を参照してください。
C++ の場合、すべてのプリミティブ数値型が既定の名前空間で定義されます。Platform Namespace には、Windows ランタイム型システムに固有の C++ クラスが含まれます。これには、Platform::String クラス と Platform::Object クラス が含まれます。Platform::Collections::Map クラス と Platform::Collections::Vector クラス などの具体的なコレクション型は Platform::Collections 名前空間 で定義されます。これらの型が実装するパブリック インターフェイスは Windows::Foundation::Collections 名前空間 (C++/CX) で定義されます。JavaScript、C#、および Visual Basic で使用されるのは、この種類のインターフェイスです。詳細については、「型システム (C++/CX)」を参照してください。
組み込み型の値を返すメソッド
C++
// #include <valarray>
public:
double LogCalc(double input)
{
// Use C++ standard library as usual.
return std::log(input);
}
JavaScript
//Call a method
var nativeObject = new CppComponent.SampleRefClass;
var num = nativeObject.logCalc(21.5);
document.getElementById('P2').innerHTML = num;
.NET
カスタム値の構造体を返すメソッド
value struct は、既定でパブリックであるフィールドを含めることができる plain old data オブジェクトです。value struct は値によって渡されます。
C++
namespace CppComponent
{
// Custom struct
public value struct PlayerData
{
Platform::String^ Name;
int Number;
double ScoringAverage;
};
public ref class Player sealed
{
private:
PlayerData m_player;
public:
property PlayerData PlayerStats
{
PlayerData get(){ return m_player; }
void set(PlayerData data) {m_player = data;}
}
};
}
JavaScript
ABI 間でユーザー定義の値構造体を渡すには、C++ で定義されている値構造体と同じメンバーを持つ JavaScript オブジェクトを定義します。その後、そのオブジェクトを C++ メソッドの引数として渡し、オブジェクトが C++ 型として暗黙的に変換されるようにします。
// Get and set the value struct
function GetAndSetPlayerData() {
// Create an object to pass to C++
var myData =
{ name: "Bob Homer", number: 12, scoringAverage: .357 };
var nativeObject = new CppComponent.Player();
nativeObject.playerStats = myData;
// Retrieve C++ value struct into new JavaScript object
var myData2 = nativeObject.playerStats;
document.getElementById('P3').innerHTML = myData.name + " , " + myData.number + " , " + myData.scoringAverage.toPrecision(3);
}
もう 1 つの方法は、IPropertySet を実装するクラスを定義することです (非表示)。
C#
.NET 言語の場合、C++ コンポーネントで定義されている型の変数を作成します。
private void GetAndSetPlayerData()
{
// Create a ref class
var player = new CppComponent.Player();
// Create a variable of a value struct
// type that is defined in C++
CppComponent.PlayerData myPlayer;
myPlayer.Name = "Babe Ruth";
myPlayer.Number = 12;
myPlayer.ScoringAverage = .398;
// Set the property
player.PlayerStats = myPlayer;
// Get the property and store it in a new variable
CppComponent.PlayerData myPlayer2 = player.PlayerStats;
ResultText.Text += myPlayer.Name + " , " + myPlayer.Number.ToString() +
" , " + myPlayer.ScoringAverage.ToString();
}
オーバーロードされたメソッド
C ++ のパブリック ref クラスにはオーバーロードされたメソッドを含めることができますが、JavaScript の場合はオーバーロードされたメソッドを区別する機能が限定されています。たとえば、以下のシグニチャの相違を区別できます。
public ref class NumberClass sealed
{
public:
int GetNumber(int i);
int GetNumber(int i, Platform::String^ str);
double GetNumber(int i, MyData^ d);
};
ただし、以下のシグニチャの相違は区別できません。
int GetNumber(int i);
double GetNumber(double d);
以下の相違も区別できません。
あいまいな場合、JavaScript が常にヘッダー ファイルのメソッド シグネチャに Windows::Foundation::Metadata::DefaultOverload 属性を適用することで特定のオーバーロードを呼び出すようにできます。
この JavaScript は、属性付きオーバーロードを常に呼び出します。
var nativeObject = new CppComponent.NumberClass();
var num = nativeObject.getNumber(9);
document.getElementById('P4').innerHTML = num;
.NET
.NET 言語では、.NET Framework クラスの場合と同様に、C ++ の ref クラスのオーバーロードが認識されます。
DateTime
Windows ランタイムでは、Windows::Foundation::DateTime オブジェクトは 1601 年 1 月 1 日の前または後の時間長さを 100 ナノ秒単位で表した 64 ビットの符号付き整数にすぎません。Windows:Foundation::DateTime オブジェクトにはメソッドがありません。一方、各言語では DateTime をその言語独自の方法で算出します。JavaScript では Date オブジェクト、.NET Framework では System.DateTime 型および System.DateTimeOffset 型です。
C++
public ref class MyDateClass sealed
{
public:
property Windows::Foundation::DateTime TimeStamp;
void SetTime(Windows::Foundation::DateTime dt)
{
auto cal = ref new Windows::Globalization::Calendar();
cal->SetDateTime(dt);
TimeStamp = cal->GetDateTime(); // or TimeStamp = dt;
}
};
JavaScript
C++ から JavaScript に DateTime 値を渡すと、JavaScript は Date のオブジェクトとしてそれを受け入れ、既定で長い形式の日付文字列として表示します。
function SetAndGetDate() {
var nativeObject = new CppComponent.MyDateClass();
var myDate = new Date(1956, 4, 21);
nativeObject.setTime(myDate);
var myDate2 = nativeObject.timeStamp;
//prints long form date string
document.getElementById('P5').innerHTML = myDate2;
}
.NET
.NET 言語から C++ コンポーネントに System.DateTime を渡す場合、メソッドはそれを Windows::Foundation::DateTime として受け取ります。コンポーネントが .NET Framework メソッドに Windows::Foundation::DateTime を渡すと、その .NET Framework メソッドはそれを DateTimeOffset として受け取ります。
private void DateTimeExample()
{
// Pass a System.DateTime to a C++ method
// that takes a Windows::Foundation::DateTime
DateTime dt = DateTime.Now;
var nativeObject = new CppComponent.MyDateClass();
nativeObject.SetTime(dt);
// Retrieve a Windows::Foundation::DateTime as a
// System.DateTimeOffset
DateTimeOffset myDate = nativeObject.TimeStamp;
// Print the long-form date string
ResultText.Text += myDate.ToString();
}
コレクションと配列
コレクションは、常に Windows::Foundation::Collections::IVector^ や Windows::Foundation::Collections::IMap^ などの Windows ランタイム型へのハンドルとして ABI の境界を越えて渡されます。たとえば、Platform::Collections::Map にハンドルを返す場合、Windows::Foundation::Collections::IMap^ に暗黙的に変換されます。コレクション インターフェイスは、具体的な実装を提供する C++ クラスとは別の名前空間で定義されます。そのインターフェイスを JavaScript 言語と .NET 言語で使用します。詳細については、「コレクション (C++/CX)」および「Array と WriteOnlyArray (C++/CX)」を参照してください。
IVector を渡す場合
C++
// Windows::Foundation::Collections::IVector across the ABI.
//#include <algorithm>
//#include <collection.h>
Windows::Foundation::Collections::IVector<int>^ SortVector(Windows::Foundation::Collections::IVector<int>^ vec)
{
std::sort(begin(vec), end(vec));
return vec;
}
JavaScript
var nativeObject = new CppComponent.CollectionExample();
// Call the method to sort an integer array
var inVector = [14, 12, 45, 89, 23];
var outVector = nativeObject.sortVector(inVector);
var result = "Sorted vector to array:";
for (var i = 0; i < outVector.length; i++)
{
outVector[i];
result += outVector[i].toString() + ",";
}
document.getElementById('P6').innerHTML = result;
.NET
.NET 言語は IVector<T> を IList<T> として認識します。
private void SortListItems()
{
IList<int> myList = new List<int>();
myList.Add(5);
myList.Add(9);
myList.Add(17);
myList.Add(2);
var nativeObject = new CppComponent.CollectionExample();
IList<int> mySortedList = nativeObject.SortVector(myList);
foreach (var item in mySortedList)
{
ResultText.Text += " " + item.ToString();
}
}
IMap を渡す場合
C++
// #include <map>
//#include <collection.h>
Windows::Foundation::Collections::IMap<int, Platform::String^> ^GetMap(void)
{
Windows::Foundation::Collections::IMap<int, Platform::String^> ^ret =
ref new Platform::Collections::Map<int, Platform::String^>;
ret->Insert(1, "One ");
ret->Insert(2, "Two ");
ret->Insert(3, "Three ");
ret->Insert(4, "Four ");
ret->Insert(5, "Five ");
return ret;
}
JavaScript
// Call the method to get the map
var outputMap = nativeObject.getMap();
var mStr = "Map result:" + outputMap.lookup(1) + outputMap.lookup(2)
+ outputMap.lookup(3) + outputMap.lookup(4) + outputMap.lookup(5);
document.getElementById('P7').innerHTML = mStr;
.NET
.NET 言語は IMap を IDictionary<K,V> として認識します。
private void GetDictionary()
{
var nativeObject = new CppComponent.CollectionExample();
IDictionary<int, string> d = nativeObject.GetMap();
ResultText.Text += d[2].ToString();
}
Properties
Visual C++ コンポーネント拡張のパブリック ref クラスは、property キーワードを使用して、パブリック データ メンバーをプロパティとして公開します。この概念は .NET Framework のプロパティと同じです。trivial プロパティは機能が暗黙的であるため、データ メンバーに似ています。非 trivial プロパティには、明示的な get アクセサーと set アクセサーがあり、値の「バッキング ストア」である名前付きのプライベート変数があります。この例では、プライベート メンバー variable _propertyAValue は PropertyA のバッキング ストアです。プロパティの値が変化するときにイベントを発生できます。クライアント アプリは、そのイベントを受け取るように登録することができます。
C++
//Properties
public delegate void PropertyChangedHandler(Platform::Object^ sender, int arg);
public ref class PropertyExample sealed
{
public:
PropertyExample(){}
// Event that is fired when PropetyA changes
event PropertyChangedHandler^ PropertyChangedEvent;
// Property that has custom setter/getter
property int PropertyA
{
int get() { return m_propertyAValue; }
void set(int propertyAValue)
{
if (propertyAValue != m_propertyAValue)
{
m_propertyAValue = propertyAValue;
// Fire event. (See event example below.)
PropertyChangedEvent(this, propertyAValue);
}
}
}
// Trivial get/set property that has a compiler-generated backing store.
property Platform::String^ PropertyB;
private:
// Backing store for propertyA.
int m_propertyAValue;
};
JavaScript
var nativeObject = new CppComponent.PropertyExample();
var propValue = nativeObject.propertyA;
document.getElementById('P8').innerHTML = propValue;
//Set the string property
nativeObject.propertyB = "What is the meaning of the universe?";
document.getElementById('P9').innerHTML += nativeObject.propertyB;
.NET
.NET 言語では、.NET Framework オブジェクトの場合と同様に、ネイティブ C++ オブジェクトのプロパティにアクセスします。
private void GetAProperty()
{
// Get the value of the integer property
// Instantiate the C++ object
var obj = new CppComponent.PropertyExample();
// Get an integer property
var propValue = obj.PropertyA;
ResultText.Text += propValue.ToString();
// Set a string property
obj.PropertyB = " What is the meaning of the universe?";
ResultText.Text += obj.PropertyB;
}
デリゲートおよびイベント
delegate は、関数オブジェクトを表す Windows ランタイム 型です。デリゲートは、後で実行するアクションを指定するために、イベント、コールバック、非同期メソッド呼び出しに関連して使用します。デリゲートは、関数オブジェクトのように、関数の戻り値の型およびパラメーターの型を確認するためにコンパイラを有効にすることによってタイプ セーフを提供します。デリゲートの宣言は関数シグニチャに似ており、実装はクラス定義、また呼び出しは関数の呼び出しに似ています。
イベント リスナーの追加
指定されたデリゲート型のパブリック メンバーの宣言に event キーワードを使用できます。クライアント コードは、特定の言語で用意されている標準機能を使用してイベントをサブスクライブします。
C++
public:
event SomeHandler^ someEvent;
この例では、前のプロパティに関するセクションと同じ C++ コードを使用します。
JavaScript
function Button_Click() {
var nativeObj = new CppComponent.PropertyExample();
// Define an event handler method
var singlecasthandler = function (ev) {
document.getElementById('P10').innerHTML = "The button was clicked and the value is " + ev;
};
// Subscribe to the event
nativeObj.onpropertychangedevent = singlecasthandler;
// Set the value of the property and fire the event
var propValue = 21;
nativeObj.propertyA = 2 * propValue;
}
.NET
.NET 言語の場合、C++ コンポーネントのイベントにサブスクライブすることは、.NET Framework クラスのイベントにサブスクライブすることと同じです。
//Subscribe to event and call method that causes it to be fired.
private void TestMethod()
{
var objWithEvent = new CppComponent.PropertyExample();
objWithEvent.PropertyChangedEvent += objWithEvent_PropertyChangedEvent;
objWithEvent.PropertyA = 42;
}
//Event handler method
private void objWithEvent_PropertyChangedEvent(object __param0, int __param1)
{
ResultText.Text = "the event was fired and the result is " +
__param1.ToString();
}
1 つのイベントに複数のイベント リスナーを追加する
JavaScript には、複数のハンドラーで単一のイベントをサブスクライブすることができる addEventListener メソッドがあります。
C++
public delegate void SomeHandler(Platform::String^ str);
public ref class LangSample sealed
{
public:
event SomeHandler^ someEvent;
property Platform::String^ PropertyA;
// Method that fires an event
void FireEvent(Platform::String^ str)
{
someEvent(Platform::String::Concat(str, PropertyA->ToString()));
}
//...
};
JavaScript
// Add two event handlers
var multicast1 = function (ev) {
document.getElementById('P11').innerHTML = "Handler 1: " + ev.target;
};
var multicast2 = function (ev) {
document.getElementById('P12').innerHTML = "Handler 2: " + ev.target;
};
var nativeObject = new CppComponent.LangSample();
//Subscribe to the same event
nativeObject.addEventListener("someevent", multicast1);
nativeObject.addEventListener("someevent", multicast2);
nativeObject.propertyA = "42";
// This method should fire an event
nativeObject.fireEvent("The answer is ");
.NET
C# の場合、前の例で示したように += 演算子を使用することで、任意の数のイベント ハンドラーをイベントにサブスクライブできます。
列挙体
C++ の Windows ランタイム列挙型は、public class enum を使用して宣言されます。これは、標準 C++ のスコープ列挙型に似ています。
C++
public enum class Direction {North, South, East, West};
public ref class EnumExampleClass sealed
{
public:
property Direction CurrentDirection
{
Direction get(){return m_direction; }
}
private:
Direction m_direction;
};
JavaScript
列挙値は、C++ と JavaScript の間で整数として渡されます。C++ の列挙体と同じ名前付きの値を含む JavaScript オブジェクトをオプションで宣言し、次のように使用できます。
var Direction = { 0: "North", 1: "South", 2: "East", 3: "West" };
//. . .
var nativeObject = new CppComponent.EnumExampleClass();
var curDirection = nativeObject.currentDirection;
document.getElementById('P13').innerHTML =
Direction[curDirection];
.NET
C# と Visual Basic のどちらの言語でも列挙型がサポートされます。この 2 つの言語では、.NET Framework の列挙型と同様に C ++ パブリック列挙型クラスを認識します。
非同期メソッド
他の Windows ランタイム オブジェクトによって公開される非同期メソッドを実装するには、task クラス (同時実行ランタイム) を使用します。詳細については、「Asychronous Programming in C++」と「タスクの並列化 (同時実行ランタイム)」を参照してください。
C++ で非同期メソッドを実装するには、ppltasks.h で定義されている create_async を使用します。詳細については、「C++ における Windows ストア アプリ用の非同期操作の作成」を参照してください。例については、「チュートリアル : C++ での基本 Windows ランタイム コンポーネントの作成および JavaScript による呼び出し」を参照してください。.NET 言語では、.NET Framework で定義される非同期メソッドと同様に C++ 非同期メソッドが使用されます。
例外
Windows ランタイム によって定義された任意の例外の型をスローできます。Windows ランタイム のどの例外の型からもカスタム型は取得できません。ただし、COMException をスローし、例外をキャッチするコードがアクセスできるカスタム HRESULT を提供できます。COMException でカスタム メッセージを指定する方法はありません。
デバッグのヒント
コンポーネント DLL を含む JavaScript ソリューションをデバッグする場合、デバッガーを設定して、スクリプトのステップ実行か、ネイティブ コードのステップ実行を有効にできますが、その両方を同時に有効にすることはできません。設定を変更するには、ソリューション エクスプローラーで JavaScript プロジェクト ノードを選択し、[プロパティ]、[デバッグ]、[デバッガーの種類] をクリックします。
パッケージ デザイナーで適切な機能を選択してください。たとえば、Windows ランタイム API を使用してファイルを開く場合、パッケージ デザイナーの [機能] ペインの [ドキュメント ライブラリ アクセス] チェック ボックスをオンにしてください。
JavaScript コードがコンポーネントのパブリック プロパティまたはメソッドを認識していないようであるなら、JavaScript で Camel 形式の大文字小文字の区別を使用していることを確認します。たとえば、LogCalc C++ メソッドは、JavaScript では logCalc として参照する必要があります。
ソリューションから C++ Windows ランタイム コンポーネント プロジェクトを削除する場合、JavaScript のプロジェクトから手動でプロジェクト参照も削除する必要があります。これを行わなかった場合、後続のデバッグまたはビルド操作が妨げられます。その後、必要に応じて DLL にアセンブリ参照を追加できます。
参照
概念
チュートリアル : C++ での基本 Windows ランタイム コンポーネントの作成および JavaScript による呼び出し
その他の技術情報
Roadmap for Windows Store apps using C++
JavaScript および C++ での Windows ストア アプリ (Bing Maps Trip Optimizer) の開発