简体   繁体   中英

Passing an array of structs to a function as a parameter

I know this question has come up a lot of time in stackoverflow and I am posting this question after checking all the questions suggested by stackoverflow when I started this question.

Basically, I want to achieve this. Big picture: Pass an array of structs from c++ dll to ac# application. The catch here is that, the size of the array can only be determined by the c++ dll. Now I only know that getting an array of anything from c++ to c# is possible with the out parameter as a function argument. And yes, I can pass a ref to a c++ function as an out parameter whose size is not known but is initialized to NULL. But I am not sure if this is right or if it will work! And another way is, to get the array of structs as a return value, for which I didn't find any example in c#.

So what I did: Simplified this problem into a c++ problem :

I tried some sample examples to return an array of structs from a function to my main(), from a c++ dll to a test c++ application. --> Works fine. So as a return value, an array of structs works fine. Obviously, because I do not have to initialize my array of structs with the exact size, in the beginning before calling my function to populate the array!

But the catch is, I need to implement it in a way which will be feasible to be received by the c# end! So, as an out parameter to the function, and not as a return value (if anybody knows how to receive an array of structs from c++ dll to c#, please help!).

All the questions in stackoverflow or in google about passing an array of structs to a function have their size determined previously. But in my case, the function will only be able to decide the size. I do not know the size of the array prior to calling the function. And, I am really trying to complete the tasks in a single function call and hence, I cannot have 2 APIs, one for computing the size and one for getting the array. It would be a repetitive task since the same functionality has to be executed twice, once to only get the size and another time to get the actual array, which is not feasible!

I have a sample code. If anyone can help, it would be really appreciated.

// structsEx.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <iostream>
#include <conio.h>
#include <vector>
#include <string>

using namespace std;
using std::vector;
using std::string;

struct example
{
    char* name; // char* or const char*. But not sure if I can have an
                // std::string.
    int age;
    char* company;
};

void passStructsRef(struct example** ex)  // Again, not sure if I can have 
                                          // std::array or std::vector since 
                                          // this will be the API I expose 
                                          // to C# side.
{
    vector<string> names = {"AAA", "BBB", "CCC", "DDD", "EEE"};
    vector<int> ages = {25, 26, 27, 28, 29, 30};
    vector<string> companies = { "AAA1", "BBB2", "CCC3", "DDD4", "EEE5" };

    *ex = new example[5];  // I think this is a problem. It only initializes 
                           // the first element and hence, I get only the 
                           // first item.
    for (int i = 0; i < 5; i++)
    {
        ex[i] = new example();  // Is this right? Not sure.. 
        ex[i]->age = ages[i];
        ex[i]->name = _strdup(names[i].c_str());
        ex[i]->company = _strdup(companies[i].c_str());
    }

    cout << "2 in function" << endl;
    for (int i = 0; i < 5; i++)
    {
        cout << ex[i]->name << "\t" << ex[i]->age << "\t" << ex[i]->company << endl;
    }
}

int main()
{
    example ex;
    ex.age = 30;
    ex.name = "AEIOU";
    ex.company = "ABCDE";

    cout << "1" << endl;
    cout << ex.name << "\t" << ex.age << "\t" << ex.company << endl;

    cout << "2" << endl;
    example *ex2 = nullptr;
    passStructsRef(&ex2);

    for (int i = 0; i < 5; i++)
    {
        cout << ex2[i].name << "\t" << ex2[i].age << "\t" << ex2[i].company << endl;
    }

    _getch();
    return 0;
}

Following the comment, I think I have to give a bit of comments on my code. I am not sure if I can use STL arrays or vectors or std::string in my code which I am going to expose to C# application. Which is why I am using new operator here. Next question which might arise is where is delete? Well, I have to have another API calling delete to delete the memory allocated for the structure. Which will be called by my C# application when it has successfully received all the data I have returned and processed them. That is not a problem. But sending data to C# is a problem which has led me to write this sample C++ test application.

There is no big syntax or any special code to write to send data to C#. If it works in C++, it has to work in C#, if the parameters and APIs are exported properly. Which is why I am trying to get my C++ side perfect.

Hope this helps. Thanks a lot in advance.

There're actually two questions here.

One is how to return array of structures.

The classical way is this:

C++:

int getStructsLength();
int getStructs( int bufferLength, struct example* buffer ); // Returns the count of structures actually written

C#

static extern int getStructsLength();
static extern int getStructs( int bufferLength, [Out, MarshalAs( UnmanagedType.LPArray, SizeParamIndex = 0 )] example[] buffer );

Another one is how to return strings in these structures.

If you don't have too much data or too many calls and running on Windows, one simple way is change const char* into BSTR , and adjust C++ code accordingly. These are Unicode strings every language knows how to properly allocate/deallocate across DLL boundaries; the only downside is they're relatively slow.

Another method, more complex, declare the following field in your C# structure:

 [MarshalAs(UnmanagedType.LPStr)] public string name;

In C++, replace strdup with CoTaskMemAlloc and strcpy calls (don't forget about terminating '\\0' when allocating). This way the interop will free memory used by these strings after making .NET strings from the pointers.

Another way, most performant, is do manual marshalling, ie declare the following fields in your C# structure:

 public IntPtr name;

In C++, change the code to just write c_str() there without making copies.

In C#, use Marshal.PtrToStringAnsi to convert them to .NET strings. But beware if your C++ will change any of these strings before you're done with your custom marshalling, your app will crash.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM