.NET Core 编程指南中文版 —— 2.13 入门 -> 从本机代码承载.NET Core

编写自定义的 .NET Core 主机,以从本机代码控制 .NET 运行时

像所有的托管代码一样,.NET Core 应用程序也由主机执行。 主机负责启动运行时(包括 JIT 和垃圾回收器等组件)和调用托管的入口点。

承载 .NET Core 运行时是一个高级的场景,在大多数情况下,.NET Core 开发人员无需担心承载问题,因为 .NET Core 生成过程会提供默认主机来运行 .NET Core 应用程序。 但是,在某些特殊情况下,显式地承载 .NET Core 运行时是非常有用的 —— 无论是作为一种在本机进程中调用托管代码的方式,还是为了获得对运行时工作原理更好的控制。

本文概述了从本机代码启动 .NET Core 运行时和在其中执行托管代码的必要步骤。

系统必备

由于主机是本机应用程序,所以本教程将介绍如何构造 C++ 应用程序以承载 .NET Core。 将需要一个 C++ 开发环境(例如,Visual Studio 提供的环境)。

还将需要一个简单的 .NET Core 应用程序来测试主机,因此应安装 .NET Core SDK 并构建一个小型的 .NET Core 测试应用(例如,“Hello World”应用)。 使用通过新的 .NET Core 控制台项目模板创建的“Hello World”应用就足够了。

承载 API

可以使用两种不同的 API 来承载 .NET Core。 本文档(及其相关的示例)涵盖这两个选项。

  • 承载 .NET Core 运行时的首选方法是使用 CoreClrHost.h API。 此 API 公开一些函数,用于轻松地启动和停止运行时并调用托管代码(通过启动一个托管的 exe 或通过调用静态的托管方法)。
  • 也可以使用 mscoree.h 中的 ICLRRuntimeHost2 接口承载 .NET Core。 此 API 比 CoreClrHost.h 出现的时间更早,因此你可能看到过使用此 API 的较旧版本的主机。 它仍然可用,并且比起 CoreClrHost,它可以对主机进程进行更多控制。 但是对于大多数场景,CoreClrHost.h 现在是首选,因为它的 API 更简单。

示例主机

有关展示在下面的教程中所述步骤的示例主机,请访问 dotnet/samples GitHub 存储库。 该示例中的注释清楚地将这些教程中已编号的步骤与它们在示例中的执行位置关联。 有关下载说明,请参阅示例和教程

请记住,示例主机的用途在于提供学习指导,在纠错方面不甚严谨,其重在可读性而非效率。 更多的真实主机示例可从 dotnet/coreclr 存储库获取。 尤其是 CoreRun 主机和 Unix CoreRun 主机,它们是读者了解这些较简单示例后用于研究的不错的通用主机。

使用 CoreClrHost.h 创建主机

以下步骤详细说明如何使用 CoreClrHost.h API 在本机应用程序中启动 .NET Core 运行时并调用托管静态方法。 本文档中的代码片段使用一些特定于 Windows 的 API,但是完整示例主机同时显示 Windows 和 Linux 的代码路径。

步骤 1 - 查找和加载 CoreCLR

.NET Core 运行时 API 位于 coreclr.dll(对于 Windows)、libcoreclr.so(对于 Linux)或 libcoreclr.dylib(对于 macOS)。 承载 .NET Core 的第一步是加载 CoreCLR 库。 一些主机探测不同的路径或使用输入参数来查找库,而另一些主机能够从某个路径(例如,紧邻主机的路径,或从计算机范围内的位置)加载库。

找到库之后,系统会使用 LoadLibraryEx(对于 Windows)或 dlopen(对于 Linux/Mac)加载库。

HMODULE coreClr = LoadLibraryExA(coreClrPath.c_str(), NULL, 0);

步骤 2 - 获取 .NET Core 承载函数

CoreClrHost 有几个可用于承载 .NET Core 的重要方法:

  • coreclr_initialize:启动 .NET Core 运行时并设置默认(且仅设置)AppDomain。
  • coreclr_execute_assembly:执行托管程序集。
  • coreclr_create_delegate:创建指向托管方法的函数指针。
  • coreclr_shutdown:关闭 .NET Core 运行时。
  • coreclr_shutdown_2:类似 coreclr_shutdown,但还会检索托管代码的退出代码。

加载 CoreCLR 库之后,下一步是使用 GetProcAddress(对于 Windows)或 dlsym(对于 Linux/Mac)引用这些函数。

coreclr_initialize_ptr initializeCoreClr = (coreclr_initialize_ptr)GetProcAddress(coreClr, "coreclr_initialize");
coreclr_create_delegate_ptr createManagedDelegate = (coreclr_create_delegate_ptr)GetProcAddress(coreClr, "coreclr_create_delegate");
coreclr_shutdown_ptr shutdownCoreClr = (coreclr_shutdown_ptr)GetProcAddress(coreClr, "coreclr_shutdown");

步骤 3 - 准备运行时属性

在启动运行时之前,有必要准备一些属性来指定行为(特别是关于程序集加载器的行为)。

常用属性包括:

  • TRUSTED_PLATFORM_ASSEMBLIES 这是程序集路径列表(对于 Windows,使用“;”分隔,对于 Linux,使用“:”分隔),运行时在默认情况下能够解析这些路径。 一些主机有硬编码清单,其中列出了它们可以加载的程序集。 其他主机将把任何库放在这个列表上的特定位置(例如 coreclr.dll 旁边)。
  • APP_PATHS 这是一个用来探测程序集的路径的列表(如果在受信任的平台程序集 (TPA) 列表中找不到程序集)。 因为主机使用 TPA 列表可以更好地控制加载哪些程序集,所以对于主机来说,确定要加载的程序集并显式列出它们是最佳做法。 但是,如果需要探测运行时,则此属性可以支持该方案。
  • APP_NI_PATHS 此列表与 APP_PATHS 相似,不同之处在于其中的路径用于探测本机映像。
  • NATIVE_DLL_SEARCH_DIRECTORIES 此属性是一个路径列表,加载程序在查找通过 p/invoke 调用的本机库时应使用这些路径进行探测。
  • PLATFORM_RESOURCE_ROOTS 此列表包含的路径用于探测资源附属程序集(在区域性特定的子目录中)。

在此示例主机中,TPA 列表是通过简单列出当前目录中的所有库来进行构造的:

void BuildTpaList(const char* directory, const char* extension, std::string& tpaList)
{
    // This will add all files with a .dll extension to the TPA list.
    // This will include unmanaged assemblies (coreclr.dll, for example) that don't
    // belong on the TPA list. In a real host, only managed assemblies that the host
    // expects to load should be included. Having extra unmanaged assemblies doesn't
    // cause anything to fail, though, so this function just enumerates all dll's in
    // order to keep this sample concise.
    std::string searchPath(directory);
    searchPath.append(FS_SEPERATOR);
    searchPath.append("*");
    searchPath.append(extension);

    WIN32_FIND_DATAA findData;
    HANDLE fileHandle = FindFirstFileA(searchPath.c_str(), &findData);

    if (fileHandle != INVALID_HANDLE_VALUE)
    {
        do
        {
            // Append the assembly to the list
            tpaList.append(directory);
            tpaList.append(FS_SEPERATOR);
            tpaList.append(findData.cFileName);
            tpaList.append(PATH_DELIMITER);

            // Note that the CLR does not guarantee which assembly will be loaded if an assembly
            // is in the TPA list multiple times (perhaps from different paths or perhaps with different NI/NI.dll
            // extensions. Therefore, a real host should probably add items to the list in priority order and only
            // add a file if it's not already present on the list.
            //
            // For this simple sample, though, and because we're only loading TPA assemblies from a single path,
            // and have no native images, we can ignore that complication.
        }
        while (FindNextFileA(fileHandle, &findData));
        FindClose(fileHandle);
    }
}

因为该示例简单,所以只需要 TRUSTED_PLATFORM_ASSEMBLIES 属性:

// Define CoreCLR properties
// Other properties related to assembly loading are common here,
// but for this simple sample, TRUSTED_PLATFORM_ASSEMBLIES is all
// that is needed. Check hosting documentation for other common properties.
const char* propertyKeys[] = {
    "TRUSTED_PLATFORM_ASSEMBLIES"      // Trusted assemblies
};

const char* propertyValues[] = {
    tpaList.c_str()
};

步骤 4 - 启动运行时

与 mscoree.h hosting API(上面所述)不同,CoreCLRHost.h API 使用一个调用启动运行时并创建默认的 AppDomain。 coreclr_initialize 函数采用基本路径、名称和前面描述的属性,并通过 hostHandle 参数将图柄返回到主机。

 

void* hostHandle;
unsigned int domainId;

// This function both starts the .NET Core runtime and creates
// the default (and only) AppDomain
int hr = initializeCoreClr(
                runtimePath,        // App base path
                "SampleHost",       // AppDomain friendly name
                sizeof(propertyKeys) / sizeof(char*),   // Property count
                propertyKeys,       // Property names
                propertyValues,     // Property values
                &hostHandle,        // Host handle
                &domainId);         // AppDomain ID

步骤 5 - 运行托管代码!

启动运行时之后,主机可以调用托管代码。 这可以通过两种不同的方法实现。 与本教程相关的示例代码使用 coreclr_create_delegate 函数创建静态托管方法的委托。 此 API 采用程序集名称、符合命名空间条件的类型名称和方法名称作为输入,并返回可用于调用该方法的委托。

doWork_ptr managedDelegate;

// The assembly name passed in the third parameter is a managed assembly name
// as described at https://docs.microsoft.com/dotnet/framework/app-domains/assembly-names
hr = createManagedDelegate(
        hostHandle,
        domainId,
        "ManagedLibrary, Version=1.0.0.0",
        "ManagedLibrary.ManagedWorker",
        "DoWork",
        (void**)&managedDelegate);

在此示例中,主机现在可以调用 managedDelegate 来运行 ManagedWorker.DoWork 方法。

或者,可以使用 coreclr_execute_assembly 函数启动托管可执行文件。 此 API 采用程序集路径和实参数组作为输入形参。 它在该路径加载程序集并调用其主方法。

int hr = executeAssembly(
        hostHandle,
        domainId,
        argumentCount,
        arguments,
        "HelloWorld.exe",
        (unsigned int*)&exitCode);

步骤 6 - 关闭和清理

最后,主机完成运行托管代码后,使用 coreclr_shutdown 或 coreclr_shutdown_2 关闭 .NET Core 运行时。

hr = shutdownCoreClr(hostHandle, domainId);

请记住使用 FreeLibrary(对于 Windows)或 dlclose(对于 Linux/Mac)卸载 CoreCLR 库。

使用 Mscoree.h 创建主机

如前所述,CoreClrHost.h 现在是承载 .NET Core 运行时的首选方法。 但是,如果 CoreClrHost.h 接口不够用(例如,需要非标准的启动标志,或者在默认域上需要 AppDomainManager),仍然可以使用 ICLRRuntimeHost2 接口。 这些说明将指导你使用 mscoree.h 执行承载 .NET Core 的操作。

有关 mscoree.h 的说明

ICLRRuntimeHost2 .NET Core 承载接口在 MSCOREE.IDL 中定义。 主机需要引用的此文件 (mscoree.h) 的头文件版本,是在构建 .NET Core 运行时时通过 MIDL 生成。 如果不想构建 .NET Core 运行时,mscoree.h 还可从 dotnet/coreclr 库中作为预生成的头文件获取。 有关构建 .NET Core 运行时的说明可在其 GitHub 库中找到。

步骤 1 - 标识托管的入口点

引用必要的头文件后(例如,mscoree.h 和 stdio.h),.NET Core 主机必须完成的首要任务之一就是找到要使用的托管入口点。 在示例主机中,通过将主机的第一个命令行参数作为托管的二进制文件(将执行该文件的 main 方法)的路径,即可完成此操作。

// The managed application to run should be the first command-line parameter.
// Subsequent command line parameters will be passed to the managed app later in this host.
wchar_t targetApp[MAX_PATH];
GetFullPathNameW(argv[1], MAX_PATH, targetApp, NULL);

步骤 2 - 查找和加载 CoreCLR

.NET Core 运行时 API 位于 CoreCLR.dll(在 Windows 上)中。 若要获取托管接口 (ICLRRuntimeHost2),就必须查找并加载 CoreCLR.dll。 由主机定义查找 CoreCLR.dll 方式的约定。 一些主机会预期文件位于一个常用的计算机范围内的位置(如 %programfiles%\dotnet\shared\Microsoft.NETCore.App\2.1.6)。 其他主机会预期 CoreCLR.dll 从主机本身或要托管的应用旁边的某个位置进行加载。 还有一些主机可能会参考环境变量来查找库。

在 Linux 或 Mac 上,核心运行时库分别是 libcoreclr.so 或者 libcoreclr.dylib

示例主机会为 CoreCLR.dll 探测几个常用位置。 找到后,必须通过 LoadLibrary(在 Linux/Mac 上通过 dlopen)进行加载。

HMODULE ret = LoadLibraryExW(coreDllPath, NULL, 0);

步骤 3 - 获取 ICLRRuntimeHost2 实例

通过在 GetCLRRuntimeHost 上调用 GetProcAddress(或在 Linux/Mac 上调用 dlsym),然后再调用该函数来检索 ICLRRuntimeHost2 托管接口。

ICLRRuntimeHost4* runtimeHost;

FnGetCLRRuntimeHost pfnGetCLRRuntimeHost =
    (FnGetCLRRuntimeHost)::GetProcAddress(coreCLRModule, "GetCLRRuntimeHost");

if (!pfnGetCLRRuntimeHost)
{
    printf("ERROR - GetCLRRuntimeHost not found");
    return -1;
}

// Get the hosting interface
HRESULT hr = pfnGetCLRRuntimeHost(IID_ICLRRuntimeHost4, (IUnknown**)&runtimeHost);

步骤 4 - 设置启动标志和启动运行时

有了 ICLRRuntimeHost2,现在便可指定运行时范围级的启动标志并启动该运行时。 启动标志决定要使用的垃圾回收器 (GC)(并发或服务器)、是使用单个 AppDomain 还是多个 Appdomain,以及要使用的加载程序优化策略(对于非特定于域的程序集加载)。

hr = runtimeHost->SetStartupFlags(
    // These startup flags control runtime-wide behaviors.
    // A complete list of STARTUP_FLAGS can be found in mscoree.h,
    // but some of the more common ones are listed below.
    static_cast<STARTUP_FLAGS>(
        // STARTUP_FLAGS::STARTUP_SERVER_GC |								// Use server GC
        // STARTUP_FLAGS::STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN |		// Maximize domain-neutral loading
        // STARTUP_FLAGS::STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST |	// Domain-neutral loading for strongly-named assemblies
        STARTUP_FLAGS::STARTUP_CONCURRENT_GC |						// Use concurrent GC
        STARTUP_FLAGS::STARTUP_SINGLE_APPDOMAIN |					// All code executes in the default AppDomain
                                                                    // (required to use the runtimeHost->ExecuteAssembly helper function)
        STARTUP_FLAGS::STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN	// Prevents domain-neutral loading
    )
);

通过调用 Start 函数启动运行时。

hr = runtimeHost->Start();

步骤 5 - 准备 AppDomain 设置

启动运行时后,将需要设置 AppDomain。 但创建 .NET AppDomain 时必须指定大量选项,因此必须先准备这些选项。

AppDomain 标志指定与安全性和互操作性相关的 AppDomain 行为。 早期 Silverlight 主机对沙盒用户代码使用这些设置,但大多数现代 .NET Core 主机以完全信任的方式运行用户代码并启用互操作。

int appDomainFlags =
    // APPDOMAIN_FORCE_TRIVIAL_WAIT_OPERATIONS |		// Do not pump messages during wait
    // APPDOMAIN_SECURITY_SANDBOXED |					// Causes assemblies not from the TPA list to be loaded as partially trusted
    APPDOMAIN_ENABLE_PLATFORM_SPECIFIC_APPS |			// Enable platform-specific assemblies to run
    APPDOMAIN_ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP |	// Allow PInvoking from non-TPA assemblies
    APPDOMAIN_DISABLE_TRANSPARENCY_ENFORCEMENT;			// Entirely disables transparency checks

确定要使用的 AppDomain 标志后,必须定义 AppDomain 属性。 该属性是字符串的键/值对。 这些属性中的许多与 AppDomain 程序集的加载方式相关。

常见 AppDomain 属性包括:

  • TRUSTED_PLATFORM_ASSEMBLIES 这是一个程序集路径的列表(在 Windows 上以 ; 分隔,在 Linux/Mac 上以 : 分隔),AppDomain 应优先加载它们并对其授予完全信任(甚至在部分受信任域中也一样)。 此列表应包含“框架”程序集和其他受信任的模块,与 .NET Framework 方案中的 GAC 类似。 一些主机会将任何库置于此列表上的 coreclr.dll 旁,其他主机具有硬编码的清单,其中列出了用于所需用途的受信任程序集。
  • APP_PATHS 这是一个用来探测程序集的路径的列表(如果在受信任的平台程序集 (TPA) 列表中找不到程序集)。 因为主机使用 TPA 列表可以更好地控制加载哪些程序集,所以对于主机来说,确定要加载的程序集并显式列出它们是最佳做法。 但是,如果需要探测运行时,则此属性可以支持该方案。
  • APP_NI_PATHS 此列表与 APP_PATHS 非常相似,不同之处在于其中的路径用于探测本机映像。
  • NATIVE_DLL_SEARCH_DIRECTORIES 此属性是一个路径列表,加载程序在查找通过 p/invoke 调用的本机 DLL 时应使用这些路径进行探测。
  • PLATFORM_RESOURCE_ROOTS 此列表包含的路径用于探测资源附属程序集(在区域性特定的子目录中)。

简单示例主机中,这些属性将进行如下设置:

// TRUSTED_PLATFORM_ASSEMBLIES
// "Trusted Platform Assemblies" are prioritized by the loader and always loaded with full trust.
// A common pattern is to include any assemblies next to CoreCLR.dll as platform assemblies.
// More sophisticated hosts may also include their own Framework extensions (such as AppDomain managers)
// in this list.
size_t tpaSize = 100 * MAX_PATH; // Starting size for our TPA (Trusted Platform Assemblies) list
wchar_t* trustedPlatformAssemblies = new wchar_t[tpaSize];
trustedPlatformAssemblies[0] = L'\0';

// Extensions to probe for when finding TPA list files
const wchar_t *tpaExtensions[] = {
    L"*.dll",
    L"*.exe",
    L"*.winmd"
};

// Probe next to CoreCLR.dll for any files matching the extensions from tpaExtensions and
// add them to the TPA list. In a real host, this would likely be extracted into a separate function
// and perhaps also run on other directories of interest.
for (int i = 0; i < _countof(tpaExtensions); i++)
{
    // Construct the file name search pattern
    wchar_t searchPath[MAX_PATH];
    wcscpy_s(searchPath, MAX_PATH, coreRoot);
    wcscat_s(searchPath, MAX_PATH, L"\\");
    wcscat_s(searchPath, MAX_PATH, tpaExtensions[i]);

    // Find files matching the search pattern
    WIN32_FIND_DATAW findData;
    HANDLE fileHandle = FindFirstFileW(searchPath, &findData);

    if (fileHandle != INVALID_HANDLE_VALUE)
    {
        do
        {
            // Construct the full path of the trusted assembly
            wchar_t pathToAdd[MAX_PATH];
            wcscpy_s(pathToAdd, MAX_PATH, coreRoot);
            wcscat_s(pathToAdd, MAX_PATH, L"\\");
            wcscat_s(pathToAdd, MAX_PATH, findData.cFileName);

            // Check to see if TPA list needs expanded
            if (wcsnlen(pathToAdd, MAX_PATH) + (3) + wcsnlen(trustedPlatformAssemblies, tpaSize) >= tpaSize)
            {
                // Expand, if needed
                tpaSize *= 2;
                wchar_t* newTPAList = new wchar_t[tpaSize];
                wcscpy_s(newTPAList, tpaSize, trustedPlatformAssemblies);
                trustedPlatformAssemblies = newTPAList;
            }

            // Add the assembly to the list and delimited with a semi-colon
            wcscat_s(trustedPlatformAssemblies, tpaSize, pathToAdd);
            wcscat_s(trustedPlatformAssemblies, tpaSize, L";");

            // Note that the CLR does not guarantee which assembly will be loaded if an assembly
            // is in the TPA list multiple times (perhaps from different paths or perhaps with different NI/NI.dll
            // extensions. Therefore, a real host should probably add items to the list in priority order and only
            // add a file if it's not already present on the list.
            //
            // For this simple sample, though, and because we're only loading TPA assemblies from a single path,
            // we can ignore that complication.
        }
        while (FindNextFileW(fileHandle, &findData));
        FindClose(fileHandle);
    }
}


// APP_PATHS
// App paths are directories to probe in for assemblies which are not one of the well-known Framework assemblies
// included in the TPA list.
//
// For this simple sample, we just include the directory the target application is in.
// More complex hosts may want to also check the current working directory or other
// locations known to contain application assets.
wchar_t appPaths[MAX_PATH * 50];

// Just use the targetApp provided by the user and remove the file name
wcscpy_s(appPaths, targetAppPath);


// APP_NI_PATHS
// App (NI) paths are the paths that will be probed for native images not found on the TPA list.
// It will typically be similar to the app paths.
// For this sample, we probe next to the app and in a hypothetical directory of the same name with 'NI' suffixed to the end.
wchar_t appNiPaths[MAX_PATH * 50];
wcscpy_s(appNiPaths, targetAppPath);
wcscat_s(appNiPaths, MAX_PATH * 50, L";");
wcscat_s(appNiPaths, MAX_PATH * 50, targetAppPath);
wcscat_s(appNiPaths, MAX_PATH * 50, L"NI");


// NATIVE_DLL_SEARCH_DIRECTORIES
// Native dll search directories are paths that the runtime will probe for native DLLs called via PInvoke
wchar_t nativeDllSearchDirectories[MAX_PATH * 50];
wcscpy_s(nativeDllSearchDirectories, appPaths);
wcscat_s(nativeDllSearchDirectories, MAX_PATH * 50, L";");
wcscat_s(nativeDllSearchDirectories, MAX_PATH * 50, coreRoot);


// PLATFORM_RESOURCE_ROOTS
// Platform resource roots are paths to probe in for resource assemblies (in culture-specific sub-directories)
wchar_t platformResourceRoots[MAX_PATH * 50];
wcscpy_s(platformResourceRoots, appPaths);

步骤 6 - 创建 AppDomain

所有 AppDomain 标志和属性都准备都就绪后,可使用 ICLRRuntimeHost2::CreateAppDomainWithManager 设置 AppDomain。 此函数选择性地采用完全限定的程序集名称和类型名称作为域的 AppDomain 管理器。 AppDomain 管理器可允许主机控制 AppDomain 行为的某些方面,并且如果主机不打算直接调用用户代码,它可能会提供用于启动托管代码的入口点。

DWORD domainId;

// Setup key/value pairs for AppDomain  properties
const wchar_t* propertyKeys[] = {
    L"TRUSTED_PLATFORM_ASSEMBLIES",
    L"APP_PATHS",
    L"APP_NI_PATHS",
    L"NATIVE_DLL_SEARCH_DIRECTORIES",
    L"PLATFORM_RESOURCE_ROOTS"
};

// Property values which were constructed in step 5
const wchar_t* propertyValues[] = {
    trustedPlatformAssemblies,
    appPaths,
    appNiPaths,
    nativeDllSearchDirectories,
    platformResourceRoots
};

// Create the AppDomain
hr = runtimeHost->CreateAppDomainWithManager(
    L"Sample Host AppDomain",		// Friendly AD name
    appDomainFlags,
    NULL,							// Optional AppDomain manager assembly name
    NULL,							// Optional AppDomain manager type (including namespace)
    sizeof(propertyKeys) / sizeof(wchar_t*),
    propertyKeys,
    propertyValues,
    &domainId);

步骤 7 - 运行托管代码!

现在 AppDomain 启动并运行后,主机可以开始执行托管的代码。 执行此操作的最简单方法是使用 ICLRRuntimeHost2::ExecuteAssembly 调用托管程序集的入口点方法。 请注意,此函数仅适用于单一域方案。

DWORD exitCode = -1;
hr = runtimeHost->ExecuteAssembly(domainId, targetApp, argc - 1, (LPCWSTR*)(argc > 1 ? &argv[1] : NULL), &exitCode);

如果 ExecuteAssembly 不满足主机的需要,那么另一种方法是使用 CreateDelegate 创建指向静态托管方法的函数指针。 这要求主机知道要调用的方法的签名(以创建函数指针类型),还允许主机除了调用程序集的入口点之外,还灵活地调用其他代码。 第二个参数中提供的程序集名称是要加载的库的完全托管程序集名称

void *pfnDelegate = NULL;
hr = runtimeHost->CreateDelegate(
  domainId,
  L"HW, Version=1.0.0.0, Culture=neutral",  // Target managed assembly
  L"ConsoleApplication.Program",            // Target managed type
  L"Main",                                  // Target entry point (static method)
  (INT_PTR*)&pfnDelegate);

((MainMethodFp*)pfnDelegate)(NULL);

步骤 8 - 清理

最后,主机应随后通过卸载 Appdomain、停止运行时并释放 ICLRRuntimeHost2 引用来进行清理。

runtimeHost->UnloadAppDomain(domainId, true /* Wait until unload complete */);
runtimeHost->Stop();
runtimeHost->Release();

结束语

构建主机后,可以通过从命令行运行主机并传递其所需的任何参数(例如,要运行用于 mscoree 示例主机的托管应用)来对主机进行测试。 指定主机要运行的 .NET Core 应用时,请务必使用 dotnet build 生成的 .dll。 dotnet publish 为独立应用程序生成的可执行文件(.exe 文件)实际上是默认的 .NET Core 主机(以便可直接从主流场景中的命令行启动应用);用户代码被编译为具有相同名称的 dll。

如果开始时操作不起作用,请再次检查 coreclr.dll 是否在主机预期的位置可用、是否 TPA 列表中包含了所有必需的框架库以及 CoreCLR 的位数(32 位或 64 位)是否匹配主机的构建方式。

托管 .NET Core 运行时是高级方案,许多开发人员并不需要实施这一方案,但对于那些需要从本机进程启动托管代码的人员,或需要更好地控制 .NET Core 运行时的行为的人员而言,它会非常有用。 因为 .NET Core 能够与其自身并行运行,甚至可以创建主机,这些主机能够初始化和启动多个 .NET Core 运行时版本并在同一进程中执行所有这些版本上的应用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在ASP.NET Core Razor编程中,列表模板页面是非常常见的。这些页面通常用于显示数据库或其他数据源中的一组记录。 在本文中,我将向您展示如何使用ASP.NET Core Razor列表模板页面。 首先,我们需要创建一个模型类来代表我们的数据。例如,假设我们正在构建一个博客应用程序,我们需要一个名为“Post”的模型类来表示博客文章。以下是一个示例模型类: ```csharp public class Post { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } public DateTime CreatedDate { get; set; } } ``` 接下来,我们需要创建一个控制器类来处理与“Post”模型类相关的操作。以下是一个示例控制器类: ```csharp public class PostController : Controller { private readonly ApplicationDbContext _context; public PostController(ApplicationDbContext context) { _context = context; } public IActionResult Index() { var posts = _context.Posts.ToList(); return View(posts); } } ``` 在此示例控制器中,我们从数据库中检索所有博客文章,并将它们传递给视图。 现在,我们需要创建一个视图来显示我们的博客文章列表。我们可以使用ASP.NET Core Razor模板引擎来创建一个动态模板,该模板可以将我们的博客文章显示为HTML表格。以下是一个示例视图: ```html @model IEnumerable<Post> <table> <thead> <tr> <th>Title</th> <th>Content</th> <th>Created Date</th> </tr> </thead> <tbody> @foreach (var post in Model) { <tr> <td>@post.Title</td> <td>@post.Content</td> <td>@post.CreatedDate.ToShortDateString()</td> </tr> } </tbody> </table> ``` 在此示例视图中,我们使用了一个foreach循环遍历我们的博客文章,并将它们显示为HTML表格行。 最后,我们需要在控制器的Index方法中返回视图。在我们的示例控制器中,我们已经传递了一个包含所有博客文章的IEnumerable<Post>对象。我们可以将此对象传递给视图,如下所示: ```csharp public IActionResult Index() { var posts = _context.Posts.ToList(); return View(posts); } ``` 现在,当我们访问PostController的Index操作时,我们将看到一个包含所有博客文章的HTML表格。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值