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

NoWrap questions #37

Open
ricknijhuis opened this issue Jul 28, 2024 · 3 comments
Open

NoWrap questions #37

ricknijhuis opened this issue Jul 28, 2024 · 3 comments
Labels
question Further information is requested

Comments

@ricknijhuis
Copy link

First off thanks for this incredible library it's mostly easy to use and works well. I do have some questions though regarding the NoWrap option.

  1. When using nowrap for an opaque ptr typedef it still wraps for example:
    typedef struct Test Test;
    Will result in
[global::System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public readonly partial struct Test : IEquatable<native.Test >
{
    public Test(nint handle) => Handle = handle;
    
    public nint Handle { get; }
    
    public bool Equals(Test other) => Handle.Equals(other.Handle);
    
    public override bool Equals(object obj) => obj is Test other && Equals(other);
    
    public override int GetHashCode() => Handle.GetHashCode();
    
    public override string ToString() => "0x" + (nint.Size == 8 ? Handle.ToString("X16") : Handle.ToString("X8"));
    
    public static bool operator ==(Test left, Test right) => left.Equals(right);
    
    public static bool operator !=(Testleft, Testright) => !left.Equals(right);
}

Is it possible to just ignore the typedef if it is an opaque ptr and just use nint whenever it is used as a parameter or member?

  1. When using nowrap for an function pointer typedef. it does not generate a type at all, for example:
typedef void (*Testfun)(int arg1, const char* arg2);

gets ignored and functions using it as param get the following signature

public static partial delegate*unmanaged[Cdecl]<int, byte*, void> setTestCallback(delegate*unmanaged[Cdecl]<int, byte*, void> callback);

Would it be possible to just not wrap it and add the delegate to the main class directly like so:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void Testfun(int arg1, const char* arg2);

And lastly maybe a bit unrelated question but what would be the easiest way to change the names of functions, types and constants?Within a c library they often prefix a library name as a sort of namespace, In c# this would be unnecessary as you already have both the namespace and the class. Is there a built-in way to remove such a suffix? for example testInit() to just Init() where test is the prefix?

@xoofx
Copy link
Owner

xoofx commented Jul 28, 2024

Is it possible to just ignore the typedef if it is an opaque ptr and just use nint whenever it is used as a parameter or member?

I would believe that setting both:

Might do it, but might be not. 😅

It is sometimes impossible to convey all the sorts of weirdness that you could have with some libraries just with limited enum/boolean knobs, so you can override the default conversion yourself by adding your own ICSharpConverterPlugin that changes typedef conversion (See an example here integrated here).

When using nowrap for an function pointer typedef. it does not generate a type at all, for example:

Well precisely, if you don't generate a wrap (a typedef) then it is used inline. Can't do anything here as it is using the new GC free unsafe delegate. The codegen library is now trying to stay away from using managed delegates because they are not efficient and causing problems (you need to keep them alive, they generate marshalling when using them in structs or function arguments...etc.). I think there is always the DefaultLegacyFunctionTypeConverter lying around that you could use inplace of the existing one but you have to manipulate plugins, similarly to my response above.

And lastly maybe a bit unrelated question but what would be the easiest way to change the names of functions, types and constants?Within a c library they often prefix a library name as a sort of namespace, In c# this would be unnecessary as you already have both the namespace and the class. Is there a built-in way to remove such a suffix? for example testInit() to just Init() where test is the prefix?

Yeah, many of these things are not documented, but you can do it with mapping rules for example (or in custom plugins if you want. Mapping rules are using plugins behind the scene to plug into the conversion pipeline).

For example this line here

                    // sys/sysinfo.h
                    e => e.Map<CppClass>("sysinfo").Name("sysinfo_t"),

Is renaming the original struct sysinfo to sysinfo_t

There are several other rules that can be used, like changing the type (e.g of a field / parameter), or changing the way a pointer parameter is passed to a function (by ref, by in, by pointer directly...etc), discard type/functions entirely, map defines to const/enums...

Some examples:

There are other examples in the linked repository. The key is that, for many C/C++ headers not designed for mapping to some specific languages, you will have to build your own extended codegen and modify the generated C# model to fit the constraints of the C/C++ headers you are dealing with. In the repository linked, I can often clone functions to provide other marshalling options (e.g via Span) or integrate documentation coming from another source (e.g troff man pages)...etc. You will see that some generators I have developed are relatively small, while some others are a lot more complicated.

CppAst.CodeGen should allow you to cover custom cases, many things are pluggable/overrideable... but it's not documented at all. ☺️

@xoofx xoofx added the question Further information is requested label Jul 28, 2024
@ricknijhuis
Copy link
Author

Just one thing I found, you mentioned that setting TypedefCodeGenKind to NoWrap and DetectOpaquePointers to false would result in nint being used but this is not the case. It will generate an empty struct as follows:

public readonly partial struct Test
{
} 

And use that type for function arguments and fields. I believe that this is not a valid outcome right?

@xoofx
Copy link
Owner

xoofx commented Jul 28, 2024

And use that type for function arguments and fields. I believe that this is not a valid outcome right?

It depends of your C++. If you have typedef struct Test Test; it will still generate a struct Test. The struct test is not an nint here. It will likely be used as Test*.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants