Search code examples
c#c++arrayspinvoke

pinvoke - pass a string[] as out parameter


I couldn't get the below p/invoke code working, please help, thanks.

vertices below stays as null after the c++ call. I've tried to use IntPtr instead of string[], IntPtr stays as 0 after c++ call.

c++ code

extern "C"  __declspec(dllexport)
float compute_similarity(char** vertices)
{       
  vertices = new char*[2];
  vertices[0] = new char[3];
  vertices[1] = new char[3];

  strcpy(vertices[0], "he");
  strcpy(vertices[1], "ha");

  return 1.01;
}

c# code

[DllImport("demo.dll", EntryPoint = "compute_similarity", 
    CallingConvention =  CallingConvention.Cdecl,   CharSet = CharSet.Ansi)]
static extern float compute_similarity(out string[] vertices);  
//also tried 'static extern float compute_similarity(out IntPtr vertices);'  

public string Func()
{
  string[] vertices; //also tried 'IntPtr vertices'
  float sim = compute_similarity(out vertices); 
  //break point here vertices stays null(or 0 for IntPtr)
  return sim.ToString();
}

Solution

  • I don't think it can be done properly this way, because you have no way of specifying that P/Invoke must free the memory with delete[] calls after converting it to managed strings.

    However, with a SAFEARRAY of BSTRs and the MarshalAs attribute, you may have a fighting chance.

    extern "C"  __declspec(dllexport)
    float compute_similarity(SAFEARRAY** vertices)
    {
        SAFEARRAY *pArr = SafeArrayCreateVector(VT_BSTR, 0, 2);
        if(pArr != NULL)
        {
            LONG index = 0;
            BSTR bs = SysAllocString(L"he");
            SafeArrayPutElement(pArr, &index, bs);
            SysFreeString(bs);
            index = 1;
            bs = SysAllocString(L"ha");
            SafeArrayPutElement(pArr, &index, bs);
            SysFreeString(bs);
        }
        *vertices = pArr;
        return 1.01;
    }
    
    [DllImport("demo.dll", EntryPoint = "compute_similarity", CallingConvention =  CallingConvention.Cdecl)]
    static extern float compute_similarity(
        [Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BSTR)] out string[] vertices
    );  
    

    Of you may still use IntPtr (and do the marshaling manually on the C# side, while exporting a delete_strings function), but remember that your function must take its char** by reference, or it can't actually modify it.