Description
Hi,
I am trying to get a project running, where I don't want to rely on a installed framework on the users system. So I tried the following setup:
-
Build a c++ wrapper app using this tutorial: Write a custom .NET Core host to control the .NET runtime from your native code
-
Build a dll in .Net Core 3.1.3 and publish it as self-contained
My hosting-code:
#include <iostream>
#include "pch.h"
#include <windows.h>
#include <assert.h>
#include <string>
#include <ShlObj.h>
#include "nethost.h"
#include "hostfxr.h"
#include "coreclr_delegates.h"
using std::wstring;
#define STR(s) L ## s
#define CH(c) L ## c
#define DIR_SEPARATOR L'\\'
namespace
{
void load_lib(std::string lib_name)
{
auto load_lib_result = LoadLibraryA(lib_name.c_str());
if (load_lib_result != NULL)
{
std::string debug_message = "success loading -> " + lib_name;
printf(debug_message.c_str());
FreeLibrary(load_lib_result);
}
else
{
int errorCode = GetLastError();
std::string debug_message = "loading " + lib_name + " " + std::to_string(errorCode) + " failed";
printf(debug_message.c_str());
}
}
hostfxr_initialize_for_runtime_config_fn init_fptr;
hostfxr_get_runtime_delegate_fn get_delegate_fptr;
hostfxr_close_fn close_fptr;
void* load_library(const char_t*);
void* get_export(void*, const char*);
void* load_library(const char_t* path)
{
HMODULE h = ::LoadLibraryW(path);
assert(h != nullptr);
return (void*)h;
}
void* get_export(void* h, const char* name)
{
void* f = ::GetProcAddress((HMODULE)h, name);
assert(f != nullptr);
return f;
}
}
load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const char_t* config_path)
{
void* load_assembly_and_get_function_pointer = nullptr;
hostfxr_handle cxt = nullptr;
hostfxr_initialize_parameters parameters{
sizeof(hostfxr_initialize_parameters),
L".\\",
L".\\"
};
int rc = init_fptr(config_path, ¶meters, &cxt);
if (rc > 1 || cxt == nullptr)
{
std::string error_message = "Init failed with rc: " + std::to_string(rc);
printf(error_message.c_str());
close_fptr(cxt);
return nullptr;
}
rc = get_delegate_fptr(
cxt,
hdt_load_assembly_and_get_function_pointer,
&load_assembly_and_get_function_pointer);
if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) {
std::string error_message = "Get delegate failed with rc: " + std::to_string(rc);
printf(error_message.c_str());
}
close_fptr(cxt);
return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer;
}
bool load_hostfxr()
{
char_t buffer[MAX_PATH] = L".\\hostfxr.dll";
// Load hostfxr and get desired exports
void* lib = load_library(buffer);
init_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config");
get_delegate_fptr = (hostfxr_get_runtime_delegate_fn)get_export(lib, "hostfxr_get_runtime_delegate");
close_fptr = (hostfxr_close_fn)get_export(lib, "hostfxr_close");
return (init_fptr && get_delegate_fptr && close_fptr);
}
namespace
{
bool isInitialized = false;
component_entry_point_fn moduleStart = nullptr;
component_entry_point_fn moduleStop = nullptr;
int init()
{
printf(">> DnsCLoudClientEntry::init\n");
if (!isInitialized)
{
if (!load_hostfxr())
{
printf("<< DnsCLoudClientEntry::init exit with failure in load_hostfxr()\n");
assert(false && "Failure: load_hostfxr()");
return EXIT_FAILURE;
}
wstring workingDirectory = L".\\";
const wstring config_path = workingDirectory + STR("ElTesto.runtimeconfig.json");
load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = nullptr;
load_assembly_and_get_function_pointer = get_dotnet_load_assembly(config_path.c_str());
assert(load_assembly_and_get_function_pointer != nullptr && "Failure: get_dotnet_load_assembly()");
const wstring dotnetlib_path = workingDirectory + STR("ElTesto.dll");
const char_t* dotnet_type = STR("ElTesto.ElTesto, ElTesto");
const char_t* dotnet_type_method = STR("FooBar");
int rc = load_assembly_and_get_function_pointer(
dotnetlib_path.c_str(),
dotnet_type,
dotnet_type_method,
nullptr,
nullptr,
(void**)&moduleStart);
assert(rc == 0 && moduleStart != nullptr && "Failure: load_assembly_and_get_function_pointer() ModuleStart");
isInitialized = true;
}
printf("<< DnsCLoudClientEntry::init\n");
return 0;
}
}
int main()
{
init();
struct lib_args
{
const char_t* message;
int number;
};
lib_args args{ STR(""), 1 };
moduleStart(nullptr, 0);
}
The C# library has no dependencies besides the .Net Core runtime. I build it with this command line-command:
dotnet publish -r win-x64 -c release -f netcoreapp3.1 --self-contained
Now two questions:
- The publish command generates a runtimeconfig like this
{
"runtimeOptions": {
"tfm": "netcoreapp3.1",
"includedFramework": [
{
"name": "Microsoft.NETCore.App",
"version": "3.1.3"
}
]
}
}
Running the wrapper with that configuration I get an Error:
Initialization for self-contained components is not supported
Changing the runtimeconfig to this makes it run:
{
"runtimeOptions": {
"tfm": "netcoreapp3.1",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "3.1.3"
}
}
}
But I still have the problem, the it isn't fully self-contained, because while calling hostfxr_initialize_for_runtime_config
it will always try getting some files system folders. Do i just missunderstand self-contained?