Pregunta

[EDITAR] Cambié la fuente según lo sugerido por Stephen Martin (resaltado en negrita). Y agregó el código fuente de C ++ también.

Me gustaría llamar a una función no administrada en un dll C ++ auto-escrito. Esta biblioteca lee la memoria compartida de la máquina para obtener información sobre el estado de un software de terceros. Como hay un par de valores, me gustaría devolver los valores en una estructura. Sin embargo, dentro de la estructura hay char [] (matrices de char con un tamaño fijo). Ahora trato de recibir esa estructura de la llamada dll de esta manera:

[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;
        }
    }
}

También publicaré código de la c ++ dll, estoy seguro de que hay más para cazar. La estructura original STATUS_DATA tiene una matriz de cuatro instancias de la estructura SYSTEM_CHARACTERISTICS y dentro de esa estructura hay char [] s, que no están siendo lleno (todavía), lo que resulta en un mal puntero. Es por eso que estoy tratando de extraer un subconjunto del primer elemento SYSTEM_CHARACTERISTICS en 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;
}

Ahora obtengo una estructura output vacía y el valor de retorno no es 0 como se esperaba. Es más bien un número cambiante con siete dígitos, lo que me deja perplejo ... ¿Me he equivocado en el dll? Si hago que el código no administrado sea ejecutable y lo depure, puedo ver que output se está rellenando con los valores apropiados.

¿Fue útil?

Solución

Al devolver información en una estructura, el método estándar es pasar un puntero a una estructura como parámetro del método. El método completa los miembros de la estructura y luego devuelve un código de estado (o booleano) de algún tipo. Por lo tanto, es probable que desee cambiar su método C ++ para tomar SYSTEM_OUTPUT * y devolver 0 para el éxito o algún código de error:

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;
        }
    }
}

Otros consejos

  1. Asegúrese de que su campo ReadyForConnect no esté lleno hasta 4 bytes. En mi proyecto, resultó que todos los campos cortos int (2 bytes) estaban llenos de bytes ficticios a 4 bytes en una DLL no administrada. Si ese es el problema, debe ordenar la estructura de esta manera:
    [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. Si las cadenas son cadenas terminadas en nulo ANSI, puede anotarlas como:
  [MarshalAs(UnmanagedType.LPStr)]                   public String VersionStr;

En realidad no está ordenando ningún dato al lado administrado. Cuando declara output en el lado administrado, su valor predeterminado es null . Luego, en el lado no administrado, nunca asigna memoria para output . Debe asignar un poco de memoria no administrada, pasar el puntero a esa memoria a su función dll, luego ordenar el puntero de esa memoria a su estructura:

[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
        }
    }
}

Editar:

Su problema podría ser un problema con la diferencia entre estrategias de embalaje . He actualizado la definición de la estructura.

EDITAR: estoy reescribiendo toda esta respuesta.

Tomé todo su código C ++ y C #, lo coloqué en una solución y lo ejecuté, y todo funciona para mí. No tenía su material de mapeo de memoria específico, así que lo simulé llenando pBuf con algunos datos falsos, y todo vuelve a estar bien; tanto el valor de retorno como la estructura de salida son correctos.

¿Podría haber algún problema con la configuración de su proyecto? Esto suena tonto, pero mencionó ejecutar y depurar el código no modificado; estás construyendo un dll ¿verdad?

¿Qué estás tratando de hacer? Es posible, pero creo que estás resolviendo el problema incorrecto.

¿Por qué no leer el archivo mapeado de memoria directamente desde C #? Eche un vistazo a Winterdom.IO.FileMap

Lo he usado y funciona bien.

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

Con eso no has terminado, todavía tienes que convertir un búfer de bytes a una estructura, pero todos están en el lado administrado y será más fácil.

¿Quién asignó la memoria para la estructura? No puede eliminar la memoria nativa del montón administrado. En general, la DLL nativa debería asignarse en el montón COM si espera que la persona que llama libere la memoria, o devuelva una interfaz de devolución de llamada como IMalloc para liberar la memoria de retorno. Eso significa que debe recibir la dirección de memoria de resultados como IntPtr y usar System.Runtime.InteropServices.Marshal para copiar datos del montón nativo al administrado (puede estar en una estructura) antes de liberar la memoria.

Editar para la firma de función actualizada: use public static extern int getStatus (ref. salida SYSTEM_OUTPUT); No está asignando en el montón COM en la función nativa, por lo que no es necesario.

¿Ha considerado agregar un ensamblaje C ++ / CLI a su proyecto? Esa es una manera extremadamente fácil y poderosa de cerrar la brecha entre el código administrado y el no administrado. Yo mismo lo uso bastante.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top