Domanda

[EDIT] Ho cambiato la fonte come suggerito da Stephen Martin (evidenziato in grassetto). E ha aggiunto anche il codice sorgente C ++.

Vorrei chiamare una funzione non gestita in una DLL C ++ scritta da sola. Questa libreria legge la memoria condivisa della macchina per le informazioni sullo stato di un software di terze parti. Dato che ci sono un paio di valori, vorrei restituire i valori in una struttura. Tuttavia, all'interno della struttura ci sono char [] (array di caratteri a dimensione fissa). Ora provo a ricevere quella struttura dalla chiamata dll in questo modo:

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

Pubblicherò anche il codice dalla DLL c ++, sono sicuro che ci sarà altro da cercare. La struttura originale STATUS_DATA ha una matrice di quattro istanze della struttura SYSTEM_CHARACTERISTICS e all'interno di quella struttura ci sono char [] , che non vengono riempito (ancora), risultando in un puntatore errato. Ecco perché sto cercando di estrarre un sottoinsieme del primo elemento SYSTEM_CHARACTERISTICS in 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;
}

Ora sto ottenendo uno output vuoto e il valore di ritorno non è 0 come previsto. È piuttosto un numero variabile con sette cifre, che mi lascia perplesso ... Ho fatto un casino nella DLL? Se rendo eseguibile il codice non gestito e lo eseguo il debug, vedo che output viene riempito con i valori appropriati.

È stato utile?

Soluzione

Quando si restituiscono informazioni in una struttura, il metodo standard consiste nel passare un puntatore a una struttura come parametro del metodo. Il metodo riempie i membri struct e quindi restituisce un codice di stato (o booleano) di qualche tipo. Quindi probabilmente vuoi cambiare il tuo metodo C ++ per prendere un SYSTEM_OUTPUT * e restituire 0 per il successo o un codice di errore:

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

Altri suggerimenti

  1. Assicurarsi che il campo ReadyForConnect non sia riempito fino a 4 byte. Nel mio progetto è risultato che tutti i campi int (2 byte) brevi sono stati riempiti con byte fittizi a 4 byte in DLL non gestita. In questo caso, dovresti eseguire il marshalling della struttura in questo modo:
    [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. Se le stringhe sono stringhe ANSI con terminazione nulla, è possibile annotarle come:
  [MarshalAs(UnmanagedType.LPStr)]                   public String VersionStr;

In realtà non stai trasferendo dati sul lato gestito. Quando dichiari output sul lato gestito, il suo valore predefinito è null . Quindi, dal lato non gestito, non allocare mai memoria per output . Dovresti allocare un po 'di memoria non gestita, passare il puntatore a quella memoria alla tua funzione dll, quindi eseguire il marshalling del puntatore per quella memoria sulla tua struttura:

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

Modifica:

Il tuo problema potrebbe essere un problema con la differenza tra strategie di imballaggio . Ho aggiornato la definizione della struttura.

EDIT: sto riscrivendo l'intera risposta.

Ho preso tutto il tuo codice C ++ e C #, l'ho lasciato cadere in una soluzione e l'ho eseguito - e tutto funziona per me. Non avevo il tuo specifico materiale di mappatura della memoria, quindi l'ho simulato riempiendo pBuf di alcuni dati falsi, e tutto è tornato a posto; sia il valore di ritorno che la struttura di output sono corretti.

Potrebbe esserci qualcosa che non va nelle impostazioni del tuo progetto? Sembra sciocco, ma hai menzionato l'esecuzione e il debug del codice non gestito; stai costruendo una dll giusto?

Cosa stai cercando di fare è possibile, ma penso che tu stia risolvendo il problema sbagliato.

Perché non leggere il file mappato in memoria direttamente da C #? Dai un'occhiata a Winterdom.IO.FileMap

L'ho usato e funziona benissimo.

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

Con ciò non hai finito - devi ancora convertire un buffer di byte in uno struct, ma sei tutto sul lato gestito e sarà più facile.

chi ha allocato la memoria per la struttura? Non è possibile eliminare la memoria nativa dall'heap gestito. In generale, la DLL nativa dovrebbe allocare sull'heap COM se si aspetta che il chiamante liberi la memoria o restituisca un'interfaccia di callback come IMalloc per liberare la memoria di ritorno. Ciò significa che è necessario ricevere l'indirizzo di memoria dei risultati come IntPtr e utilizzare System.Runtime.InteropServices.Marshal per copiare i dati dall'heap nativo a gestito (potrebbe essere una struttura) prima di liberare la memoria.

Modifica per la firma della funzione aggiornata: usa public stat extern int getStatus (ref SYSTEM_OUTPUT output); Non si sta allocando sull'heap COM nella funzione nativa, quindi non è necessario.

Hai pensato di aggiungere un assembly C ++ / CLI al tuo progetto? È un modo estremamente semplice e potente per colmare il divario tra codice gestito e non gestito. Lo uso abbastanza da solo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top