Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to execute host native code from a hosted CLR #41319

Closed
blakemcbride opened this issue Aug 25, 2020 · 16 comments
Closed

How to execute host native code from a hosted CLR #41319

blakemcbride opened this issue Aug 25, 2020 · 16 comments
Labels
area-Interop-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.
Milestone

Comments

@blakemcbride
Copy link

blakemcbride commented Aug 25, 2020

Greetings,

I have a C program that is hosting a CLR. It is able to call C# methods. However, I want to be able to have the C# code call the host's C functions too. I can't use DllImport because that would be out of the context of the C call to C#. For example:

C psudo code:

global var X = 4

init CLR
call C# myMeth

myFun() {
printf X;
}

C# psudo code:

myMeth() {
call native myFun()
}

So, C calls C# and then the C# code calls back into the host's myFun in the same context as C's call into C#. Therefore X is still visible. I am running on Linux and happy to use MS or Mono.

Thanks!

area-Interop-coreclr @jeffschwMSFT @AaronRobinsonMSFT @lambdageek

@Dotnet-GitSync-Bot
Copy link
Collaborator

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Aug 25, 2020
@jkotas jkotas added question Answer questions and provide assistance, not an issue with source code or documentation. area-Interop-coreclr labels Aug 25, 2020
@jkotas
Copy link
Member

jkotas commented Aug 25, 2020

The host needs to pass in function pointers for the methods that your C# needs to be able to call back.

Before .NET 5, the function pointers should be passed in as IntPtr and converted using Marshal.GetDelegateForFunctionPointer to something that can be called from C#.

In .NET 5, the function pointers can be passed as the new C# 9 function pointers, and the call to Marshal.GetDelegateForFunctionPointer is unnecessary.

@AaronRobinsonMSFT AaronRobinsonMSFT removed the untriaged New issue has not been triaged by the area owner label Aug 26, 2020
@AaronRobinsonMSFT AaronRobinsonMSFT added this to the Future milestone Aug 26, 2020
@AaronRobinsonMSFT
Copy link
Member

@blakemcbride Please let us know if @jkotas's suggestion makes sense. If it does but there are still concerns hearing suggestions how better or ideal solutions is always welcome. This is an area we are very interested in improving. See #40484 for a survey that involves interop.

@blakemcbride
Copy link
Author

I am using C (not C++) in Linux. I am able to call C# from C and then back to C. I still need to work with the marshaling a bit.

I've got the following questions, however.

  1. Can you give me a link to the full documentation for load_assembly_and_get_function_pointer?

  2. (I'm not a C#/.NET expert.) I presume in a DLL more than one class can exist. In the call to load_assembly_and_get_function_pointer you specify the name of the method you are calling. However, several classes could have the same methods. How does load_assembly_and_get_function_pointer know which one?

  3. Your examples show calling static/class methods. How can I call instance methods?

  4. Where is the best place to ask questions like this?

Ultimately, I will be writing a code generator that generates a C interface for bi-directional communications.

Thanks!

@AaronRobinsonMSFT
Copy link
Member

@blakemcbride These are all good questions. I think a good example of the instance method dispatch can be found here.

  1. Can you give me a link to the full documentation for load_assembly_and_get_function_pointer?

The best documentation for this API is found here.

  1. (I'm not a C#/.NET expert.) I presume in a DLL more than one class can exist. In the call to load_assembly_and_get_function_pointer you specify the name of the method you are calling. However, several classes could have the same methods. How does load_assembly_and_get_function_pointer know which one?

See the above link for details, but you can also see a concrete usage example here.

/cc @vitek-karas

  1. Where is the best place to ask questions like this?

This repo is a great location for these questions. Please feel free to suggest samples that we can construct to help explain demonstrate these concepts.

Ultimately, I will be writing a code generator that generates a C interface for bi-directional communications.

Ha! That is something I am working on in an experiment for possible future inclusion in the SDK: AaronRobinsonMSFT/DNNE#23.

@blakemcbride
Copy link
Author

load_assembly_and_get_function_pointer has a parameter 'type_name'. I need more detailed documentation on that. Do have that?

Thanks for all the rapid help. It really helps. I appreciate it!!

@AaronRobinsonMSFT
Copy link
Member

I need more detailed documentation on that. Do have that?

Bah. Oh that. That value is a huge pain and not obvious. That value is eventually passed to the following managed code:

/// <summary>
/// Native hosting entry point for creating a native delegate
/// </summary>
/// <param name="assemblyPathNative">Fully qualified path to assembly</param>
/// <param name="typeNameNative">Assembly qualified type name</param>
/// <param name="methodNameNative">Public static method name compatible with delegateType</param>
/// <param name="delegateTypeNative">Assembly qualified delegate type name</param>
/// <param name="reserved">Extensibility parameter (currently unused)</param>
/// <param name="functionHandle">Pointer where to store the function pointer result</param>
[UnmanagedCallersOnly]
public static unsafe int LoadAssemblyAndGetFunctionPointer(IntPtr assemblyPathNative,
IntPtr typeNameNative,
IntPtr methodNameNative,
IntPtr delegateTypeNative,
IntPtr reserved,
IntPtr functionHandle)

The managed API that uses the string is at

// Get the requested type.
Type type = Type.GetType(typeName, resolver, null, throwOnError: true)!;

An example of usage is at https://github.com/dotnet/samples/blob/2c00acdb3fb3a9266cce3fcf549ef5d7149e5b78/core/hosting/HostWithHostFxr/src/NativeHost/nativehost.cpp#L94-L106.

@blakemcbride
Copy link
Author

blakemcbride commented Aug 27, 2020

Thanks! I am passing several different types between C & C#. All are working well except opaque pointers (void *). That's very important to me. I have a pointer I am passing to C# and then back to C. It should be the same value when it gets back to C but it's not. How can I do that?

Thanks!!

@AaronRobinsonMSFT
Copy link
Member

@blakemcbride What are the signatures being used here? Both the C and C# function signatures.

@blakemcbride
Copy link
Author

blakemcbride commented Aug 27, 2020

The pointer is meaningful in C but not C#. I want to pass it into C# and allow C# to pass it back at another time. So basically, I don't want the process to mess with the pointer. When it comes back to C, it needs to be the same value as when I passed it to C#.

Thanks!

@AaronRobinsonMSFT
Copy link
Member

@blakemcbride I get the desire to not impact the pointer value, what I am inquiring about is what signatures have you tried?

Consider the following C function signature:

void PassPointerToCallback(void* ptr, void(*cb)(void*));

In C# the following signatures could be written to call the above function:

static void PassPointerToCallback1(IntPtr ptr, IntPtr cb);

static unsafe void PassPointerToCallback2(void* ptr, void* cb);

delegate void CallbackDelegate(IntPtr v);
static unsafe void PassPointerToCallback3(IntPtr ptr, CallbackDelegate cb);

Each of the above has different trade-offs and semantics. Understanding what you have tried and what you haven't is helpful in describing why the pointer value is being altered. We need to understand the function signature on both sides to help explain what is being observed.

@blakemcbride
Copy link
Author

Hi. Except for getting or creating detailed docs on load_assembly_and_get_function_pointer parameter 'type_name' parameter, I think I've got everything I need. I am able to call C -> C# and C# back to C. I can also pass and return all data types I need. It all runs on Linux. If you like, I can upload it for you.

Thanks for all the help!

@nxrighthere
Copy link

Guys, any idea how to get a function pointer of a managed method in .NET 5 with MethodInfo obtained using reflection similar to Delegate.CreateDelegate() with Marshal.GetFunctionPointerForDelegate()? MethodHandle.GetFunctionPointer() is for internal runtime usage, so not sure how to handle properly such case.

@jkotas
Copy link
Member

jkotas commented Aug 27, 2020

MethodHandle.GetFunctionPointer() does the same thing as &<method-name> in C# 9.

If the method is marked with UnmanagedCallersOnlyAttribute, it is fine to use MethodHandle.GetFunctionPointer() for the scenario that you are describing.

@nxrighthere
Copy link

Oh, that's awesome, thanks!

@AaronRobinsonMSFT
Copy link
Member

@blakemcbride Issue filed for clearer APIs: https://github.com/dotnet/dotnet-api-docs/issues/4733

Thanks @vitek-karas!

@ghost ghost locked as resolved and limited conversation to collaborators Dec 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Interop-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

5 participants