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

StackOverflow https://stackoverflow.com/questions/1650763

Вопрос

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

Я бы хотел вызвать неуправляемую функцию в самописной C ++ dll.Эта библиотека считывает информацию о состоянии программного обеспечения сторонних производителей из общей памяти устройства.Поскольку есть пара значений, я бы хотел вернуть значения в структуре.Однако внутри структуры существуют 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[]s, которые не заполняются (пока), что приводит к неправильному указателю.Вот почему я пытаюсь извлечь подмножество первого 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 struct и возвращаемое значение не равно 0, как предполагалось.Это скорее меняющийся номер из семи цифр, который ставит меня в тупик...Я что-то напутал в библиотеке dll?Если я сделаю неуправляемый код исполняемым и отлажу его, я увижу, что output заполняется соответствующими значениями.

Это было полезно?

Решение

При возврате информации в структуру стандартным методом является передача указателя на структуру в качестве параметра метода. Метод заполняет члены структуры и затем возвращает код состояния (или логический) некоторого вида. Таким образом, вы, вероятно, захотите изменить свой метод 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;
        }
    }
}

Другие советы

<Ол>
  • Убедитесь, что поле 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 с нулевым символом в конце, вы можете пометить их как:
    2.   [MarshalAs(UnmanagedType.LPStr)]                   public String VersionStr;
      

    На самом деле вы не передаете какие-либо данные на управляемую сторону.Когда вы заявляете 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
            }
        }
    }
    

    Редактировать:

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

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

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

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

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

    Почему бы не прочитать файл сопоставления памяти непосредственно из 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   
    }
    

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

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

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top