Share via


如何:在通用 Windows 平台应用中使用现有 C++ 代码

可以通过不同的方式在通用 Windows 平台 (UWP) 项目中使用现有的 C++ 代码。 有些方式不需要在启用组件扩展 (C++/CX) 的情况下(即使用 /ZW 选项)重新编译代码,有些则需要。 可能需要将代码保留在标准 C++ 中,或者为某些代码保留经典的 Win32 编译环境。 如果做出了适当的体系结构选择,则你仍可以这样做。 假设你的所有代码包含 UWP UI 以及向 C#、Visual Basic 和 JavaScript 调用方公开的类型。 这些代码应位于 Windows 应用项目和 Windows 运行时组件项目中。 仅从 C++(包括 C++/CX)调用的代码可以位于使用 /ZW 选项编译的项目中,也可以位于标准 C++ 项目中。 对于未使用禁止的 API 的纯二进制代码,可以通过将其作为静态库链接来使用它们。 或者,可将其与应用一起打包为内容并将其加载到 DLL 中。

要在 UWP 环境中运行桌面程序,最简单的方法或许是使用桌面桥技术。 这些技术包括 Desktop App Converter,此工具可将现有应用程序打包为 UWP 应用,而无需更改代码。 有关详细信息,请参阅桌面桥

本文的余下内容介绍如何将 C++ 库(DLL 和静态库)移植到通用 Windows 平台。 你可能需要移植代码,以便将核心 C++ 逻辑用于多个 UWP 应用。

UWP 应用在受保护的环境中运行。 因此,不允许发出许多可能危及平台安全的 Win32、COM 和 CRT API 调用。 /ZW 编译器选项可以检测此类调用并生成错误。 可以使用应用程序上的应用认证工具包检测调用禁止的 API 的代码。 有关详细信息,请参见 Windows 应用认证工具包

如果源代码适用于库,则你可以尝试消除禁止的 API 调用。 有关禁止的 API 列表,请参阅用于 UWP 应用的 Win32 和 COM API 以及通用 Windows 平台应用中不支持的 CRT 函数。 可通过 UWP 应用中的 Windows API 替代项,找到一些替代项。

如果只是尝试从通用 Windows 项目添加引用到经典桌面库,你将得到一条显示库不兼容的错误消息。 如果它是静态库,你可以通过将该库(.lib 文件)添加到链接器输入来链接到该库,如同在经典 Win32 应用程序中所做的那样。 如果只有二进制库可用,则这是唯一选项。 静态库将链接到应用的可执行文件。 但是,对于在 UWP 应用中使用的 Win32 DLL,必须通过将其包含在项目中并将其标记为“内容”,来将其打包到应用中。 若要在 UWP 应用中加载 Win32 DLL,还必须调用 LoadPackagedLibrary 而不是 LoadLibraryLoadLibraryEx

如果你有适用于 DLL 或静态库的源代码,可以使用 /ZW 编译器选项将其重新编译为 UWP 项目。 然后,可以使用解决方案资源管理器添加对它的引用并在 C++ UWP 应用中使用它。 使用导出库链接 DLL。

若要向其他语言中的调用方公开功能,则可以将库转换为 Windows 运行时组件。 Windows 运行时组件与普通的 DLL 的不同之处在于它们包括 .winmd 文件格式的元数据,这些元数据以 .NET 和 JavaScript 的使用者需要的方式介绍内容。 若要将 API 元素公开给其他语言,可以添加 C++/CX 构造(例如 ref 类)并将其公开。 在 Windows 10 以及更高版本中,建议使用 C++/WinRT 库而不是 C++/CX。

前面的讨论不适用于 COM 组件,COM 组件必须以不同方式处理。 如果 EXE 或 DLL 中有 COM 服务器,可以在通用 Windows 项目中使用它。 将其打包为免注册 COM 组件,将其作为内容文件添加到项目中,然后使用 CoCreateInstanceFromApp 将其实例化。 有关详细信息,请参阅在 Windows 应用商店 C++ 项目中使用 Free-COM DLL

如果你想要将现有的 COM 库移植到 UWP,也可以将其转换为 Windows 运行时组件。 我们建议将 C++/WinRT 库用于此类端口,但也可以使用 Windows 运行时 C++ 模板库 (WRL)。 WRL 已弃用,它不支持 ATL 和 OLE 的所有功能。 此类端口是否可行取决于组件所需的 COM、ATL 和 OLE 功能。

无论选择哪种开发方案,都应该了解一些宏定义。 可以在代码中使用这些宏,以便在经典桌面 Win32 和 UWP 中按条件编译代码。

#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PC_APP)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)

这些语句分别适用于 UWP 应用、Windows Phone 应用商店应用、都适用或都不适用(仅针对经典 Win32 桌面)。 这些宏仅在 Windows SDK 8.1 和更高版本中可用。

本文包含以下过程:

在 UWP 应用中使用 Win32 DLL

为了提高安全性和可靠性,通用 Windows 应用在受限的运行时环境中运行。 不能像在经典 Windows 桌面应用程序中那样使用任何本机 DLL。 如果你有 DLL 的源代码,则可以移植此代码,以便使其在 UWP 上运行。 你首先更改几个项目设置和项目文件元数据,以将此项目标识为 UWP 项目。 使用 /ZW 选项重新编译库代码,从而启用 C++/CX。 由于与该环境相关的控制更严格,在 UWP 应用中,某些 API 调用是不被允许的。 有关详细信息,请参阅 UWP 应用的 Win32 和 COM API

如果你的本机 DLL 使用 __declspec(dllexport) 导出函数,则可以通过将 DLL 重新编译为 UWP 项目,从 UWP 应用中调用这些函数。 例如,假设我们有一个名为 Giraffe 的 Win32 DLL 项目,它使用类似于以下标头文件的代码,可导出几个类及其方法

// giraffe.h
// Define GIRAFFE_EXPORTS when building this DLL
#pragma once

#ifdef GIRAFFE_EXPORTS
#define GIRAFFE_API __declspec(dllexport)
#else
#define GIRAFFE_API
#endif

GIRAFFE_API int giraffeFunction();

class Giraffe
{
    int id;
        Giraffe(int id_in);
    friend class GiraffeFactory;

public:
    GIRAFFE_API int GetID();
};

class GiraffeFactory
{
    static int nextID;

public:
    GIRAFFE_API GiraffeFactory();
    GIRAFFE_API static int GetNextID();
    GIRAFFE_API static Giraffe* Create();
};

以下代码文件:

// giraffe.cpp
#include "pch.h"
#include "giraffe.h"

Giraffe::Giraffe(int id_in) : id(id_in)
{
}

int Giraffe::GetID()
{
    return id;
}

int GiraffeFactory::nextID = 0;

GiraffeFactory::GiraffeFactory()
{
    nextID = 0;
}

int GiraffeFactory::GetNextID()
{
    return nextID;
}

Giraffe* GiraffeFactory::Create()
{
    return new Giraffe(nextID++);
}

int giraffeFunction();

项目(pch.hdllmain.cpp)中的其他所有内容都属于标准 Win32 项目模板。 代码定义宏 GIRAFFE_API,定义 GIRAFFE_EXPORTS 时,该宏解析为 __declspec(dllexport)。 也就是说,它是在项目生成为 DLL 时定义的,而不是在客户端使用 giraffe.h 标头时定义的。 此 DLL 可以在 UWP 项目中使用,而无需更改源代码。 只有某些项目设置和属性需要更改。

如果你的本机 DLL 使用 __declspec(dllexport) 公开函数,则以下过程适用。

若要在无需创建新项目的情况下将本机 DLL 移植到 UWP

  1. 在 Visual Studio 中打开你的 DLL 项目。

  2. 打开 DLL 项目中的“项目属性”,并将“配置”设置为“所有配置”

  3. 在“项目属性”的“C/C++”>“常规”选项卡下,将“使用 Windows 运行时扩展”设置为“是(/ZW)”。 此属性启用组件扩展 (C++/CX)。

  4. 在“解决方案资源管理器”中,选择项目节点,打开快捷菜单,然后选择“卸载项目”。 然后,在卸载的项目节点上打开快捷菜单,然后选择编辑项目文件。 找到 WindowsTargetPlatformVersion 元素,并将其替换为下列元素。

    <AppContainerApplication>true</AppContainerApplication>
    <ApplicationType>Windows Store</ApplicationType>
    <WindowsTargetPlatformVersion>10.0.10156.0</WindowsTargetPlatformVersion>
    <WindowsTargetPlatformMinVersion>10.0.10156.0</WindowsTargetPlatformMinVersion>
    <ApplicationTypeRevision>10.0</ApplicationTypeRevision>
    

    关闭 .vcxproj 文件,再次打开快捷菜单,然后选择“重新加载项目”

    现在,解决方案资源管理器会将该项目标识为通用 Windows 项目

  5. 请确保预编译的头文件的名称正确。 在“预编译标头”部分,可能需要将“预编译标头文件”从 pch.h 更改为 stdafx.h;如果看到如下所示的错误,请采取其他解决方法

    错误 C2857:在源文件中没有找到用 /Ycpch.h 命令行选项指定的“#include”语句

    问题在于早期的项目模板对预编译标头文件使用不同的命名约定。 Visual Studio 2019 和更高版本的项目使用 pch.h

  6. 生成项目。 可能会收到一些有关不兼容的命令行选项的错误。 例如,许多较旧的 C++ 项目中默认设置有“启用最小重新生成(/Gm)”这一常用选项(但现在已弃用),它与 /ZW 不兼容

    为通用 Windows 平台编译时,某些功能不可用。 你将看到有关任何问题的编译器错误。 解决这些错误,直到有一个干净的生成。

  7. 要在同一解决方案中的 UWP 应用中使用该 DLL,请打开 UWP 项目节点的快捷菜单,然后选择“添加”>“引用”

    在“项目”>“解决方案”下,选中 DLL 项目旁边的复选框,然后选择“确定”按钮

  8. 将库的一个或多个头文件添加到 UWP 应用的 pch.h 文件中。

    #include "..\Giraffe\giraffe.h"
    
  9. 照常将代码添加到 UWP 项目中,以从 DLL 中调用函数并创建类型。

    MainPage::MainPage()
    {
        InitializeComponent();
        GiraffeFactory gf;
        Giraffe* g = gf.Create();
        int id = g->GetID();
    }
    

在 UWP 应用中使用本机 C++ 静态库

你可以在 UWP 项目中使用本机 C++ 静态库,但有一些限制和局限需要注意。 请先阅读 C++/CX 中的静态库。 你可以从 UWP 应用访问静态库中的本机代码,但不建议在此类静态库中创建公共 ref 类型。 如果使用 /ZW 选项编译静态库,则管理员(实际是经过伪装的链接器)会发出警告:

LNK4264: 正在将使用 /ZW 编译的对象文件归档到静态库中;请注意,创作 Windows 运行时类型时,建议不要与包含 Windows 运行时元数据的静态库链接

但是,无需使用 /ZW 重新编译 UWP 应用中的静态库即可使用该库。 库无法声明任何 ref 类型或使用 C++/CX 构造。 但是,如果你的目的只是使用本机代码库,则可以执行以下步骤。

若要在 UWP 项目中使用本机 C++ 静态库

  1. 在 UWP 项目的项目属性中,在左窗格中依次选择“配置属性”>“链接器”>“输入”。 在右窗格中,将路径添加到库中的“其他依赖项”属性中。 例如,对于将输出放入 <SolutionFolder>\Debug\MyNativeLibrary\MyNativeLibrary.lib 的项目中的库,请添加相对路径 Debug\MyNativeLibrary\MyNativeLibrary.lib

  2. 添加 include 语句,以将头文件引用到 pch.h 文件(若有)或所需的任何 .cpp 文件中,并开始添加使用库的代码。

    #include "..\MyNativeLibrary\MyNativeLibrary.h"
    

    不要在“解决方案资源管理器”的“引用”节点中添加引用。 该机制仅适用于 Windows 运行时组件。

将 C++ 库移植到 Windows 运行时组件

假设你要使用 UWP 应用的静态库中的本机 API。 如果你有本机库的源代码,可将代码移植到 Windows 运行时组件。 它不再是静态库;它将变成一个可以在任何 C++ UWP 应用中使用的 DLL。 此过程说明如何创建使用 C++/CX 扩展的新 Windows 运行时组件。 有关创建使用 C++/WinRT 的组件的信息,请参阅使用 C++/WinRT 的 Windows 运行时组件

使用 C++/CX 时,可以添加可供任何 UWP 应用代码中的客户端使用的 ref 类型和其他 C++/CX 构造。 可以从 C#、Visual Basic 或 JavaScript 访问这些类型。 基本过程:

  • 创建 Windows 运行时组件(通用 Windows)项目;
  • 将静态库的代码复制到该项目;
  • 解决 /ZW 选项在编译器中导致的错误。

若要将 C++ 库移植到 Windows 运行时组件

  1. 创建 Windows 运行时组件(通用 Windows)项目。

  2. 关闭该项目。

  3. 在“Windows 文件资源管理器”中找到新项目。 然后找到 C++ 库项目,该项目包含你想要移植的代码。 从 C++ 库项目复制源文件(头文件、代码文件和任何其他资源,包括子目录中的资源)。 将它们粘贴到新项目文件夹,确保保留相同的文件夹结构。

  4. 重新打开 Windows 运行时组件项目。 在“解决方案资源管理器”中打开项目节点的快捷菜单,然后选择“添加”>“现有项”

  5. 从原始项目中选择要添加的所有文件,然后选择“确定”。 如果子文件夹需要,则重复。

  6. 你现在可能有一些重复代码。 如果有多个预编译标头(例如 stdafx.hpch.h),请选择要保留的一个标头。 将任何所需的代码(比如 include 语句)复制到你要保留的标头中。 然后删除另一个标头,并在“预编译标头”下的项目属性中,确保头文件的名称正确。

    如果更改了要用作预编译标头的文件,请确保预编译标头选项适用于每个文件。 依次选择每个 .cpp 文件,打开其属性窗口,并确保所有项都设置为“使用 (/Yu)”(预编译标头除外,其应设置为“创建 (/Yc)”)

  7. 生成项目并解决任何错误。 这些错误可能是因使用 /ZW 选项而导致的,或者是由新的 Windows SDK 版本导致的。 或者,它们可能反映了依赖关系,例如库依赖的头文件,或者旧项目与新项目之间的项目设置差异。

  8. 将公共 ref 类型添加到项目,或将普通类型转换为 ref 类型。 使用这些类型将入口点公开到要从 UWP 应用调用的功能中。

  9. 通过从 UWP 应用项目添加对组件的引用来测试该组件,然后添加某些代码来调用你创建的公共 API。

另请参阅

移植到通用 Windows 平台