문제

[편집] Stephen Martin이 제안한 대로 소스를 변경했습니다(굵게 강조 표시).그리고 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;
        }
    }
}

C++ dll의 코드도 게시할 예정이며, 찾아볼 내용이 더 있을 것이라고 확신합니다.원래 구조체 STATUS_DATA 구조체의 4개 인스턴스 배열이 있습니다. 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 구조체 및 반환 값이 의도한 대로 0이 아닙니다.오히려 7자리 숫자로 바뀌는 것 같아서 헷갈리네요...내가 DLL을 망쳤나요?비관리 코드를 실행 가능하게 만들고 디버깅하면 알 수 있습니다. output 적절한 값으로 채워지고 있습니다.

도움이 되었습니까?

해결책

구조물로 정보를 반환 할 때 표준 방법은 방법의 매개 변수로서 구조물에 포인터를 전달하는 것입니다. 이 메소드는 구조물 멤버를 채운 다음 어떤 종류의 상태 코드 (또는 부울)를 반환합니다. 따라서 C ++ 메소드를 변경하여 System_output*를 가져 와서 성공 또는 일부 오류 코드를 반환하려고합니다.

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

다른 팁

  1. ReadyForConnect 필드가 최대 4 바이트까지 채워지지 않도록하십시오. 내 프로젝트에서는 모든 짧은 int (2 바이트) 필드가 관리되지 않은 DLL에서 더미 바이트로 4 바이트로 채워졌습니다. 그것이 문제라면, 당신은 이런 식으로 구조물을 마샬링해야합니다.
    [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 null 종료 문자열 인 경우 다음과 같이 주석을 달 수 있습니다.
  [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.파일 맵

나는 그것을 사용했고 잘 작동합니다.

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

아직 끝나지 않았습니다. 여전히 바이트 버퍼를 구조체로 변환해야 하지만 모두 관리 측에 있으므로 더 쉬울 것입니다.

누가 구조에 대한 메모리를 할당 했습니까? 관리 된 힙에서 기본 메모리를 삭제할 수 없습니다. 일반적으로 기본 DLL은 발신자가 메모리를 제거하거나 IMALLOC와 같은 콜백 인터페이스를 반환하여 리턴 메모리를 제거 할 것으로 예상되는 경우 COM 힙에 할당해야합니다. 즉, 결과 메모리 주소를 intptr로 수신하고 System.Runtime.interOpservices.marshal을 사용하여 메모리를 제거하기 전에 네이티브에서 관리 힙으로 데이터를 복사해야합니다.

업데이트 된 기능 서명 편집 : 공개 정적 외부 int getstatus (ref system_output output)를 사용합니다. 기본 기능에서 COM 힙에 할당되지 않으므로 불필요합니다.

프로젝트에 C ++/CLI 어셈블리를 추가하는 것을 고려해 보셨습니까? 그것은 관리되는 코드와 관리되지 않는 코드 사이의 격차를 해소하는 매우 쉽고 강력한 방법입니다. 나는 그것을 꽤 많이 사용합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top