Поделиться через


Создание компонентов среды выполнения Windows с помощью C++/CX

Примечание.

Этот раздел поможет вам поддерживать приложение C++/CX. Но мы рекомендуем использовать C++/WinRT для новых приложений. C++/WinRT — это полностью стандартная проекция языка C++17 для API среды выполнения Windows (WinRT), реализованная как библиотека на основе файлов заголовков и предназначенная для предоставления вам первоклассного доступа к современным интерфейсам API Windows. Сведения о создании компонента среды выполнения Windows с помощью C++/WinRT см. в статье Создание компонентов среды выполнения Windows с помощью C++/WinRT.

В этом разделе показано, как использовать C++/CX для создания компонента среда выполнения Windows — компонента, вызываемого из универсального приложения Windows, созданного с помощью любого языка среда выполнения Windows (C#, Visual Basic, C++или JavaScript).

Существует несколько причин для создания компонента среда выполнения Windows в C++.

  • Чтобы получить преимущество производительности C++ в сложных или вычислительных интенсивных операциях.
  • Чтобы повторно использовать код, который уже написан и протестирован.

При создании решения, содержащего проект JavaScript или .NET, а также проект компонента среда выполнения Windows, файлы проекта JavaScript и скомпилированные библиотеки DLL объединяются в один пакет, который можно отлаживать локально в симуляторе или удаленно на связанном устройстве. Вы также можете распространять только проект компонента в виде пакета SDK расширения. Дополнительные сведения о пакетах SDK см. в статье Создание пакета средств разработки программного обеспечения.

Как правило, при коде компонента C++/CX используйте обычную библиотеку C++ и встроенные типы, за исключением границы абстрактного двоичного интерфейса (ABI), в котором передаются данные в другой winmd-пакет и из него. Там используйте среда выполнения Windows типы и специальный синтаксис, поддерживаемый C++/CX для создания и управления этими типами. Кроме того, в коде C++/CX используйте такие типы, как делегат и событие для реализации событий, которые можно создать из компонента и обрабатывать в JavaScript, Visual Basic, C++или C#. Дополнительные сведения о синтаксисе C++/CX см. в справочнике по языку Visual C++ (C++/CX).

Правила регистра и именования

JavaScript

JavaScript учитывает регистр. Поэтому необходимо следовать этим соглашениям о регистре:

  • При ссылке на пространства имен и классы C++ используйте тот же регистр, который используется на стороне C++.
  • При вызове методов используйте регистр верблюда, даже если имя метода заглавно на стороне C++. Например, метод GetDate() C++ должен вызываться из JavaScript как getDate().
  • Имя активируемого класса и имя пространства имен не может содержать символы ЮНИКОДа.

.NET

Языки .NET следуют их обычным правилам регистра.

Создание экземпляра объекта

Через границу ABI можно передавать только среда выполнения Windows типы. Компилятор вызовет ошибку, если компонент имеет тип, например std::wstring в качестве возвращаемого типа или параметра в общедоступном методе. Встроенные типы компонентов Visual C++ (C++/CX) включают обычные скаляры, такие как int и double, а также их эквиваленты typedef int32, float64 и т. д. Дополнительные сведения см. в разделе 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();

Встроенные типы, типы библиотек и типы среда выполнения Windows C++/CX

Активируемый класс (также известный как класс ссылок) — это класс, который можно создать на другом языке, например JavaScript, C# или Visual Basic. Чтобы использовать другой язык, компонент должен содержать по крайней мере один активируемый класс.

Компонент среда выполнения Windows может содержать несколько общедоступных активируемых классов, а также дополнительные классы, известные только внутри компонента. Примените атрибут WebHostHidden к типам C++/CX, которые не предназначены для отображения в JavaScript.

Все общедоступные классы должны находиться в одном корневом пространстве имен, которое имеет то же имя, что и файл метаданных компонента. Например, экземпляр класса с именем A.B.C.MyClass может быть создан, только если он определен в файле метаданных с именем A.winmd, A.B.winmd или A.B.C.winmd. Имя DLL-файла не обязательно должно соответствовать имени WINMD-файла.

Клиентский код создает экземпляр компонента с помощью нового ключевого слова (New in Visual Basic) так же, как для любого класса.

Активируемый класс должен быть объявлен как открытый класс ссылок запечатанным. Ключевое слово класса ref сообщает компилятору создать класс как совместимый тип среда выполнения Windows, а ключевое слово с запечатанным указывает, что класс не может быть унаследован. В настоящее время среда выполнения Windows не поддерживает обобщенную модель наследования. Ограниченная модель наследования поддерживает создание пользовательских элементов управления XAML. Дополнительные сведения см. в статьях Ref для классов и структур (C++/CX).

Для C++/CX все числовые примитивы определяются в пространстве имен по умолчанию. Пространство имен платформы содержит классы C++/CX, относящиеся к системе типов среда выполнения Windows. К ним относятся класс 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, определите объект JavaScript, имеющий те же элементы, что и структуру значений, определенную в C++/CX. Затем этот объект можно передать в качестве аргумента методу 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);
}

Другой подход — определить класс, реализующий 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 может содержать перегруженные методы, но 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::D efaultOverload к сигнатуре метода в файле заголовка.

Этот JavaScript всегда вызывает перегрузку атрибутов:

var nativeObject = new CppComponent.NumberClass();
var num = nativeObject.getNumber(9);
document.getElementById('P4').innerHTML = num;

.NET

Языки .NET распознают перегрузки в классе ссылок C++/CX так же, как и в любом классе .NET.

Дата/время

В среда выполнения Windows объект Windows::Foundation::D ateTime — это всего лишь 64-разрядное целое число со знаком, представляющее число интервалов 100-nanosecond до или после 1 января 1601 года. В объекте Windows:Foundation::D ateTime нет методов. Вместо этого каждый язык проектируют DateTime таким образом, чтобы он был собственным языком: объект Date в JavaScript и типах System.DateTime и System.DateTimeOffset в .NET.

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;
    }
};

При передаче значения DateTime из C++/CX в JavaScript 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 передает System.DateTime компоненту C++/CX, метод принимает его как Windows::Foundation::D ateTime. Когда компонент передает метод Windows::Foundation::D ateTime методу .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();
}

Коллекции и массивы

Коллекции всегда передаются через границу ABI в качестве дескрипторов для среда выполнения Windows типов, таких как Windows::Foundation::Collections::IVector^ и Windows::Foundation::Collections::IMap^. Например, если вы возвращаете дескриптор в Platform::Collections::Map, он неявно преобразуется в Windows::Foundation::Collections::IMap^. Интерфейсы коллекции определяются в пространстве имен, отдельном от классов C++/CX, которые предоставляют конкретные реализации. Языки JavaScript и .NET используют интерфейсы. Дополнительные сведения см. в статьях "Коллекции" (C++/CX) и array and 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 видят 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

// #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();
}

Свойства

Открытый класс ссылок в расширениях компонентов C++/CX предоставляет общедоступные элементы данных в качестве свойств с помощью ключевого слова свойства. Концепция идентична свойствам .NET. Тривиальное свойство напоминает элемент данных, так как его функциональность неявна. Нетривиальное свойство имеет явные методы получения и задания доступа и именованной частной переменной, которая является резервным хранилищем для значения. В этом примере переменная частного члена _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 получают доступ к свойствам собственного объекта C++/CX так же, как и в объекте .NET.

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, представляющий объект функции. Делегаты можно использовать в связи с событиями, обратными вызовами и асинхронными вызовами метода, чтобы указать действие, которое необходимо выполнить позже. Как и объект функции, делегат обеспечивает безопасность типов, позволяя компилятору проверять тип возвращаемого типа и типы параметров функции. Объявление делегата напоминает сигнатуру функции, реализация напоминает определение класса, а вызов напоминает вызов функции.

Добавление прослушивателя событий

Ключевое слово события можно использовать для объявления общедоступного члена указанного типа делегата. Клиентский код подписывается на событие с помощью стандартных механизмов, предоставляемых на определенном языке.

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();
}

Добавление нескольких прослушивателей событий для одного события

JavaScript имеет метод 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#любое количество обработчиков событий может подписаться на событие с помощью оператора +=, как показано в предыдущем примере.

Перечисления

Перечисление среда выполнения Windows в C++/CX объявляется с помощью перечисления общедоступного класса; оно напоминает перечисление с областью действия в стандартном 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 в виде целых чисел. При необходимости можно объявить объект JavaScript, содержащий те же именованные значения, что и перечисление C++/CX, и использовать его следующим образом.

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 поддерживаются языковые функции перечисления. Эти языки видят открытый класс перечисления C++ так же, как и перечисление .NET.

Асинхронные методы

Чтобы использовать асинхронные методы, предоставляемые другими объектами среда выполнения Windows, используйте класс задач (среда выполнения параллелизма). Дополнительные сведения см. в разделе и параллелизме задач (среда выполнения параллелизма).

Чтобы реализовать асинхронные методы в C++/CX, используйте функцию create_async , определенную в ppltasks.h. Дополнительные сведения см. в разделе "Создание асинхронных операций" в C++/CX для приложений UWP. Пример см. в пошаговом руководстве по созданию компонента C++/CX среда выполнения Windows и вызове его из JavaScript или C#. Языки .NET используют асинхронные методы C++/CX так же, как и любой асинхронный метод, определенный в .NET.

Исключения

Вы можете вызвать любой тип исключения, определенный среда выполнения Windows. Не удается наследить пользовательские типы от любого типа исключения среда выполнения Windows. Однако вы можете создать COMException и предоставить пользовательский HRESULT, к которому можно получить доступ с помощью кода, который перехватывает исключение. Невозможно указать настраиваемое сообщение в COMException.

Советы по отладке

При отладке решения JavaScript с библиотекой DLL компонента можно настроить отладчик для включения пошагового выполнения скрипта или пошагового выполнения машинного кода в компоненте, но не одновременно. Чтобы изменить параметр, выберите узел проекта JavaScript в Обозреватель решений и выберите свойства, отладку, тип отладчика.

Убедитесь, что в конструкторе пакетов выбраны соответствующие возможности. Например, если вы пытаетесь открыть файл изображения в библиотеке изображений пользователя с помощью API среда выполнения Windows, установите флажок "Библиотека рисунков" в области возможностей конструктора манифестов.

Если код JavaScript не распознает общедоступные свойства или методы в компоненте, убедитесь, что в JavaScript вы используете верблюдю регистр. Например, метод LogCalc C++/CX должен ссылаться как logCalc в JavaScript.

При удалении проекта компонента C++/C среда выполнения Windows X из решения необходимо также вручную удалить ссылку на проект JavaScript. Сбой этого предотвращает последующие операции отладки или сборки. При необходимости можно добавить ссылку на сборку в библиотеку DLL.