dotnet / samples

Sample code referenced by the .NET documentation
https://docs.microsoft.com/samples/browse
Creative Commons Attribution 4.0 International
3.39k stars 5.08k forks source link

CustomHostClr Sample doesn't work with returning values #6975

Open RomanGirin opened 4 months ago

RomanGirin commented 4 months ago

[CustomHostClr Sample doesn't work with returning values]

I worked through this sample core/hosting/src. There is a missing sample for invocation when some value retuned from the managed side via pre-defined buffer. I tried to add this sample myself but it doesn't seem to work.

On the native side (in nativehost.cpp line ~190) added:

        // Function pointer to managed delegate with non-default signature
        typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_returning_fn)(wchar_t const* someptr, wchar_t* out_res);
        custom_entry_point_returning_fn custom_returning = nullptr;

        // Custom delegate type
        rc = load_assembly_and_get_function_pointer(
            dotnetlib_path.c_str(),
            dotnet_type,
            STR("CustomEntryPointReturning") /*method_name*/,
            STR("DotNetLib.Lib+CustomEntryPointReturningDelegate, DotNetLib") /*delegate_type_name*/,
            nullptr,
            (void**)&custom_returning);
        assert(rc == 0 && custom_returning != nullptr && "Failure: load_assembly_and_get_function_pointer()");
        wchar_t const* someptr = STR("some data");
        wchar_t buffer[128]{STR("some init data")};
        custom_returning(someptr, buffer);

        std::wcout << STR("returned buffer: ") << buffer << std::endl;

In the Lib.cs I added:

        public delegate void CustomEntryPointReturningDelegate(IntPtr someptr, StringBuilder res);
        public static void CustomEntryPointReturning(IntPtr someptr, StringBuilder res)
        {
            string message = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
                ? Marshal.PtrToStringUni(someptr)
                : Marshal.PtrToStringUTF8(someptr);

            Console.WriteLine($"Hello, world! from {nameof(CustomEntryPointReturning)} in {nameof(Lib)}");

            Console.WriteLine($"Passed-in string: {message}");

            Console.WriteLine($"the buffer on the managed side: {res.ToString()}");
            res.Clear();
            res.Append("some returned text");
            Console.WriteLine($"the buffer on the managed side: {res.ToString()}");
        }

It outputs:

Hello, world! from CustomEntryPointReturning in Lib
Passed-in string: some data
the buffer on the managed side: s
the buffer on the managed side: some returned text
returned buffer:

Please, add this kind of example I sketch above (with some fixes required) into the sample.

UPDATE: To fix reported behaviour

On the native side (in nativehost.cpp line ~190) added:

        // Function pointer to managed delegate with non-default signature
        typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_returning_fn)(wchar_t const* someptr, char* out_res);
        custom_entry_point_returning_fn custom_returning = nullptr;

        // Custom delegate type
        rc = load_assembly_and_get_function_pointer(
            dotnetlib_path.c_str(),
            dotnet_type,
            STR("CustomEntryPointReturning") /*method_name*/,
            STR("DotNetLib.Lib+CustomEntryPointReturningDelegate, DotNetLib") /*delegate_type_name*/,
            nullptr,
            (void**)&custom_returning);
        assert(rc == 0 && custom_returning != nullptr && "Failure: load_assembly_and_get_function_pointer()");
        wchar_t const* someptr = STR("some data");
        char buffer[128]{"some init data"};
        custom_returning(someptr, buffer);

        std::wcout << STR("returned buffer: ") << buffer << std::endl;

Note the change of type for out_res pointer from wchat_t to char. And then it works (the output is below):

Hello, world! from CustomEntryPointReturning in Lib
Passed-in string: some data
the buffer on the managed side: some init data
the buffer on the managed side: some returned text
returned buffer: some returned text

It's strange that marshaled back string is 8-bit instead of 16-bit. If you let me know I'll create issue in more relevant repo about this possible CLR marshaling bug.


Issue metadata

RomanGirin commented 4 months ago

Also add a small remark as a comment: this example IMHO is very important because returning values via pre-created buffer is very often used idiom. Actually this is the only way(?) to return a string from managed side to native. So without this kindly requested addition the sample is incomplete. Thank you in advance!