DNNE
DNNE copied to clipboard
Platform usage compatibility
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
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
Awesome! I will try to put something together to test within the next week or two, will get back with you.
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?
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 https://github.com/AaronRobinsonMSFT/DNNE/issues/84#issuecomment-924191881 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.
https://github.com/AaronRobinsonMSFT/DNNE/blob/d48209b02e956b3a56b19dc6672e56ccfeefaf55/src/msbuild/DNNE.props#L51-L54
The CI leg compiles and runs on Linux.
https://github.com/AaronRobinsonMSFT/DNNE/blob/d48209b02e956b3a56b19dc6672e56ccfeefaf55/.github/workflows/main.yml#L10-L25
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
@johnwc The issue above is that you are using g++ and not gcc. The generated source for DNNE is pure C99, not C++.
@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).
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.
@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.
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.
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.
@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
@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 *.
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);
}