How to PInvoke CMark's cmark_markdown_to_html

107 views Asked by At

I'm trying to PInvoke the following function from GitHub's fork of CMark

char *cmark_markdown_to_html(const char *text, size_t len, int options)

and here's my PInvoke signature:

[DllImport("cmark.dll")]
    public static extern IntPtr cmark_markdown_to_html(
        [In()] [MarshalAs(UnmanagedType.LPStr)] string text, 
        [MarshalAs(UnmanagedType.SysUInt)] uint len,
        [MarshalAs(UnmanagedType.SysInt)] int options);

I call it as follows:

var retValue = cmark_markdown_to_html(markdown, 0, 0);

However, it throws a Marshaling exception the with the message:

Cannot marshal 'parameter #2': 
  Invalid managed/unmanaged type combination 
  (Int32/UInt32 must be paired with I4, U4, or Error). 

OK, so I change the signature to:

[DllImport("cmark.dll")]
    public static extern IntPtr cmark_markdown_to_html(
        [In, MarshalAs(UnmanagedType.LPStr)] string text, 
        [MarshalAs(UnmanagedType.U4)] uint len,
        [MarshalAs(UnmanagedType.I4)] int options); 

Now it throws a PInvokeStackImbalance error

The name '$exception' does not exist in the current context

Native stuff is a mystery to me. Can someone help?

2

There are 2 answers

0
Wazner On BEST ANSWER

PInvokeStackImbalance

Presumably CMark is using the default calling convention. The default calling convention for C code is "Cdecl", while the default used for P/invoke calls is "StdCall". The calling convention specifies how the arguments and return value are placed on the call stack. A mismatch between the used calling convention causes the stack to become imbalanced.

You can specify the calling convention to use from C# code in the DllImport attribute.

[DllImport("cmark.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cmark_markdown_to_html(
    [In, MarshalAs(UnmanagedType.LPStr)] string text, 
    [MarshalAs(UnmanagedType.U4)] uint len,
    [MarshalAs(UnmanagedType.I4)] int options); 

Marshalling

In case you want your code to be compatible with architectures other than 32-bit, you would want to marshall size_t with UIntPtr. (Read @Eugene Podskal answer)

[DllImport("cmark.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cmark_markdown_to_html(
    [In, MarshalAs(UnmanagedType.LPStr)] string text, 
    [MarshalAs(UnmanagedType.U4)] UIntPtr len,
    [MarshalAs(UnmanagedType.I4)] int options);

Then you can call the method like so

var retValue = cmark_markdown_to_html(markdown, UIntPtr.Zero, 0);
1
Eugene Podskal On

Stack imbalance

The reason for stack imbalance is described in Why are Cdecl calls often mismatched in the "standard" P/Invoke Convention?.

Basically you need to specify proper calling convention in the DllImport that will probably be cdecl:

[DllImport("cmark.dll", CallingConvention = CallingConvention.Cdecl)]

Marshalling size_t

That one is discussed in what is equal to the c++ size_t in c# .

  • Assuming that CMark will always be native (32 bit on x86, 64 bit on x64), you should use (U)IntPtr.
  • If it is always 32 bit, then Int32 should be used.