перебирая память, выделенную с помощью Marshal.AllocHGlobal () - PullRequest
5 голосов
/ 02 апреля 2012

У меня есть сторонняя библиотека C, один из ее экспортированных методов выглядит следующим образом:

#define MAX_INDEX 8
int GetStuff(IN char* index[MAX_INDEX], OUT char* buf, IN size_t size);

Первый аргумент заполняется указателями на аргумент buf, в котором хранятся определенные строки. Размер указывает, как долго каждая строка должна находиться в буфере буфера. Мой метод C # P / Invoke для этого в настоящее время выглядит так:

[DllImport("Path/To/Dll", CharSet = CharSet.Ansi)]
private static extern int GetStuff(IntPtr indecies, IntPtr buf, Int32 size);

Метод C # P / Invoke является закрытым, потому что я обертываю его функциональность в общедоступный метод "getter", который обрабатывает выделение / освобождение памяти для вызывающей стороны. Когда я использую этот метод в C ++, итерации на самом деле довольно просты. Я просто делаю что-то вроде:

char* pIndecies[MAX_INDEX];
char* pBuffer = new char[MAX_INDEX * (256 + 1)]; // +1 for terminating NULL

GetStuff(pIndecies, pBuffer, 256);

// iterate over the items
for(int i(0); i < MAX_INDEX; i++) {
    if(pIndecies[i]) {
        std::cout << "String for index: " << i << " " << pIndecies[i] << std::endl;
    }
}

Из-за того, как они используются в C ++, я решил, что, вероятно, мне следует использовать объекты IntPtr и просто выделить память, которая мне понадобится из кучи, вызвать собственный код и выполнить итерацию по нему так же, как в C ++. , Затем я вспомнил, что символы в C # - это символы Unicode, а не символы ASCII. Будет ли итерация в C # работать так же, даже если я поместил ее в небезопасный блок кода? Моей первой мыслью было сделать следующее:

IntPtr pIndecies = Marshal.AllocHGlobal(MAX_INDEX * 4); // the size of a 32-pointer
IntPtr pBuffer = Marshal.AllocHGlobal(MAX_INDEX * (256 + 1)); // should be the same
NativeMethods.GetStuff(pIndecies, pBuffer, 256);

unsafe {
    char* pCStrings = (char*)pIndecies.ToPointer();
    for(int i = 0; i < MAX_INDEX; i++) {
        if(pCStrings[i])
            string s = pCStrings[i];
    }
}

Тогда мой вопрос: «Как мне повторять то, что возвращает этот метод нативного кода?» Это правильный путь для выполнения этой функции? Должен ли я использовать объект StringBuilder для второго аргумента? Одна ограничивающая проблема заключается в том, что первый аргумент является отображением структуры 1: 1 позади метода GetStuff (). Значение каждого индекса имеет решающее значение для понимания того, на что вы смотрите во втором аргументе буфера.

Я ценю любые предложения.

Спасибо, Andy

1 Ответ

3 голосов
/ 02 апреля 2012

Я думаю, что вы на правильном пути, но я бы сделал это без использования небезопасного кода. Как это:

[DllImport("Path/To/Dll", CharSet = CharSet.Ansi)]
private static extern int GetStuff(IntPtr[] index, IntPtr buf, Int32 size);
....
IntPtr[] index = new IntPtr[MAX_INDEX];
IntPtr pBuffer = Marshal.AllocHGlobal(MAX_INDEX * 256 + 1);
try
{
    int res = NativeMethods.GetStuff(index, pBuffer, 256);
    // check res for errors?
    foreach (IntPtr item in index)
    {
        if (item != IntPtr.Zero)
            string s = Marshal.PtrToStrAnsi(item);
    }
} 
finally
{
    Marshal.FreeHGlobal(pBuffer);
}

Это довольно прямой перевод вашей версии C ++.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...