3

I have two different implementation of marshaling C code into C#.

First of all the C code is:

int GetVersion(char * pVer, size_t * piSize);

My first attempt in C#(I don't show the DllImport part):

static extern int GetVersion( byte[] ver, ref UIntPtr size );

And the other attempt in C#:

static extern int GetVersion([MarshalAs(UnmanagedType.LPStr)] StringBuilder ver, ref ulong size);

I came through these from different examples in net. But I cannot conclude which one is correct.

GNZ
  • 575
  • 2
  • 10
  • What have you done to even *try* and determine which, if either, is "correct"? – Scott Hunter Jan 10 '22 at 01:01
  • There are these ways in the examples I found. What do you mean? – user1245 Jan 10 '22 at 01:03
  • The StringBuilder flavor is more likely to be practical, you won't need Encoding.GetString(). Don't forget to set its Capacity before making the call, aim high. Using *int* for the 2nd parameter is practical. – Hans Passant Jan 10 '22 at 01:09
  • @HansPassant: How do you know there's a NUL on the end? The more I think about it, the more I think there isn't one. – Joshua Jan 10 '22 at 01:14
  • Because that's what a *practical* programmer would do. – Hans Passant Jan 10 '22 at 01:31
  • Did you look at https://stackoverflow.com/questions/1994477/c-sharp-pinvoke-out-strings-declaration – Flydog57 Jan 10 '22 at 03:38
  • Given that that call includes a `char*` and an `int*`, there's a chance that it may expect the caller to deallocate the buffer. Do you have docs on the call? Getting P/Invoke just right is tricky – Flydog57 Jan 10 '22 at 03:53

1 Answers1

2

Bare bones: we can always do this

static extern int GetVersion(IntPtr ver, ref UIntPtr size );

I would never write this because I don't like what this generates for A->W conversion:

static extern int GetVersion([MarshalAs(UnmanagedType.LPStr)] StringBuilder ver, ref UIntPtr size);

Also, if this is a true counted string, the P/Invoke code doesn't work as it wants a 0 after the end.

I would normally write

static extern int GetVersion( byte[] ver, ref UIntPtr size );

in which you must first create ver at the maximum size. You can then convert ver to string using the stuff in System.Text.Encoding. GetVersion() almost certainly passes a constant back, so you can hardcode the correct encoding (probably ASCII anyway).

There's a pretty good chance you're missing [DllImport(..., CallingConvention=CallingConvention.Cdecl)] causing you some memory corruption, but I can't actually tell.

Mistake: don't use ulong for size_t. size_t is UIntPtr on all supported platforms.


Comment suggests a completely different signature GetDevInfo(int Index, DevInfo * pdevInfo); This is a different thing altogether and there's clearly one best choice:

const int IDLength = 100;
[StructLayout(LayoutKind.Sequential)]
struct DeviceInfo {
   [MarshalAs(UnmanagedType.ByValArray, SizeConst = IDLength)]
   byte[] ID;
}

static extern /* I don't know what's here because missing from comment */
    GetDevInfo(int index, ref strcut DeviceInfo info);

string GetInfo(int index)
{
    var info = new DeviceInfo();
    info.ID = new byte[IDLength];
    GetDevInfo(info.ID);
    return System.Text.Encoding.ASCII.GetString(info.ID, 0, strlen(info.ID));
}

int strlen(byte[] bytes)
{
    int i;
    for (i = 0; i < bytes.Length && bytes[i] != 0; i++)
        ;
    return i;
}
````
Joshua
  • 40,822
  • 8
  • 72
  • 132
  • I see, very nice answer. Btw I sometimes see [Out] instead of ref. For example in C side where DevInfo is a struct there is the function with argument: myFunction(DevInfo * devInfo). Now in C# I saw this is sometimes translated as "ref DevInfo devInfo" and some writes "[Out ] DevInfo devInfo". Have you ever seen that? – GNZ Jan 10 '22 at 01:22
  • @GNZ: I have. Does `GetVersion` read the parameter or only write to it? If it reads it it's `ref` if it writes it it's `out`. An API like this might take a maximum value as the initial value of `size`. – Joshua Jan 10 '22 at 01:23
  • The function is: GetDevInfo(int Index, DevInfo * pdevInfo); In the header it says this function retrieves device info. I guess it is out then. Correct? – GNZ Jan 10 '22 at 01:31
  • @GNZ: `GetDevInfo` doesn't take a pointer to a string. The answer's going to be completely different. – Joshua Jan 10 '22 at 01:32
  • \in the header param DeviceInfo [out] Device info structure – GNZ Jan 10 '22 at 01:32
  • Its a struct. Here is Devinfo: typedef struct { char ID[100]; } DevInfo; – GNZ Jan 10 '22 at 01:34
  • I was talking about [Out] attribute not out btw. – GNZ Jan 10 '22 at 01:47
  • Thank you anyway Im very glad you wrote a great answer before they close this question. – GNZ Jan 10 '22 at 02:05
  • "[Out] Indicates that data should be marshaled from callee back to caller." So shouldn't it be [Out]? Because we want to marshal the struct from C(callee) to C#(caller) https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.outattribute?view=net-6.0 – GNZ Jan 10 '22 at 02:21
  • That's the thing. The call signature doesn't contain enough information for you to describe the marshaling mechanism. You need to understand how the function is expected to be called – Flydog57 Jan 10 '22 at 03:54