C++/CX を使用した Windows ランタイム コンポーネント
Note
このトピックは、C++/CX アプリケーションの管理ができるようにすることを目的としています。 ただし、新しいアプリケーションには C++/WinRT を使用することをお勧めします。 C++/WinRT は Windows ランタイム (WinRT) API の標準的な最新の C++17 言語プロジェクションで、ヘッダー ファイル ベースのライブラリとして実装され、最新の Windows API への最上位アクセス権を提供するように設計されています。 C++/WinRT を使用して Windows ランタイム コンポーネントを作成する方法については、「C++/WinRT を使用した Windows Runtime コンポーネント」を参照してください。
このトピックでは、C++/CX を使用してWindows ランタイム コンポーネントを作成する方法について説明します。これは、任意のWindows ランタイム言語 (C#、Visual Basic、C++、または JavaScript) を使用して構築されたユニバーサル Windows アプリから呼び出し可能なコンポーネントです。
Windows ランタイム コンポーネントを C++ で構築するのには、いくつかの理由があります。
- 複雑な操作または計算負荷の高い操作で C++ のパフォーマンス上の利点を得る。
- 既に記述およびテスト済みのコードを再利用するため。
JavaScript または .NET プロジェクト、および Windows ランタイム コンポーネント プロジェクトを含むソリューションをビルドすると、JavaScript プロジェクト ファイルとコンパイル済み DLL が 1 つのパッケージにマージされます。このパッケージは、シミュレーターでローカルにデバッグすることも、テザリングされたデバイスでリモートでデバッグすることもできます。 コンポーネント プロジェクトだけを拡張機能 SDK として配布することもできます。 詳しくは、「Creating a Software Development Kit」(ソフトウェア開発キットの作成) をご覧ください。
一般的に、C++/CX コンポーネントをコーディングする場合、他の .winmd パッケージのコードとの間でデータをやり取りする抽象バイナリ インターフェイス (ABI) の境界を除いて、標準の C++ ライブラリと組み込み型を使います。 境界では、Windows ランタイム型と、それらの型の作成と操作をサポートする C++/CX の特別な構文を使います。 さらに、C++/CX コードでは、コンポーネントから生成され、JavaScript、Visual Basic、C# で処理されるイベントの実装に delegate や event などの型を使います。 C++/CX 構文について詳しくは、「Visual C++ の言語リファレンス (C++/CX)」をご覧ください。
大文字と小文字の区別と名前付け規則
JavaScript
JavaScript では大文字と小文字が区別されます。 したがって、次の大文字と小文字の表記規則に従う必要があります。
- C++ の名前空間とクラスを参照する場合は、C++ 側で使用されるのと同じ大文字と小文字を使用します。
- メソッドを呼び出すときは、C++ 側でメソッド名が大文字になっている場合でも、キャメル ケースを使用します。 たとえば、C++ メソッド GetDate() を JavaScript から getDate() として呼び出す必要があります。
- アクティブ化可能なクラス名と名前空間名に UNICODE 文字を含めることはできません。
.NET
.NET 言語は、通常の大文字と小文字の規則に従います。
オブジェクトのインスタンス化
ABI 境界を越えて渡すことができるのは、Windows ランタイム型のみです。 コンポーネントが戻り値の型またはパブリック メソッドのパラメーターとして std::wstring のような型を持っている場合、コンパイラはエラーを発生させます。 Visual C++ コンポーネント拡張機能 (C++/CX) 組み込み型には、int や double などの通常のスカラーと、int32、float64 などの typedef 同等のスカラーが含まれます。 詳細については、「 Type System (C++/CX)」を参照してください。
// 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);
}
};
//Instantiation in JavaScript (requires "Add reference > Project reference")
var nativeObject = new CppComponent.SampleRefClass();
//Call a method and display result in a XAML TextBlock
var num = nativeObject.LogCalc(21.5);
ResultText.Text = num.ToString();
C++/CX の組み込み型、ライブラリ型、および Windows ランタイム型
アクティブ化可能なクラス (ref クラスとも呼ばれます) は、JavaScript、C#、Visual Basic などの別の言語からインスタンス化できるクラスです。 別の言語から使用できるようにするには、コンポーネントに少なくとも 1 つのアクティブ化可能なクラスが含まれている必要があります。
Windows ランタイム コンポーネントには、複数のパブリックアクティブ化可能なクラスと、コンポーネントの内部でのみ認識される追加のクラスを含めることができます。 JavaScript に公開することを目的としていない C++/CX の型に WebHostHidden 属性を適用します。
すべてのパブリック クラスは、コンポーネント メタデータ ファイルと同じ名前を持つ同じルート名前空間に存在する必要があります。 たとえば、A.B.C.MyClass という名前のクラスは、A.winmd または A.B.winmd または A.B.C.winmd という名前のメタデータ ファイルで定義されている場合のみインスタンス化できます。 DLL の名前が .winmd ファイル名と一致する必要はありません。
クライアント コードは、任意のクラスと同様に、 new (Visual Basic では New ) キーワードを使用して、コンポーネントのインスタンスを作成します。
アクティブ化可能なクラスは、 public ref クラス sealed として宣言する必要があります。 ref クラスキーワードは、クラスをWindows ランタイム互換性のある型として作成するようコンパイラに指示し、sealed キーワードはクラスを継承できないことを指定します。 Windows ランタイムは現在、一般化された継承モデルをサポートしていません。限られた継承モデルでは、カスタム XAML コントロールの作成がサポートされています。 詳細については、「 Ref クラスと構造体 (C++/CX)」を参照してください。
C++/CX では、すべてのプリミティブ数値型が既定の名前空間で定義されます。 Platform 名前空間には、Windows ランタイム型システムに固有の C++/CX クラスが含まれます。 これには、 Platform::String クラスと Platform::Object クラスが含まれます。 Platform::Collections::Map クラスや Platform::Collections::Vector クラスなどの具象コレクション型は、Platform::Collections 名前空間で定義されます。 これらの型が実装するパブリック インターフェイスは、 Windows::Foundation::Collections 名前空間 (C++/CX)で定義されています。 これらのインターフェイス型は、JavaScript、C#、および Visual Basic で使用されます。 詳細については、「 Type System (C++/CX)」を参照してください。
組み込み型の値を返すメソッド
// #include <valarray>
public:
double LogCalc(double input)
{
// Use C++ standard library as usual.
return std::log(input);
}
//Call a method
var nativeObject = new CppComponent.SampleRefClass;
var num = nativeObject.logCalc(21.5);
document.getElementById('P2').innerHTML = num;
カスタム値構造体を返すメソッド
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;}
}
};
}
ユーザーが定義した値の構造体を ABI の境界を越えて渡すには、C++/CX で定義された値の構造体と同じメンバーを持つ JavaScript オブジェクトを定義します。 その後で、そのオブジェクトを C++/CX メソッドの引数として渡し、オブジェクトが C++/CX 型として暗黙的に変換されるようにします。
// 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 を実装するクラスを定義することです (示されていません)。
.NET 言語の場合、C++/CX コンポーネントで定義されている型の変数を作成します。
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++/CX のパブリック 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);
あいまいな場合は、ヘッダー ファイル内のメソッド シグネチャに Windows::Foundation::Metadata::D efaultOverload 属性を適用することで、JavaScript が常に特定のオーバーロードを呼び出すようにすることができます。
この JavaScript は、常に属性付きオーバーロードを呼び出します。
var nativeObject = new CppComponent.NumberClass();
var num = nativeObject.getNumber(9);
document.getElementById('P4').innerHTML = num;
.NET
.NET 言語では、.NET クラスの場合と同様に、C++/CX の ref クラスのオーバーロードが認識されます。
DateTime
Windows ランタイムでは、Windows::Foundation::D ateTime オブジェクトは、1601 年 1 月 1 日以降に 100 ナノ秒間隔の数を表す 64 ビット符号付き整数です。 Windows:Foundation::D ateTime オブジェクトにはメソッドがありません。 代わりに、各言語では DateTime をその言語独自の方法で算出します。JavaScript では Date オブジェクト、.NET では System.DateTime 型および System.DateTimeOffset 型を利用します。
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;
}
};
C++/CX から 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 言語から C++/CX コンポーネントに System.DateTime を渡すと、メソッドはこの値を Windows::Foundation::DateTime として受け取ります。 コンポーネントが .NET メソッドに Windows::Foundation::DateTime を渡すと、その 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++/CX クラスとは別の名前空間で定義されます。 JavaScript と .NET 言語では、インターフェイスが使用されます。 詳細については、「 Collections (C++/CX) 」および「 Array および WriteOnlyArray (C++/CX)」を参照してください。
IVector を渡す
// 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;
}
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 言語では、IList<T> として IVector<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 を渡す
// #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;
}
// 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 言語には、IMap と IDictionary<K、V> が表示されます。
private void GetDictionary()
{
var nativeObject = new CppComponent.CollectionExample();
IDictionary<int, string> d = nativeObject.GetMap();
ResultText.Text += d[2].ToString();
}
プロパティ
Visual C++/CX コンポーネント拡張のパブリック ref クラスは、property キーワードを使って、パブリック データ メンバーをプロパティとして公開します。 この概念は .NET のプロパティと同じです。 単純なプロパティは、その機能が暗黙的であるため、データ メンバーに似ています。 単純でないプロパティには、明示的な get アクセサーと set アクセサーと、値の "バッキング ストア" である名前付きプライベート変数があります。 この例では、プライベート メンバー変数 \_propertyAValue は PropertyA のバッキング ストアです。 プロパティは、値が変更されたときにイベントを発生させ、クライアント アプリがそのイベントを受信するように登録できます。
//Properties
public delegate void PropertyChangedHandler(Platform::Object^ sender, int arg);
public ref class PropertyExample sealed
{
public:
PropertyExample(){}
// Event that is fired when PropertyA 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;
};
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 オブジェクトの場合と同様に、ネイティブ C++/CX オブジェクトのプロパティにアクセスします。
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;
}
デリゲートおよびイベント
デリゲートは、関数オブジェクトを表すWindows ランタイム型です。 イベント、コールバック、非同期メソッド呼び出しに関連してデリゲートを使用して、後で実行するアクションを指定できます。 関数オブジェクトと同様に、デリゲートは、コンパイラが関数の戻り値の型とパラメーター型を検証できるようにすることで、型セーフを提供します。 デリゲートの宣言は関数シグネチャに似ていて、実装はクラス定義に似ていて、呼び出しは関数呼び出しに似ています。
イベント リスナーの追加
event キーワードを使用して、指定したデリゲート型のパブリック メンバーを宣言できます。 クライアント コードは、特定の言語で提供される標準メカニズムを使用してイベントをサブスクライブします。
public:
event SomeHandler^ someEvent;
この例では、前のプロパティ セクションと同じ C++ コードを使用します。
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 言語の場合、C++ コンポーネントのイベントをサブスクライブすることは、.NET クラスのイベントをサブスクライブすることと同じです。
//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 には、複数のハンドラーが 1 つのイベントをサブスクライブできるようにする addEventListener メソッドがあります。
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()));
}
//...
};
// 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 ");
C# では、前の例に示すように、+= 演算子を使用して、任意の数のイベント ハンドラーでイベントをサブスクライブできます。
列挙型
C++/CX の Windows ランタイム列挙型は、public class enum を使って宣言されます。これは、標準 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;
};
列挙値は、C++/CX と JavaScript の間で整数として渡されます。 C++/CX の列挙体と同じ名前付きの値を含む 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];
C# と Visual Basic の両方に、列挙型の言語サポートがあります。 この 2 つの言語では、.NET の列挙型と同様に C ++ パブリック列挙型クラスを認識します。
非同期メソッド
他のWindows ランタイム オブジェクトによって公開される非同期メソッドを使用するには、task クラス (コンカレンシー ランタイム)を使用します。 詳細については、「タスクの並列処理 (コンカレンシー ランタイム)」を参照してください。
C++/CX で非同期メソッドを実装するには、ppltasks.h で定義されている create\_async 関数を使います。 詳しくは、「UWP アプリ用の C++ での非同期操作の作成」をご覧ください。 たとえば、「C++/CX Windows ランタイム コンポーネントの作成と JavaScript または C# からの呼び出しに関するチュートリアル」をご覧ください。 .NET 言語では、.NET で定義される非同期メソッドと同様に C++/CX 非同期メソッドが利用されます。
例外
Windows ランタイムによって定義されている任意の例外の種類をスローできます。 Windows ランタイム例外の種類からカスタム型を派生させることはできません。 ただし、COMException をスローし、例外をキャッチするコードからアクセスできるカスタム HRESULT を提供できます。 COMException でカスタム メッセージを指定する方法はありません。
デバッグのヒント
コンポーネント DLL を含む JavaScript ソリューションをデバッグする場合は、デバッガーを設定して、スクリプトのステップ実行またはコンポーネント内のネイティブ コードのステップ実行を有効にできますが、両方を同時に有効にすることはできません。 設定を変更するには、ソリューション エクスプローラーで JavaScript プロジェクト ノードを選択し、[プロパティ]、[デバッグ]、[デバッガーの種類] の順に選択します。
パッケージ デザイナーで必ず適切な機能を選んでください。 たとえば、Windows ランタイム API を使用してユーザーの Pictures ライブラリでイメージ ファイルを開こうとする場合は、マニフェスト デザイナーの [機能] ウィンドウで [画像ライブラリ] チェック ボックスをオンにしてください。
JavaScript コードがコンポーネント内のパブリック プロパティまたはメソッドを認識していないように見える場合は、JavaScript でキャメル ケースを使用していることを確認してください。 たとえば、LogCalc C++/CX メソッドは、JavaScript では logCalc として参照する必要があります。
C++/CX Windows ランタイム コンポーネント プロジェクトをソリューションから削除する場合、JavaScript プロジェクトからプロジェクト参照も手動で削除する必要があります。 これを行わないと、後続のデバッグまたはビルド操作が妨げられます。 その後、必要に応じてアセンブリ参照を DLL に追加できます。