user3322182
user3322182

Reputation: 29

How To P/Invoke char* [] in C#

How To P/Invoke char* [] in C#

Any 1 tell how to continue hereafter. I want to send parameters to C++ DLL from C#. I had googled many sites but no solution.

C Function

public void* find(char*[] argv)
{
}

C# Function i want invoke this function with following paramter arguments

char *Argv[] = { "Tool", "Sachin", "192.168.1.1", "3", "400"};

Thanks in Advance.

Upvotes: 1

Views: 1669

Answers (1)

Colin Smith
Colin Smith

Reputation: 12540

There are multiple ways to do it...here are just a couple...but I outline other information you need to be aware of.


Properly Exporting/Importing your "find" Function

First you need to make sure your "exported" C function and the DllImport are defined properly (if you have this working already, then ignore this section).

If your C function is compiled to use the cdecl calling convention (usually the default in C/C++ projects), then you need to use CallingConvention = CallingConvention.Cdecl on the DllImport e.g.

[DllImport("yourdll.dll", CharSet = Ansi, CallingConvention = CallingConvention.Cdecl)]
public IntPtr find([In] String[] args);

If your C function is compiled to use the 'stdcall' calling convention (by project options or by putting the WINAPI macro, or __stdcall decoration on the function definition), then you need to use CallingConvention = CallingConvention.Stdcall (which is the default on DllImport anyway) e.g.

[DllImport("yourdll.dll", CharSet = Ansi)]
public IntPtr find([In] String[] args);

In addition when defining a "C" function you can use extern "C" to stop the the C++ compiler mangling the name of the function.

You then either use __declspec(export), or use a .DEF file to specify the function as an exported entry.


What is your "find" Contract?

Important: you need to know the contract of your "find" function...i.e. what will it look for to signify the "end" of that list of arguments...it might use NULL, or it might use an empty string, etc.

Usually, functions like that, have another parameter that signifies the "count" of the number of items in the array so that a "marker" isn't needed.

For the example below, I will assume it uses NULL at the end...you will have to modify the code accordingly if it's not that way.

If it's possible for some of your parameters to be NULL, then you will have to change the "sentinel" used to signify the end.


You can do the "marshalling" between C# and native types (using the Marshalling attributes):

    // Note: this uses a "NULL" to mark the end of the array...
    // because your "find" function doesn't have a "count" parameter, and I'm
    // assuming it uses a NULL parameter to detect the end. 

    IntPtr opaqueresult = find(new string[] { "Tool", "Sachin", "192.168.1.1", "3", "400", null});  // call the function in that DLL.

Or you can do the marshaling logic yourself (rearrange and expand the code as necessary to clean up memory etc):

// When need to use IntPtr as we are allocating the native memory.
[DllImport("yourdll.dll", CharSet = Ansi)]
public IntPtr find([In] IntPtr args);

IntPtr []allocatednativestrings;
IntPtr args = AllocateAnsiIntPtrArrayWithSentinel( new string[] { "Tool", "Sachin", "192.168.1.1", "3", "400"}, out allocatednativestrings);

IntPtr opaqueresult = find(args);  // call the function in that DLL.

// If "find" DOESN'T hold onto the pointers passed into it, then you can "free"
// the memory just after you make the call....otherwise you can't...you then
// have to decide how/who has responsibility for freeing that memory and when.

// Free the "strings", and the memory containing the pointers
FreeAnsiIntPtrArrayWithSentinel(args, allocatednativestrings);

(Note: I am only using this bit of hacky code, as it was mentioned in a comment above...
http://www.codeproject.com/Articles/17450/Marshal-an-Array-of-Zero-Terminated-Strings-or-Str ....
the crucial thing is to make sure you "free" the memory properly, when you are finished with it....
this can be done neater...just giving you enough to get the idea) 

public static IntPtr AllocateAnsiIntPtrArrayWithSentinel(string[] InputStrArray, out IntPtr[] InPointers)
{
    int size = InputStrArray.Length + 1; // +1 for NULL sentinel

    //build array of pointers to string
    InPointers = new IntPtr[size];

    int dim = IntPtr.Size * size;

    IntPtr rRoot = Marshal.AllocCoTaskMem(dim);

    int i = 0;
    foreach(string arg in args)
    {
        if (arg == null)
        {
            System.Diagnostics.Debug.Assert(false, "this code needs changing to support NULL arguments");
        }

        InPointers[i++] = Marshal.StringToCoTaskMemAnsi(arg);
    }

    // The NULL sentinel...don't need to do this as already initialized to null...
    // but just making it clearer for you.
    InPointers[size-1] = IntPtr.Zero;

    //copy the array of pointers
    Marshal.Copy(InPointers, 0, rRoot, size);
    return rRoot;
} 

public static void FreeAnsiIntPtrArrayWithSentinel(IntPtr args, IntPtr[] intptrs)
{
    foreach(IntPtr ptr in intptrs)
    {
        if (ptr != IntPtr.Zero) // we need to avoid the sentinel
            Marshal.FreeCoTaskMem(ptr); // free the mem allocated for the string
    }

    // free the memory that contained the list of string pointers and sentinel
    Marshal.FreeCoTaskMem(args);
}

Upvotes: 5

Related Questions