Вызов неуправляемого кода из C # - возвращение структуры с массивами - PullRequest
7 голосов
/ 30 октября 2009

[РЕДАКТИРОВАТЬ] Я изменил источник в соответствии с предложением Стивена Мартина (выделено жирным шрифтом). И также добавил исходный код C ++.

Я бы хотел вызвать неуправляемую функцию в самописной DLL C ++. Эта библиотека считывает общую память устройства для получения информации о состоянии стороннего программного обеспечения. Поскольку есть несколько значений, я хотел бы вернуть значения в структуре. Тем не менее, в структуре есть char [] (массивы char с фиксированным размером). Я сейчас пытаюсь получить эту структуру из вызова DLL, как это:

[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_OUTPUT
{
    UInt16 ReadyForConnect;        

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    String VersionStr;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
    String NameOfFile;    
    // actually more of those
}

public partial class Form1 : Form
{
    public SYSTEM_OUTPUT output;

    [DllImport("testeshm.dll", EntryPoint="getStatus")]
    public extern static int getStatus(out SYSTEM_OUTPUT output);

    public Form1()
    {
        InitializeComponent();           

    }

    private void ReadSharedMem_Click(object sender, EventArgs e)
    {
        try
        {
            label1.Text = getStatus(out output).ToString();
        }
        catch (AccessViolationException ave)
        {
            label1.Text = ave.Message;
        }
    }
}

Я также выложу код из dll c ++, я уверен, что есть еще, что искать. Исходная структура STATUS_DATA имеет массив из четырех экземпляров структуры SYSTEM_CHARACTERISTICS, и в этой структуре есть char[] с, которые еще не заполнены (что приводит к неправильному указателю). Вот почему я пытаюсь извлечь подмножество первого элемента SYSTEM_CHARACTERISTICS в STATUS_DATA.

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include <iostream>
#if defined(_MSC_VER)
#include <windows.h>
#define DLL extern "C" __declspec(dllexport)
#else
#define DLL
#endif

using namespace std;

enum { SYSID_LEN = 1024, VERS_LEN = 128, SCENE_LEN = 1024 };
enum { MAX_ENGINES = 4 };

struct SYSTEM_CHARACTERISTICS
{
    unsigned short  ReadyForConnect;
    char            VizVersionStr[VERS_LEN];
    char            NameOfFile[SCENE_LEN];

    char            Unimplemented[SCENE_LEN]; // not implemented yet, resulting to bad pointer, which I want to exclude (reason to have SYSTEM_OUTPUT)
};

struct SYSTEM_OUTPUT
{
    unsigned short  ReadyForConnect;        
    char            VizVersionStr[VERS_LEN];
    char            NameOfFile[SCENE_LEN];
};

struct STATUS_DATA
{
    SYSTEM_CHARACTERISTICS engine[MAX_ENGINES];
};


TCHAR szName[]=TEXT("E_STATUS");


DLL int getStatus(SYSTEM_OUTPUT* output)
{
    HANDLE hMapFile;
    STATUS_DATA* pBuf;

    hMapFile = OpenFileMapping(
        FILE_MAP_READ,          // read access
        FALSE,                  // do not inherit the name
        szName);                // name of mapping object 

    if (hMapFile == NULL) 
    { 
        _tprintf(TEXT("Could not open file mapping object (%d).\n"), 
            GetLastError());
        return -2;

    } 

    pBuf = (STATUS_DATA*) MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);                                          

    if (pBuf == NULL) 
    { 
        _tprintf(TEXT("Could not map view of file (%d).\n"), 
            GetLastError()); 

        CloseHandle(hMapFile);  
        return -1;

    }

    output->ReadyForConnect = pBuf->engine[0].ReadyForConnect;              
    memcpy(output->VizVersionStr, pBuf->engine[0].VizVersionStr, sizeof(pBuf->engine[0].VizVersionStr));
    memcpy(output->NameOfFile, pBuf->engine[0].NameOfFile, sizeof(pBuf->engine[0].NameOfFile));

    CloseHandle(hMapFile);
    UnmapViewOfFile(pBuf);  

    return 0;
}

Теперь я получаю пустую структуру output, и возвращаемое значение не равно 0, как предполагалось. Это скорее изменяющееся число с семью цифрами, которое ставит меня в тупик ... Я что-то напутал в dll? Если я сделаю неуправляемый код исполняемым и отладлю его, я увижу, что output заполняется соответствующими значениями.

Ответы [ 7 ]

3 голосов
/ 23 ноября 2009
  1. Убедитесь, что поле ReadyForConnect не заполнено до 4 байтов. В моем проекте оказалось, что все короткие поля int (2 байта) были заполнены фиктивными байтами до 4 байтов в неуправляемой DLL. Если это проблема, вы должны упорядочить структуру следующим образом:
    [StructLayout(LayoutKind.Sequential)] 
    public struct SYSTEM_OUTPUT 
    {     
       [MarshalAs(UnmanagedType.I2)] 
       UInt16 ReadyForConnect;
       [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.I1, SizeConst=2)]
       byte[] aligment;          // 2 byte aligment up to 4 bytes margin
       [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]    
       String VersionStr;    
       [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]    
       String NameOfFile;        // ...
    }
  1. Если строки являются строками ANSI с нулевым символом в конце, вы можете пометить их как:
  [MarshalAs(UnmanagedType.LPStr)]                   public String VersionStr;
3 голосов
/ 01 ноября 2009

При возврате информации в структуре стандартным методом является передача указателя на структуру в качестве параметра метода. Метод заполняет члены структуры и затем возвращает код состояния (или логический) некоторого вида. Таким образом, вы, вероятно, захотите изменить свой метод C ++, чтобы взять SYSTEM_OUTPUT * и вернуть 0 для успеха или какой-либо код ошибки:

public partial class Form1 : Form
{
    public SYSTEM_OUTPUT output;

    [DllImport("testeshm.dll", EntryPoint="getStatus")]
    public extern static int getStatus(out SYSTEM_OUTPUT output);

    public Form1()
    {
        InitializeComponent();           
    }

    private void ReadSharedMem_Click(object sender, EventArgs e)
    {
        try
        {
            if(getStatus(out output) != 0)
            {
                //Do something about error.
            }
        }
        catch (AccessViolationException ave)
        {
            label1.Text = ave.Message;
        }
    }
}
2 голосов
/ 19 ноября 2009

На самом деле вы не направляете какие-либо данные на управляемую сторону. Когда вы объявляете output на управляемой стороне, его значением по умолчанию является null. Затем, на неуправляемой стороне, вы никогда не выделяете память для output. Вы должны выделить некоторую неуправляемую память, передать указатель на эту память в вашу функцию dll, а затем направить указатель на эту память в вашу структуру:

[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)]
public struct SYSTEM_OUTPUT
{
    UInt16 ReadyForConnect;        

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    String VersionStr;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
    String NameOfFile;    
    // actually more of those
}

public partial class Form1 : Form
{
    public SYSTEM_OUTPUT output;

    [DllImport("testeshm.dll", EntryPoint="getStatus")]
    public extern static int getStatus(IntPtr output);

    public Form1()
    {
        InitializeComponent();           

    }

    private void ReadSharedMem_Click(object sender, EventArgs e)
    {
        IntPtr ptr;
        try
        {
            ptr = Marshall.AllocHGlobal(Marshall.SizeOf(typeof(SYSTEM_OUTPUT)));
            int ret = getStatus(ptr);

            if(ret == 0)
            {
                output = (SYSTEM_OUTPUT)Marshal.PtrToStructure(ptr, typeof(SYSTEM_OUTPUT));
            }

        //do something with output

            label1.Text = ret;
        }
        catch (AccessViolationException ave)
        {
            label1.Text = ave.Message;
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);  //make sure to free the memory
        }
    }
}

Edit:

Ваша проблема может быть связана с разницей между упаковкой стратегий. Я обновил определение структуры.

1 голос
/ 24 ноября 2009

То, что вы пытаетесь сделать, возможно, но я думаю, что вы решаете не ту проблему.

Почему бы не прочитать файл, отображенный в памяти, непосредственно из C #? Взгляните на Winterdom.IO.FileMap

Я использовал его, и он отлично работает.

MemoryMappedFile file = MemoryMappedFile.Open(FileMapRead, name);
using (Stream stream = memoryMappedFile.MapView(MapAccess.FileMapAllAccess, 0, length))
{
    // here read the information that you need   
}

С этим вы еще не закончили - вам все еще нужно преобразовать байтовый буфер в структуру, но вы все на управляемой стороне, и это будет проще.

1 голос
/ 23 ноября 2009

РЕДАКТИРОВАТЬ: я переписываю весь этот ответ.

Я взял весь ваш код на C ++ и C #, бросил его в решение и запустил - и все у меня работает. У меня не было ваших конкретных картографических данных, поэтому я смоделировал их, заполнив pBuf некоторыми поддельными данными, и все вернулось обратно; и возвращаемое значение, и структура вывода верны.

Может что-то не так с настройками вашего проекта? Это звучит глупо, но вы упомянули о запуске и отладке неустановленного кода; вы строите dll верно?

0 голосов
/ 21 ноября 2009

Рассматривали ли вы добавление сборки C ++ / CLI в свой проект? Это чрезвычайно простой и мощный способ преодолеть разрыв между управляемым и неуправляемым кодом. Я использую это довольно много сам.

0 голосов
/ 02 ноября 2009

кто выделил память для структуры? Вы не можете удалить собственную память из управляемой кучи. Вообще говоря, нативная DLL должна размещаться в куче COM, если она ожидает, что вызывающая сторона освободит память, или вернет интерфейс обратного вызова, такой как IMalloc, чтобы освободить возвращаемую память. Это означает, что вам нужно получить адрес памяти результатов как IntPtr и использовать System.Runtime.InteropServices.Marshal для копирования данных из собственной в управляемую кучу (может быть в структуру) перед освобождением памяти.

Изменить для обновленной сигнатуры функции: использовать public static extern int getStatus (ссылка на вывод SYSTEM_OUTPUT); Вы не размещаете в куче COM в нативной функции, поэтому выход не требуется.

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