12

I need to pass an argument to an unsafe DllImported function in the form of:

[DllImport("third_party.dll")]
private static extern unsafe int start(int argc, char** argv);

I'm assuming it's an array of strings. However when I try to do the following, I get 'Cannot convert from string[] to char**' error. How do I go about getting this to work? Thanks.

string[] argv = new string[] { };
start(0, argv);

EDIT 1: The question was marked as duplicate, but looking at the possible duplicate question, I still do not see how to get this to work.

EDIT 2: To further clafiry the question and required parameters. It looks like your standard argc/argv parameters (parameter count, and then parameter values). The same way you would start a c program: int main(int argc, char** argv); For this particular problem, I don't want to pass any arguments at all (so count is 0).

EDIT 3: I got more information from the 3rd party library vendor. Here it is:

  • the first parameter is the count of arguments
  • the second parameter is an array of null terminated strings
  • the strings are ANSI encoded

EDIT 4: Final edit with a working solution (at least in my case). I would make this the answer, but can't because this question is marked as a duplicate. Here's a link to a question that helped me the most. In the end the dll function expected an array of pointers to buffers with ANSI strings. So my final approach (based off the linked question), was as follows. Create an array in memory to hold the pointers, then allocate each string elsewhere in memory, and write pointers to those strings inside the first pointer array. This code works in production:

[DllImport("third_party.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern int start(Int32 args, IntPtr argv);

public bool start(params string[] arguments)
{
    int result;

    if (arguments == null || arguments.Length == 0)
    {
        result = dll_system_startup(0, IntPtr.Zero);
    }
    else
    {
        List<IntPtr> allocatedMemory = new List<IntPtr>();

        int sizeOfIntPtr = Marshal.SizeOf(typeof(IntPtr));
        IntPtr pointersToArguments = Marshal.AllocHGlobal(sizeOfIntPtr * arguments.Length);

        for (int i = 0; i < arguments.Length; ++i)
        {
            IntPtr pointerToArgument = Marshal.StringToHGlobalAnsi(arguments[i]);
            allocatedMemory.Add(pointerToArgument);
            Marshal.WriteIntPtr(pointersToArguments, i * sizeOfIntPtr, pointerToArgument);
        }

        result = start(arguments.Length, pointersToArguments);

        Marshal.FreeHGlobal(pointersToArguments);

        foreach (IntPtr pointer in allocatedMemory)
        {
            Marshal.FreeHGlobal(pointer);
        }
    }

    return result == 0;
}
19
  • 2
    Agreed - question is actually concise. I've not done this so I cannot answer :) Commented Jun 9, 2016 at 18:17
  • 1
    using ref to a char * might do it. Commented Jun 9, 2016 at 18:21
  • 1
    How about this link: stackoverflow.com/questions/11572631/… Commented Jun 9, 2016 at 18:25
  • 2
    Note that if it's char in C it won't be char in C#; C char is 1 byte but C# char is 2 bytes Commented Jun 9, 2016 at 18:45
  • 1
    You still have not defined the interface. Usually argv is of length argc + 1 with a final entry that is NULL. Is that the case here? You've asked the question poorly and have received a lot of poor advice. Commented Jun 11, 2016 at 7:15

3 Answers 3

3

I think you might need to use Marshal.

var a = (char*)Marshal.StringToCoTaskMemAuto("myString");
char** = &a;

This is just a guess because I don't have a library that takes char** so I haven't been able to try it.

Sign up to request clarification or add additional context in comments.

2 Comments

It compiled, without me having to change the DllImport signature, but I still end up with PInvokeStackImbalance exception. The call does seem to work partially though, because I see 3rd party dll logging to output window. I need to verify something else is not causing it, before I accept your answer. +1 for now.
@Eternal21 Given the PInvokeStackImbalance error, I'm going to guess you were right on it being "an array of strings," though it'd be really helpful to see more details on the third-party library. Steve is on the right path that you should use Marshal in order to convert strings to unsafe pointers, but I'm thinking you'll need to figure out what those strings are supposed to be, and then build your array from your strings.
2

The equivalent of C's char** is a fully-pinned byte[][] in C# (and by fully-pinned I mean the outer array AND all inner arrays). If you want to pass C# strings you'll have to convert them to byte arrays, for instance with Encoding.ASCII.GetBytes.

5 Comments

I've decided to manually 'Marshal' the data as a single byte[] buffer. Ive edited my question with the new code I came up with. It looks like it should work, but the library doesn't like it, I end up with Access Violation from it. Not sure if it's because I used IntPtr instead of byte**.
Your dllimport signature is probably wrong. It's unlikely the function expects a C# char**, which would be like a wchar_t** in C. It should probably be simply string[] instead and let P/Invoke deal with the marshalling, as in the linked duplicate questions.
The function doesn't expect a C# char**, which is why I used a byte like you suggested.
But then the C# DllImport signature is wrong because it uses the C# char.
All fixed now in my final edit. Your byte comment was helpful.
0

Can you change the DllImport declaration parameter type to StringBuilder[]? Then you can call the function like:

StringBuilder[] args = new StringBuilder[] { };
start(args);

1 Comment

After everything I read, shockingly this might actually work.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.