Emitting `[MarshalAs(...)]` dynamically using Reflection.Emit
Asked Answered
B

1

6

I need to create a dynamic .NET delegate with specially marshalled parameters (e.g. [MarshalAs(UnamangedType.LPWStr)]) at runtime using System.Reflection.Emit. This requires me to create a type inheriting MulticastDelegate (no problems so far), an appropriate constructor, and the Invoke-method with the specially marshalled parameters.

My C# code so far:

TypeBuilder type_builder = ...;

MethodBuilder method_builder = type_builder.DefineMethod(
    "Invoke",
    MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Public,
    CallingConventions.Standard,
    typeof(int),
    new[] { typeof(string) } // only an example - this array could hold any number of parameter types
);
ParameterBuilder parameter_1 = invoke.DefineParameter(1, ParameterAttributes.HasFieldMarshal, "arg0");

parameter_1.SetCustomAttribute(new CustomAttributeBuilder(
    typeof(MarshalAsAttribute).GetConstructor(new[] { typeof(UnmanagedType) }),
    new object[] { UnmanagedType.LPWStr }
));

// ...

type_builder.CreateType();

I can generate the dynamic assembly, however the application crashes upon invocation of the 'Invoke'-method due to invalid IL instructions.

After a closer inspection using the peverify and ildasm-tools I detect, that my code above emits the following IL:

.method public hidebysig newslot virtual instance int32 Invoke (
        string arg0
    ) runtime managed
{
    .param [1]
    .custom instance void [System.Private.CoreLib]System.Runtime.InteropServices.MarshalAsAttribute::.ctor(
            class [System.Private.CoreLib]System.Runtime.InteropServices.UnmanagedType) = (
        01 00 15 00 00 00 05 00 53 08 0c 41 72 72 61 79 /* ...huge binary blob... */ 00 00 00 00
    )

    // ...
}

Whereas the following should be the correct IL code:

.method public hidebysig newslot virtual instance int32 Invoke (
        string marshal(lpwstr) arg0
    ) runtime managed
{
    // ...
}

(Namely the marshal(lpwstr)-modifier on arg0!)

My Question

How do I have to change the code above to emit the correct marshalling information?

The MSDN documentation is not helping, as it always refers to the obsolete method MethodBuilder.SetMarshal(UnmanagedMarshal). Furthermore, the documentation only tells me to "emit the MarshalAs custom attribute instead" - which is pretty unhelpful, because this is exactly what I am doing.

Note: I am using .NET Core 5

EDIT:

Creating the IL code for a delegate is not a problem. I've tested it and it works flawlessly. The only trouble I have is correctly implementing marshal(...).

Brogle answered 29/7, 2020 at 17:27 Comment(0)
D
2

This question is over two years old, but I think I may have found the issue here.

From what I can tell, ParameterAttributes.HasFieldMarshal is used to indicate that the default marshaling behavior will be used to marshal the parameter, which may have fields that require additional explicit marshaling, e.g. a struct which contains a field with the MarshalAs attribute:

public struct MyStruct
{
    [MarshalAs(UnmanagedType.LPWStr)]
    public string s; // field has explicit marshaling
}

public delegate void Foo(MyStruct s); // param uses default marshaling behavior

When you create the ParameterBuilder (with DefineParameter), you are establishing that the default marshaling behavior will be used for the parameter and its fields (respective to the MarshalAs attribute set on each field). Then, the call to SetCustomAttribute is ignored because the marshaling behavior for the parameter has already been defined.

The correct behavior (as you have described it) would be to pass ParameterAttributes.None when defining the parameter for which you will later call SetCustomAttribute to set the marshaling behavior.

(Incidentally, I ended up here because I was attempting the same thing, but passing the wrong parameter index due to a misreading of the DefineParameter documentation.)

Dropkick answered 17/11, 2022 at 0:59 Comment(3)
Thank you very much for your reply. The project for which I initially asked this question has been set on hold due to the missing resolution to this problem. I will try your solution in the coming days/weeks and will comment my findings/results. However, I already want to thank you in advance for answering this question!Brogle
@Brogle my project NativeGenericDelegates may be of interest to you, even if only for reference. I provide a full API that wraps runtime delegates with optional custom marshaling.Dropkick
Thanks. I'll have a look at the repository you've linked. It sounds promising!Brogle

© 2022 - 2024 — McMap. All rights reserved.