AaronRobinsonMSFT / DNNE

Prototype native exports for a .NET Assembly.
MIT License
406 stars 41 forks source link

Is it possible to make "out" or "ref" calls by reference? #42

Closed climblinne closed 3 years ago

climblinne commented 4 years ago

At the moment I see, that only call by values are allowed. So only the return value can transport information back from the function. Could parameters make calls by reference? I would need to return multiple string or to modify an image (.net would copy in a preallocated array (from C)).

Also some hints, when there is no solution in your project, would be nice. Maybe there is another solution around. I need it for Linux and Windows.

AaronRobinsonMSFT commented 4 years ago

At the moment I see, that only call by values are allowed. So only the return value can transport information back from the function. Could parameters make calls by reference?

@climblinne Yes they can. However what you are indicates a minor misunderstanding in what is possible with this project. The current requirement of DNNE exports is that they contain only blittable types. Marking an argument with ref or out makes the argument a "by ref" which is under the covers a managed pointer (non-blittable), not a pure native pointer (blittable). This requirement exists because the project is designed to by-default be a building block that is low level and letting the developer optimize the marshalling as needed for their scenario.

Exporting a managed function as a native one requires making all argument blittable so all marshalling must be done manually by the developer exposing the managed function. For example the following signature to return an array of functions could be exposed to native callers, but note the developer needs to handle all memory management a type conversions.

[UnmanagedCallersOnly]
public unsafe static int GetStrings(ushort*** arrayOfStrings)
{
    // 1) Allocate native memory for ushort**.
    //   The native side needs to know how to
    //     free the returned memory.
    //   The managed char is actually 16-bits wide (i.e. ushort in C#). 
    // 2) Convert all strings to ushort* and insert into array.
    // 3) Assign native memory for array to *arrayOfStrings.
    // 4) Return length of allocated array.
}

The following example is on Windows.

using GetStringsFptr = int(__stdcall *)(uint16_t***);

HMODULE mod = ::LoadLibraryW(L"PATH TO NATIVE MODULE");
auto fptr = (GetStringsFptr)::GetProcAddress("GetStrings");

uint16_t** arrayOfStrings;
int len = fptr(&arrayOfStings);
for (int i = 0; i < len; ++i)
{
    ::wprintf((wchar_t*)arrayOfStrings[i]);
}

// Free memory of each string and then the returned array.
// On Windows the canonical convention is CoTaskMemAlloc().
// On non-Windows the convention is free().
// When allocating native memory in managed code using
// Marshal.AllocCoTaskMem() this convention is followed.
AaronRobinsonMSFT commented 3 years ago

@climblinne Please let me know if you have any additional questions or my response wasn't clear.

climblinne commented 3 years ago

@AaronRobinsonMSFT With your information all my problems are gone. Thanks Aaron. Really a nice project to build automatically the native dll/so connector.