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

Platform usage compatibility #104

Open
johnwc opened this issue Dec 13, 2021 · 14 comments
Open

Platform usage compatibility #104

johnwc opened this issue Dec 13, 2021 · 14 comments
Labels
discussion Issues used for discussion a design or problem in/using DNNE.

Comments

@johnwc
Copy link

johnwc commented Dec 13, 2021

With this package, would we be able to develop a hook library for the kea dhcp server for linux? If so, we would be willing to create one and share it on gtihub as a good real-world working example for others to learn.
Hooks Developer's Guide

@AaronRobinsonMSFT AaronRobinsonMSFT added the discussion Issues used for discussion a design or problem in/using DNNE. label Dec 14, 2021
@AaronRobinsonMSFT
Copy link
Owner

would we be able to develop a hook library for the kea dhcp server for linux?

@johnwc Yes! That is an example of where DNNE could be a good solution I think. In fact, I would really appreciate someone helping to validate more complex scenarios on the linux platform. Let me know if there is anything I can help with.

/cc @adityamandaleeka @richlander

@johnwc
Copy link
Author

johnwc commented Dec 15, 2021

Awesome! I will try to put something together to test within the next week or two, will get back with you.

@johnwc
Copy link
Author

johnwc commented Dec 16, 2021

Is it possible to compile a linux library using VS in windows? Or do I need to be copying the files to a linux machine to compile it?

@AaronRobinsonMSFT
Copy link
Owner

Is it possible to compile a linux library using VS in windows?

@johnwc Nope. Unfortunately that is a really complicated issue. However, this has been asked before at #84 (comment) and I provided some details.

Or do I need to be copying the files to a linux machine to compile it?

The DNNE tooling should "just work" on Linux as long as clang is on the path—typically the default once installed. Using gcc is possible using the following MSBuild property.

<!-- Set to override the computed native compiler command.
This value will be placed in double quotes (e.g. "command") and passed
the computed compiler arguments. -->
<DnneCompilerCommand></DnneCompilerCommand>

The CI leg compiles and runs on Linux.

linux:
runs-on: ubuntu-latest
strategy:
matrix:
flavor: [ 'Debug', 'Release' ]
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
include-prerelease: true
- name: Build Product and Package
run: dotnet build src/create_package.proj -c ${{ matrix.flavor }}
- name: Unit Test Product
run: dotnet test test/DNNE.UnitTests -c ${{ matrix.flavor }}

@johnwc
Copy link
Author

johnwc commented Dec 16, 2021

I created a project and pushed it to GH. I was able to get just a plain sample to compile in linux. Then added the hooks.h include, received a lot of errors. Changed to to use g++ from clang, most of the errors disappeared. But now get this single error. Any ideas?

My background: I am not at all new to programming. Just not very experienced in C++, but have done quite a bit with managed -> unmanaged calls. Have been developing in c# mostly for past 15 years, so feel free to get as technical as needed in conversation.

$ dotnet build -c Release
Microsoft (R) Build Engine version 16.9.0+5e4b48a27 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  Building native export: "g++" -O2 -shared -fpic -D DNNE_ASSEMBLY_NAME=Test -D DNNE_COMPILE_AS_SOURCE -I "/home/jcarew/.nuget/packages/dnne/1.0.27/tools/platform" -I "/usr/lib64/dotnet/packs/Microsoft.NETCore.App.Host.centos.8-x64/5.0.12/runtimes/centos.8-x64/native" -I "/usr/include/kea" -o "/home/jcarew/hooks/obj/x64/Release/net5.0/dnne/bin/TestNE.so" "/home/jcarew/hooks/obj/x64/Release/net5.0/dnne/Test.g.c" "/home/jcarew/.nuget/packages/dnne/1.0.27/tools/platform/platform.c" -lstdc++ "/usr/lib64/dotnet/packs/Microsoft.NETCore.App.Host.centos.8-x64/5.0.12/runtimes/centos.8-x64/native/libnethost.a"
  /home/jcarew/hooks/obj/x64/Release/net5.0/dnne/Test.g.c: In function ‘int32_t FancyName(int32_t)’:
/home/jcarew/hooks/obj/x64/Release/net5.0/dnne/Test.g.c(72,59): error G7E4560F8: invalid conversion from ‘void*’ to ‘int32_t (*)(int32_t)’ {aka ‘int (*)(int)’} [-fpermissive] [/home/jcarew/hooks/Test.csproj]
           FancyName_ptr = get_fast_callable_managed_function(t1_name, methodName);
                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
  /home/jcarew/.nuget/packages/dnne/1.0.27/tools/platform/platform.c:34: warning: "_GNU_SOURCE" redefined
       #define _GNU_SOURCE

  <command-line>: note: this is the location of the previous definition
/home/jcarew/.nuget/packages/dnne/1.0.27/build/DNNE.targets(145,5): error MSB3073: The command ""g++" -O2 -shared -fpic -D DNNE_ASSEMBLY_NAME=Test -D DNNE_COMPILE_AS_SOURCE -I "/home/jcarew/.nuget/packages/dnne/1.0.27/tools/platform" -I "/usr/lib64/dotnet/packs/Microsoft.NETCore.App.Host.centos.8-x64/5.0.12/runtimes/centos.8-x64/native" -I "/usr/include/kea" -o "/home/jcarew/hooks/obj/x64/Release/net5.0/dnne/bin/TestNE.so" "/home/jcarew/hooks/obj/x64/Release/net5.0/dnne/Test.g.c" "/home/jcarew/.nuget/packages/dnne/1.0.27/tools/platform/platform.c" -lstdc++ "/usr/lib64/dotnet/packs/Microsoft.NETCore.App.Host.centos.8-x64/5.0.12/runtimes/centos.8-x64/native/libnethost.a" " exited with code 1. [/home/jcarew/hooks/Test.csproj]

Build FAILED.

/home/jcarew/hooks/obj/x64/Release/net5.0/dnne/Test.g.c(72,59): error G7E4560F8: invalid conversion from ‘void*’ to ‘int32_t (*)(int32_t)’ {aka ‘int (*)(int)’} [-fpermissive] [/home/jcarew/hooks/Test.csproj]
/home/jcarew/.nuget/packages/dnne/1.0.27/build/DNNE.targets(145,5): error MSB3073: The command ""g++" -O2 -shared -fpic -D DNNE_ASSEMBLY_NAME=Test -D DNNE_COMPILE_AS_SOURCE -I "/home/jcarew/.nuget/packages/dnne/1.0.27/tools/platform" -I "/usr/lib64/dotnet/packs/Microsoft.NETCore.App.Host.centos.8-x64/5.0.12/runtimes/centos.8-x64/native" -I "/usr/include/kea" -o "/home/jcarew/hooks/obj/x64/Release/net5.0/dnne/bin/TestNE.so" "/home/jcarew/hooks/obj/x64/Release/net5.0/dnne/Test.g.c" "/home/jcarew/.nuget/packages/dnne/1.0.27/tools/platform/platform.c" -lstdc++ "/usr/lib64/dotnet/packs/Microsoft.NETCore.App.Host.centos.8-x64/5.0.12/runtimes/centos.8-x64/native/libnethost.a" " exited with code 1. [/home/jcarew/hooks/Test.csproj]
    0 Warning(s)
    2 Error(s)

Time Elapsed 00:00:02.86

@AaronRobinsonMSFT
Copy link
Owner

@johnwc The issue above is that you are using g++ and not gcc. The generated source for DNNE is pure C99, not C++.

@AaronRobinsonMSFT
Copy link
Owner

AaronRobinsonMSFT commented Dec 16, 2021

@johnwc The precise issue at hand here is that C++ has stricter casting rules than C99. The reasoning behind using C99 instead of C++ is it is the lingua franca for most of this embedding work. C++ has some subtle issues that can surprise and making a "safe" C++ API is non-trivial and generally winds up being mostly C with some C++ niceties. Unfortunately, these niceties typically introduce C++ features that aren't expected or desired (e.g., exceptions).

@AaronRobinsonMSFT
Copy link
Owner

I'm currently looking at the hooks.h and see that the entire API being used here is C++. Let me see what I can do about making the generated code more C++ friendly. It could be simply adding the appropriate casts.

@AaronRobinsonMSFT
Copy link
Owner

AaronRobinsonMSFT commented Dec 16, 2021

@johnwc I added a build/test for both gcc and g++ on Linux. This required supporting compiling and linking as C++. I've only added testing for g++ but will likely add more. A new package with these changes has been published, https://www.nuget.org/packages/DNNE/1.0.28.

@AaronRobinsonMSFT
Copy link
Owner

I've only added testing for g++ but will likely add more.

Way easier than I thought. There are now tests for compiling as C++ on all platforms.

@johnwc
Copy link
Author

johnwc commented Dec 17, 2021

This is great! With that update, I was able to create the version() method and return the same value that is set for KEA_HOOKS_VERSION in the hooks.h. I also added a simple File.AppendAllLines to the method to have a log to prove it was being called. I compiled just fine, and worked as a a hook when configured and the service restarted. I will attempt to write a simple command hook and see how it goes.

@johnwc
Copy link
Author

johnwc commented Dec 17, 2021

@AaronRobinsonMSFT how do I handle the LibraryHandle class as a parameter for load(LibraryHandle& libhandle)? I don't seem to be able to use classes for parameters with UnmanagedCallersOnly

@AaronRobinsonMSFT
Copy link
Owner

AaronRobinsonMSFT commented Dec 17, 2021

@johnwc This will get very complex and depend on how the Kea types want to be projected into managed code. In this case, the LibraryHandle seems to have some complexity that is going to make thing complicated–uses many C++ types and some are passed around by value (that is, std::vector<std::string> getParameterNames();). The int field is easy, but the CalloutManager& field makes natural projections into .NET difficult. There are many options but let me sketch one out for you below. Note that if Kea types are being projected into .NET that involve inheritance the complexity increases substantially.

unsafe struct LibraryHandle
{
    // This function pointer can be acquired via a DllImport to dlsym, GetProcAddress, or the NativeLibrary API.
    // Another approach would be to create a DllImport into the generated native binary being compiled by DNNE.
    // Then pass all the parts back into native code and make the call as expected.
    // See the readme about overriding dnne_abort. The point here is you can pass a .c or .cpp file
    // to compile into the generated native component and then DllImport into that function.
    private static ??? registerCalloutFptr;

    public static void registerCallout(LibraryHandle* h, string name, delegate*<void*, int> callout)
    {
        // Marshal the string to an byte* using System.Text.UTF8Encoding and a fixed statement.
        UTF8Encoding utf8 = new();
        byte[] encodedBytes = utf8.GetBytes(name);
        fixed (byte* b = encodedBytes)
        registerCalloutFptr(h, b, callout);    
    }
}

[UnmanagedCallersOnly]
private static unsafe int Callback(void* ptr)
{
    return 0;
}

[UnmanagedCallersOnly]
public static unsafe ??? load(void* libhandle)
{
    LibraryHandle* h = (LibraryHandle*)libhandle;
    LibraryHandle.registerCallout(h, "name", &Callback);
    ...
}

A quick note. The C++ reference (i.e., &) is just syntax. Under the covers it is a pointer, and the semantics are dictated by the language and enforced by the compiler. One can make assumptions but from an interop perspective just treat & as a *.

@johnwc
Copy link
Author

johnwc commented Dec 19, 2021

Sounds like I will have to create proxy methods like described here.

class Foo {
public:
  int Bar(int a, int b);
};

// Proxy methods
extern "C" int Foo_Bar(Foo* pFoo, int a, int b) { return pFoo->Bar(a, b); }
static class NativeFoo {
    [DllImport("Foo", EntryPoint = "Foo_Bar")]
    public static extern int Bar(IntPtr obj, int a, int b);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Issues used for discussion a design or problem in/using DNNE.
Projects
None yet
Development

No branches or pull requests

2 participants