How to get char** using C#? [duplicate]
Asked Answered
B

3

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;
}
Banter answered 9/6, 2016 at 18:11 Comment(19)
char* I know how to do, but char**...? Argh.Laural
Agreed - question is actually concise. I've not done this so I cannot answer :)Rembert
I think you need to use StringBuilder[] instead of string[]Subglacial
Possibly, but there still is the signature issue - char ** is not a StringBuilder.Rembert
@LukStorms I'm not in control of the function. It's a 3rd party dll.Banter
using ref to a char * might do it.Rembert
How about this link: #11573131Rembert
What about using &args in an unsafe context (where args is a string)?Vizza
@MichaelDorgan The answer to that question has a seemingly useful link (Marshaling char**) mono-project.com/docs/advanced/pinvoke and that's probably where I'll start looking if an easier solution doesn't present itself.Banter
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 bytesJimjimdandy
The downvotes are perfectly valid. Asking how to call an unmanaged function without specification of that unmanaged function is pointless. The type is char**. Fine. But what are the semantics?Balliol
I feel this is missing information; how many strings are you expecting to be added to the array? Are they null terminated or fixed length strings?Oxus
@DavidHeffernan I updated the question with relevant information.Banter
@MeirionHughes Question updated.Banter
No you did not. We still have no idea what the unmanaged interface is. Anyway, you've accepted an answer, and the question is closed. I'm amazed at the interest in this question. I'm not sure why there is so much activity, but unfortunately you have received a huge amount of poor advice. There should be no answers because your question is not well specified. Until you can specify the interface which you are trying to meet, it is pointless for anyone to write code. That you are doing all this trial and error is just a huge waste of time.Balliol
@DavidHeffernan There's so much activity because main(int argc, char** argv) is a fairly common entry to native c dll's, and there isn't a single stackoverflow question that answers how to marshal data across it. Certainly not the alleged duplicate questions (one talks about char*, the other about const char**).Banter
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.Balliol
@DavidHeffernan All I'm getting from the vendor, is that they expect the first argument to be name of the executable, no mention of the final NULL argument. I'll check again to be sure. In any case, I finally got it working (see my final edit). The function was expecting an array of pointers to string buffers, and not concatenated string buffers as I was told initially. Once I made that change, the application started working as expected. Your point about NULL argument is a good one, as it may just work by accident now, if the memory beyond my pointers just happens to be zeroed.Banter
Just verified, they use argc to exit the parsing loop.Banter
L
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.

Liu answered 9/6, 2016 at 18:42 Comment(2)
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.Banter
@Banter 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.Borak
L
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.

Lesser answered 9/6, 2016 at 19:18 Comment(5)
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**.Banter
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.Lesser
The function doesn't expect a C# char**, which is why I used a byte like you suggested.Banter
But then the C# DllImport signature is wrong because it uses the C# char.Lesser
All fixed now in my final edit. Your byte comment was helpful.Banter
S
0

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

StringBuilder[] args = new StringBuilder[] { };
start(args);
Subglacial answered 9/6, 2016 at 18:40 Comment(1)
After everything I read, shockingly this might actually work.Basophil

© 2022 - 2024 — McMap. All rights reserved.