Closed benjaml closed 4 years ago
Unless you have some very specific reason for using the COM hosting API, I would recommend using the new simple hosting API that works across all platforms. The API is declared in the following header: https://github.com/dotnet/coreclr/blob/5e138a6d69215546e9bf4b38ec9bdd1c436fca5b/src/coreclr/hosts/inc/coreclrhost.h. Also, you need to properly configure various paths that the runtime needs so that e.g. the managed assemblies can be located. You can see an example of how to use the new hosting api e.g. in the corerun tool sources for Unix: https://github.com/dotnet/coreclr/blob/9e3ef8eb56d5027ed96348e89911afacbccee9f0/src/coreclr/hosts/unixcoreruncommon/coreruncommon.cpp And the implementation of the new API that is actually done using the COM hosting API is here: https://github.com/dotnet/coreclr/blob/5e138a6d69215546e9bf4b38ec9bdd1c436fca5b/src/dlls/mscoree/unixinterface.cpp
So from what I've understand, coreclr_initialize create clr and appDomain, this means that I can only have one appDomain per clr, or am I wrong ?
Yes, coreclr doesn't support multiple domains.
No matter which hosting API you use, to be precise.
initializeCoreCLR need the path to an exe to be executed, but I don't know which
initializeCoreCLR need the path to an exe to be executed, but I don't know which
That's just the path to the exe that you actually want to run, i.e the .NET executable you want the host to execute. I guess you could just write a simple 'Hello World' app to try things out.
BTW there's some docs that might help with this, they mostly cover the COM API, but they do have a section on the new x-plat API, see 'About Hosting .NET Core on Unix'
@janvorli the docs on Hosting .NET Core mostly discuss the COM Api's and only have a small section 'About Hosting .NET Core on Unix'.
Should they talk about the new x-plat APIs more prominently, as per your guidance above:
Unless you have some very specific reason for using the COM hosting API, I would recommend using the new simple hosting API that works across all platforms
@mattwarren, @benjaml the initializeCoreCLR's path to exe is something different. It is a path to the current host executable.
@mattwarren I agree that the doc should be updated. Moreover, it is missing several relatively recent additions to the API.
I don't have a .NET executable, I just want to run a .NET dll, can I put the dll path instead of the executable ?
I got this while trying to use initializeCoreCLR
Exception thrown at 0x00007FFA3DE23FB8 (KernelBase.dll) in ConsoleApplication1.exe: 0x04242420 (parameters: 0x0000000031415927, 0x00007FF9F6AA0000, 0x00000049BAEF2C30).
but it returns a like it is a succes, so I don't really know if it worked or not, I try to create delegates after this and they all failed.
I'm using windows, is there any example of how to use the new hosting api for windows ?
The usage should be pretty much the same as in the https://github.com/dotnet/coreclr/blob/9e3ef8eb56d5027ed96348e89911afacbccee9f0/src/coreclr/hosts/unixcoreruncommon/coreruncommon.cpp I've referenced above. You need to correctly pass it all the parameters and at least these properties: https://github.com/dotnet/coreclr/blob/9e3ef8eb56d5027ed96348e89911afacbccee9f0/src/coreclr/hosts/unixcoreruncommon/coreruncommon.cpp#L388-L391
I'm pretty sure they are correct, but it's the exePath of coreclr_initialize that i'm not sure about
as I got no .net executable but only a dll, I need to pass this dll in APP_PATHS (am I right ?)
As I've mentioned above, the currentExeAbsolutePath is the path to your native executable that calls the API (that's what is call the "host").
ok so this should be ok, then I don't know why I got Exception thrown at 0x00007FFA3DE23FB8 (KernelBase.dll) in ConsoleApplication1.exe: 0x04242420 (parameters: 0x0000000031415927, 0x00007FF9F6AA0000, 0x00000049BAEF2C30). when I call initializeCoreCLR, even if it throws an exception it also give the return of a success, so I'm not sure of how I should interpret it
@benjaml could you share your current piece of code that is trying to initialize CoreCLR? I may be able to spot something in it.
const char *currentExeAbsolutePath= "D:\\Workspace\\KEA\\InteropSample\\CppExecutable\\x64\\Debug\\ConsoleApplication1.exe";
const char* clrFilesAbsolutePath= "D:\\Workspace\\clr\\coreclr\\bin\\Product\\Windows_NT.x64.Debug";
const char* managedAssemblyAbsolutePath= "D:\\Workspace\\KEA\\InteropSample\\CSharpDll\\HostedApplication\\bin\\Debug\\netcoreapp2.0\\HostedApplication.dll"; // this is my c# dll that I want to use in c++
int tpaSize = 100 * MAX_PATH; // Starting size for our TPA (Trusted Platform Assemblies) list
char* trustedPlatformAssemblies = new char[tpaSize];
trustedPlatformAssemblies[0] = L'\0';
// Extensions to probe for when finding TPA list files
char *tpaExtensions[] = {
"*.dll",
"*.exe",
"*.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
char searchPath[MAX_PATH];
strcpy_s(searchPath, MAX_PATH, clrFilesAbsolutePath);
strcat_s(searchPath, MAX_PATH, "\\");
strcat_s(searchPath, MAX_PATH, tpaExtensions[i]);
// Find files matching the search pattern
WIN32_FIND_DATAA findData;
HANDLE fileHandle = FindFirstFileA(searchPath, &findData);
if (fileHandle != INVALID_HANDLE_VALUE)
{
do
{
// Construct the full path of the trusted assembly
char pathToAdd[MAX_PATH];
strcpy_s(pathToAdd, MAX_PATH, clrFilesAbsolutePath);
strcat_s(pathToAdd, MAX_PATH, "\\");
strcat_s(pathToAdd, MAX_PATH, findData.cFileName);
// Check to see if TPA list needs expanded
if (strlen(pathToAdd) + (3) + strlen(trustedPlatformAssemblies) >= tpaSize)
{
// Expand, if needed
tpaSize *= 2;
char* newTPAList = new char[tpaSize];
strcpy_s(newTPAList, tpaSize, trustedPlatformAssemblies);
trustedPlatformAssemblies = newTPAList;
}
// Add the assembly to the list and delimited with a semi-colon
strcat_s(trustedPlatformAssemblies, tpaSize, pathToAdd);
strcat_s(trustedPlatformAssemblies, tpaSize, ";");
// 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 (FindNextFileA(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.
char appPaths[MAX_PATH * 50];
// Just use the targetApp provided by the user and remove the file name
strcpy_s(appPaths, managedAssemblyAbsolutePath);
//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.
char appNiPaths[MAX_PATH * 50];
strcpy_s(appNiPaths, clrFilesAbsolutePath);
// NATIVE_DLL_SEARCH_DIRECTORIES
// Native dll search directories are paths that the runtime will probe for native DLLs called via PInvoke
char nativeDllSearchDirectories[MAX_PATH * 50];
strcpy_s(nativeDllSearchDirectories, appPaths);
strcat_s(nativeDllSearchDirectories, MAX_PATH * 50, ";");
strcat_s(nativeDllSearchDirectories, MAX_PATH * 50, clrFilesAbsolutePath);
HMODULE coreclrLib = LoadLibraryA(coreClrDllPath.c_str());
if (coreclrLib == nullptr)
{
fprintf(stderr, "dlopen failed to open the libcoreclr.so with error\n");
return -1;
}
coreclr_initialize_ptr initializeCoreCLR = (coreclr_initialize_ptr)GetProcAddress(coreclrLib, "coreclr_initialize");
coreclr_create_delegate_ptr createDelegateCoreCLR = (coreclr_create_delegate_ptr)GetProcAddress(coreclrLib, "coreclr_create_delegate");
coreclr_shutdown_2_ptr shutdownCoreCLR = (coreclr_shutdown_2_ptr)GetProcAddress(coreclrLib, "coreclr_shutdown_2");
if (initializeCoreCLR == nullptr)
{
fprintf(stderr, "Function coreclr_initialize not found in the libcoreclr.so\n");
return -1;
}
else if (createDelegateCoreCLR == nullptr)
{
fprintf(stderr, "Function coreclr_create_delegate not found in the libcoreclr.so\n");
return -1;
}
else if (shutdownCoreCLR == nullptr)
{
fprintf(stderr, "Function coreclr_shutdown_2 not found in the libcoreclr.so\n");
return -1;
}
// Check whether we are enabling server GC (off by default)
const char* useServerGc = GetEnvValueBoolean(serverGcVar);
// Check Globalization Invariant mode (false by default)
const char* globalizationInvariant = GetEnvValueBoolean(globalizationInvariantVar);
// Allowed property names:
// APPBASE
// - The base path of the application from which the exe and other assemblies will be loaded
//
// TRUSTED_PLATFORM_ASSEMBLIES
// - The list of complete paths to each of the fully trusted assemblies
//
// APP_PATHS
// - The list of paths which will be probed by the assembly loader
//
// APP_NI_PATHS
// - The list of additional paths that the assembly loader will probe for ngen images
//
// NATIVE_DLL_SEARCH_DIRECTORIES
// - The list of paths that will be probed for native DLLs called by PInvoke
//
const char *propertyKeys[] = {
"TRUSTED_PLATFORM_ASSEMBLIES",
"APP_PATHS",
"APP_NI_PATHS",
"NATIVE_DLL_SEARCH_DIRECTORIES",
"System.GC.Server",
"System.Globalization.Invariant",
};
const char *propertyValues[] = {
// TRUSTED_PLATFORM_ASSEMBLIES
trustedPlatformAssemblies,
// APP_PATHS
appPaths,
// APP_NI_PATHS
appNiPaths,
// NATIVE_DLL_SEARCH_DIRECTORIES
nativeDllSearchDirectories,
// System.GC.Server
useServerGc,
// System.Globalization.Invariant
globalizationInvariant,
};
void* hostHandle;
unsigned int domainId;
std::string entryPointExecutablePath;
if (!GetEntrypointExecutableAbsolutePath(entryPointExecutablePath))
{
printf("Could not get full path to current executable");
return E_FAIL;
}
int st = -1;
st = initializeCoreCLR(
entryPointExecutablePath.c_str(),
"unixcorerun",
sizeof(propertyKeys) / sizeof(propertyKeys[0]),
propertyKeys,
propertyValues,
&hostHandle,
&domainId);
@benjaml I have few questions. As for the code sample above. I assume the GetEntrypointExecutableAbsolutePath returns the currentExeAbsolutePath, right?
when I call initializeCoreCLR, even if it throws an exception it also give the return of a success, so I'm not sure of how I should interpret it
I am not sure what you mean by that. If it returns success, it cannot throw at the same time. So is it possible that the exception is thrown after the initializeCoreCLR returns? In other words, if you run it under a debugger and step over the initializeCoreCLR, does the exception happen?
ok so maybe I'll need to be more precise. I'm using a native exe (c++) to load a native dll (c++), that will run the clr to link with the c# assembly.
I've also change the path to the coreclr.dll as it was not the right one so now the initializeCoreCLR fail and I got : coreclr_initialize failed - status: 0x80004005
Ok, that's a really general error code which can be caused by many different things. But one thing I wonder about - from the code fragment above, it seems that you are using only files built in the coreclr repo. That's not enough, you need output of both coreclr and corefx repos. But as a first step, I would recommend downloading a zip file with the latest dotnet core from https://dotnetcli.blob.core.windows.net/dotnet/Runtime/master/dotnet-runtime-latest-win-x64.zip and using the binaries from the shared\Microsoft.NETCore.App\2.1.0-preview1-26012-06 folder in the zip. That would help you to make sure that your hosting code is ok. Once that works, you can move to using the coreclr / corefx files you build.
Ok so coreclr_initialize succeed but I got this during coreclr_initialize : Exception thrown at 0x00007FFA3DE23FB8 (KernelBase.dll) in ConsoleApplication1.exe: 0x04242420 (parameters: 0x0000000031415927, 0x00007FF9FA2C0000, 0x000000154E593620).
currentExeAbsolutePath = D:\Workspace\KEA\InteropSample\CppExecutable\x64\Debug\ConsoleApplication1.exe
clrFilesAbsolutePath = C:\Users\Stage\Downloads\dotnet-runtime-latest-win-x64\shared\Microsoft.NETCore.App\2.1.0-preview1-26012-06
managedAssemblyAbsolutePath = D:\Workspace\KEA\InteropSample\CSharpDll\HostedApplication\bin\Debug\netcoreapp2.0\HostedApplication.dll
I've already asked about it, but could you please clarify this? If you step over the call to coreclr_initialize in the debugger, do you get the exception?
yes
Ah, this looks like a benign thing. The error code 0x04242420 means CLRDBG_NOTIFICATION_EXCEPTION_CODE and the first parameter 0x0000000031415927 means CLRDBG_EXCEPTION_DATA_CHECKSUM. So it is not a failure, but a debugger notification. You can ignore it. As you've said, the coreclr_initialize has returned success.
Ok so then I try to create delegates but I got : ERROR - Failed to create delegate. Error code:80070002 I've put my c# assembly in appPaths and nativeDllSearchDirectories, is it enough because it seams like it failed to open files as I got this several times : Exception thrown at 0x00007FFA3DE23FB8 in ConsoleApplication1.exe: Microsoft C++ exception: EEFileLoadException at memory location 0x000000E6CF1F0B28. Exception thrown at 0x00007FFA3DE23FB8 in ConsoleApplication1.exe: Microsoft C++ exception: [rethrow] at memory location 0x0000000000000000.
Here is the code of AddDelegates :
int AddDelegates(int domainId, void* hostHandle, coreclr_create_delegate_ptr createDelegateCoreCLR)
{
DelegateMap = std::map<std::string, void*>();
return AddDelegate("Character.CreateCharacter", domainId, hostHandle, createDelegateCoreCLR);
}
int AddDelegate(std::string route, int domainId, void* hostHandle, coreclr_create_delegate_ptr createDelegateCo)
{
std::string delimiter = ".";
std::string className = route.substr(0, route.find(delimiter));
std::string methodName = route.substr(route.find(delimiter) + 1, route.length() - 1);
void *delegateFunc = NULL;
int st = createDelegateCo(
hostHandle,
domainId,
"HostedApplication", // entryPointAssemblyName
("HostedApplication."+className).c_str(), // entryPointTypeName
methodName.c_str(), // entryPointMethodName
&delegateFunc);
if (FAILED(st))
{
printf("ERROR - Failed to create delegate.\nError code:%x\n", st);
return -1;
}
DelegateMap.insert(std::pair<std::string, void*>(route, delegateFunc));
return 0;
}
Looking at the coreruncommon.cpp sources, we also add the absolute path to the C# assembly to the TRUSTED_PLATFORM_ASSEMBLIES property.
It looks like it created well the delegates but I can't call them. I got : System.Runtime.InteropServices.SEHException Parameters looks correct I don't understand
C++ function :
void Test()
{
if (DotNetRuntime::GetInstance() == nullptr)
{
printf("CLR environment is not running try to call 'Init' before 'Test'\n");
return;
}
void* func = DotNetRuntime::GetInstance()->DelegateMap.at("Character.Test");
if (func == nullptr)
{
printf("Delegate for %s not found\n", "Character.Test");
return;
}
((NoArgsNoReturnMethod*)func)();
}
C# function :
static void Test()
{
Console.WriteLine("TEST");
}
I just store all the delegates in DelegateMap with an id
@benjaml I cannot see anything obviously incorrect. If the function had parameters or it was a member, I would think that the calling convention might be wrong, but since it is a static parameterless and function with void return, that cannot be the case. Are you familiar with amd64 assembly enough so that you'd be able to step through the code of the delegate or at least get me the disassembly of the delegate function code? The way it works is that when you call the delegate, it first calls a generated helper code that does various things that coreclr needs before entering a managed method and then executes the method itself.
Here is the disassambly of the delegate function code
00007FF99738316C mov r10,7FF997383140h
00007FF997383176 mov rax,7FF9F6FF3FF0h
00007FF997383180 jmp rax
@benjaml That looks correct. Could you please wrap the body of your test method in a try / catch, catching just "Exception" and swallowing it ? I wonder if an exception is thrown from the Console.WriteLine - that would result in the System.Runtime.InteropServices.SEHException, I think.
yes this is it, I got a System.Runtime.InteropServices.SEHException
I tried an executeAssembly that just do : Console.WriteLine("HelloWorld");
And it worked well
I've tried to make the delegate function return an int, it gives me the same result --> System.Runtime.InteropServices.SEHException I've also tried to make the delegate function call a dll import function of the native dll to print something. This gives me the same result --> System.Runtime.InteropServices.SEHException
Even if the delegate function is empty and does nothing, I still got System.Runtime.InteropServices.SEHException
@benjaml ok, thank you for testing all of these things. Let me try to debug it locally. Would you be able to get me a complete simple repro source that I can use? I can create my own based on the fragments of code you've pasted into this issue, but getting it from you would help me to do it faster.
Here is my code :
@benjaml thank you, I'll try to find some time today or tomorrow to debug it.
ok thanks a lot for your time
@benjaml I've found the problem. Your code called coreclr_shutdown before calling the delegate. You rely on ExecuteManagedAssembly to initialize the coreclr. but it also calls coreclr_shutdown. So when you call the delegate later, the runtime is shut down. In such state, calling any managed code is prohibited for obvious reasons. I have commented out that call to shutdownCoreCLR and the delegate was successfully called and printed the "TEST" message.
I feel so dumb ... Thanks a lot
No problem :-), I was happy to help.
I've one last problem (I hope).
I want to reload the appdomain when I modify my c# dll. I think this is not possible to only reload the appdomain here since it's not detach from the clr. So I need to reload the clr, So I've tried to shutdown then recreate the clr but it is not working.
here is my code :
static void ReloadAppDomain(char* dotNetDllPath)
{
instance.reset(nullptr); // --> this call shutdown and success
instance = std::make_unique<DotNetRuntime>(dotNetDllPath); // --> this call Initilize and fails
}
I got : coreclr_initialize failed - status: 0x80131022
Is there something else to do if I want to reload it ?
I've tried to change the appDomainFriendlyName, but it does not change the problem
PS : I've updated the code on the github repo, I gave you yesterday, if you need
@benjaml Neither reloading the app domain nor reinitializing the runtime is possible in coreclr. There was some work done to support assembly unloading, but it is very complex and was not completed yet. See the discussion in the issue dotnet/runtime#8815 for more details.
If you have more questions, please ask @jkotas. I am starting my paternity leave on Monday.
Assuming this discussion has reached a stopping point.
Hello,
I tried using the source to build coreclr.dll, and I got some issues. First of all I got this error
ERROR - Failed to start the runtime. Error code:80004005
Here is a sample of my code
As I'm very new to dotnet clr, I don't really know if it is the right way to start the runtimeHost. Feel free to ask for other informations as I don't really know what could be relevant to solve my problem.
Thanks for the help