Pergunta

[EDIT] Eu mudei a fonte como sugerido por Stephen Martin (em negrito). E acrescentou o C ++ código fonte também.

Eu gostaria de chamar uma função não gerenciada em uma dll auto-escrito C ++. Esta biblioteca lê memória compartilhada da máquina para informações de estado de um software de terceiros. Desde há um par de valores, eu gostaria de retornar os valores em um struct. No entanto, no interior da estrutura não são char [] (matrizes de carvão animal com um tamanho fixo). Eu agora tentar receber esse struct da chamada dll como esta:

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

Vou postar o código do c ++ dll bem, eu tenho certeza que há mais para caçar. O STATUS_DATA estrutura original tem uma série de quatro instâncias do SYSTEM_CHARACTERISTICS struct struct e dentro de que há char[]s, que não estão a ser enchidas (ainda), resultando em um ponteiro má. É por isso que eu estou tentando extrair um subconjunto do primeiro item SYSTEM_CHARACTERISTICS em 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;
}

Agora eu estou recebendo um struct output vazio eo ist valor de retorno não 0 como pretendido. É, antes, um número mudando com sete dígitos, o que me deixa intrigado ... Tem eu errei na dll? Se eu fizer o executável código não gerenciado e depurá-lo, eu posso ver, que output está sendo preenchido com os valores apropriados.

Foi útil?

Solução

Ao retornar informações em um struct o método padrão é passar um ponteiro para um struct como um parâmetro do método. O método preenche os membros da estrutura e, em seguida, retorna um código de estado (ou booleana) de algum tipo. Então você provavelmente vai querer mudar a sua C ++ método para tomar um SYSTEM_OUTPUT * e retornar 0 para sucesso ou um código de erro:

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

Outras dicas

  1. Verifique se o seu campo ReadyForConnect não foi preenchido até 4 bytes. No meu projeto acabou tudo short int (2 bytes) campos foram preenchidos com bytes fictícios para 4 bytes em DLL não gerenciado. Se esse é o problema, você deve marshall a estrutura desta maneira:
    [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 as cordas são ANSI nulos encerrado cordas você pode anotar-los como:
  [MarshalAs(UnmanagedType.LPStr)]                   public String VersionStr;

Você não está realmente empacotamento quaisquer dados para o lado gerenciado. Quando você declarar output no lado gerenciado, ele de valor padrão é null. Em seguida, no lado não gerenciado, você nunca alocar qualquer memória para output. Você deve alocar alguma memória não gerenciado, passar o ponteiro para que a memória de sua função de dll, em seguida, organizar o ponteiro para essa memória para o struct:

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

Seu problema poderia ser um problema com a diferença entre o embalagem estratégias . Eu atualizei a definição struct.

EDIT:. Estou reescrevendo esta resposta inteira

Eu levei todos tanto o seu C ++ e C # código, ele caiu em uma solução e ele correu - e tudo funciona para mim. Eu não tinha o seu específico coisas mapeamento de memória para que eu simulado através do preenchimento pBuf com alguns dados falsos, e tudo torna-lo de volta fina; tanto o valor de retorno e a estrutura de saída estão corretos.

Poderia algo estar errado com as configurações do projeto? Isso parece bobagem, mas você mencionou execução e depuração do código unamnaged; você está construindo um direito dll?

O que você está tentando fazer é possível, mas eu acho que você está resolvendo o problema errado.

Por que não ler o arquivo de memória mapeada direta de C #? Dê uma olhada na Winterdom.IO.FileMap

Eu usei-o e ele funciona muito bem.

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

Com que você não está terminado -. Você ainda tem que converter um buffer de bytes para uma estrutura, mas tudo o que você está no lado administrado e será mais fácil

que alocada a memória para a estrutura? Você não pode excluir memória nativa do heap gerenciado. De um modo geral a DLL nativa deve alocar na pilha COM se esperar que o chamador para liberar a memória, ou retornar uma interface callback como IMalloc para liberar a memória retornando. Isso significa que você precisa receber o endereço de memória resultado como IntPtr e usar System.Runtime.InteropServices.Marshal para copiar dados de nativo da heap gerenciado (pode ser a de uma estrutura) antes de liberar a memória.

Editar para a assinatura função de atualização: usar extern int getStatus (ref SYSTEM_OUTPUT saída) public static; Você não está alocando sobre a OCM pilha na função nativa, tão fora é desnecessário.

Você já pensou em adicionando um C ++ / CLI montagem para o seu projeto? Isso é uma maneira extremamente fácil e poderosa para preencher a lacuna entre código gerenciado e não gerenciado. Eu usá-lo bastante a mim mesmo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top