1

I need to use a native dll struct in my application. struct da_i2k_input_file_info in the dll .h file is

struct DA_I2K_EXPORT_API da_i2k_input_file_info {
  const WDCHAR *  image_path;
  const WDCHAR ** image_files;
  int             num_images;
};

and this is what user code might look like if written in C++

in_file_info.num_images = 3;
in_file_info.image_files = new const WDCHAR*[in_file_info.num_images];
in_file_info.image_files[0] = WSTR("IMG_8670.JPG");
in_file_info.image_files[1] = WSTR("IMG_8671.JPG");
in_file_info.image_files[2] = WSTR("IMG_8672.JPG");

But this C# code

[StructLayout(LayoutKind.Sequential)]
public struct da_i2k_input_file_info
{
    [MarshalAs(UnmanagedType.LPTStr)]
    public string image_path;
    public IntPtr image_files;
    public int num_images;
}

var openFileDialog = new OpenFileDialog{Multiselect = true};
da_i2k_input_file_info in_file_info;
in_file_info.image_path = null; // use full path in .image_files
in_file_info.num_images = openFileDialog.FileNames.Length;
in_file_info.image_files = openFileDialog.FileNames;

Causes this error

Cannot implicitly convert type 'string[]' to 'System.IntPtr'

Casting openFileDialog.FileNames as IntPtr does not help. How can I load in_file_info.image_files from openFileDialog.FileNames?

Edit: OP cross posted this here

tomfanning
  • 9,552
  • 4
  • 50
  • 78
jacknad
  • 13,483
  • 40
  • 124
  • 194

2 Answers2

1

I believe you need to change you struct to be as follows:

[StructLayout(LayoutKind.Sequential)]
public struct da_i2k_input_file_info
{
    [MarshalAs(UnmanagedType.LPTStr)]
    public string image_path;
    [MarshalAs(UnmanagedType.LPArray)]
    public string[] image_files;
    public int num_images;
}
Nick
  • 5,875
  • 1
  • 27
  • 38
  • 1
    It does build with that change but breaks during run. Changing `public IntPtr image_files` to `[MarshalAs(UnmanagedType.LPArray)] public string[] image_files` causes `System.TypeLoadException: Cannot marshal field 'image_files' of type 'da_i2k_input_file_info': Invalid managed/unmanaged type combination (Arrays fields must be paired with ByValArray or SafeArray)`. I also tried changing LPArray to ByValArray and SafeArray but that didn't work either. I believe image_files needs to be an IntPtr. – jacknad Jan 12 '12 at 20:48
1

A working answer was posted here.

Code follows.
(included by tomfanning)

    private static void DoTest()
    {
        var openFileDialog = new OpenFileDialog { Multiselect = true };
        da_i2k_input_file_info in_file_info;

        openFileDialog.ShowDialog();

        in_file_info.image_path = null; // use full path in .image_files
        in_file_info.num_images = openFileDialog.FileNames.Length;

        // Create an array of IntPtrs.
        IntPtr[] image_files_array = new IntPtr[in_file_info.num_images];

        // Each IntPtr array element will point to a copy of a
        // string element in the openFileDialog.FileNames array.
        for (int i = 0; i < openFileDialog.FileNames.Length; i++)
        {
            image_files_array[i] = Marshal.StringToCoTaskMemUni(openFileDialog.FileNames[i]);
        }

        // In order to obtain the address of the IntPtr array, 
        // we must fix it in memory. We do this using GCHandle.
        GCHandle gch = GCHandle.Alloc(image_files_array, GCHandleType.Pinned);
        // pimage_files will point to the head of the IntPtr array.
        IntPtr pimage_files = gch.AddrOfPinnedObject();

        // Set pimage_files as the value of the "image_files" field/
        in_file_info.image_files = pimage_files;

        // Call a Test API.
        TestStructure(in_file_info);

        // After the API is called, free the GCHandle.
        gch.Free();

        // Free each string contained in the IntPtr array.
        for (int i = 0; i < openFileDialog.FileNames.Length; i++)
        {
            Marshal.FreeCoTaskMem(image_files_array[i]);
        }
    }
tomfanning
  • 9,552
  • 4
  • 50
  • 78
jacknad
  • 13,483
  • 40
  • 124
  • 194