Calling unmanaged code from C # - returning a structure with arrays

[EDIT] I changed the source as suggested by Stephen Martin (in bold). And added the C ++ source code.

I would like to call an unmanaged function in a self-signed C ++ dll. This library reads the general computer memory for information on the status of third-party software. Since there are several values, I would like to return the values ​​in a struct. However, inside the structure there is char [] (fixed-size char arrays). Now I am trying to get this structure from a dll, for example:

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

I will also send the code from the C ++ dll, I am sure there is more to track. The original STATUS_DATA structure has an array of four instances of struct SYSTEM_CHARACTERISTICS , and inside this structure there are char[] s that are not populated (yet), which leads to a bad pointer. This is why I am trying to extract a subset of the first SYSTEM_CHARACTERISTICS element 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; } 

Now I get an empty output structure, and the return value is not 0, as intended. It is rather a change in a seven-digit number, which leaves me puzzled ... Am I messed up in a dll? If I make unmanaged code executable and debug it, I see that output populated with the appropriate values.

+7
c ++ c # marshalling pinvoke ipc
source share
7 answers

When returning information in a structure, the standard method should pass a pointer to the structure as a parameter of the method. The method fills the elements of the structure, and then returns a status code (or logical). Therefore, you probably want to change your C ++ method to take SYSTEM_OUTPUT * and return 0 for success or an error code:

 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; } } } 
+3
source share
  • Verify that the ReadyForConnect field is not filled up to 4 bytes. In my project, it turned out that all short int (2 bytes) fields were filled with dummy bytes up to 4 bytes in an unmanaged DLL. If this is a problem, you should arrange the structure as follows:
     [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. If the strings are ANSI null terminated strings, you can annotate them as:
   [MarshalAs (UnmanagedType.LPStr)] public String VersionStr;
+3
source share

In fact, you are not collecting data on the managed side. When you declare output on the managed side, this default value is null . Then, on the unmanaged side, you never allocate memory for output . You must allocate some unmanaged memory, pass a pointer to this memory in your DLL function, and then translate the pointer to this memory into your structure:

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

Your problem may be the problem with the difference between the packaging . I updated the structure definition.

+2
source share

EDIT: I am rewriting this whole answer.

I took everything from both your C ++ code and C #, threw it into the solution and launched it - and everything works for me. I didn’t have your specific memory matching material, so I modeled it by filling in pBuf with some fake data, and everything made it return to normal; both the return value and the output structure are correct.

Could there be something wrong with the settings of your project? This sounds silly, but you mentioned running and debugging insane code; are you building dll right?

+1
source share

What you are trying to do is possible, but I think you are solving the wrong problem.

Why not read the memory mapping file directly from C #? Take a look at Winterdom.IO.FileMap

I used it and it works great.

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

You are not done with this yet - you still have to convert the byte buffer into a structure, but you are all on the managed side, and it will be easier.

+1
source share

who allocated memory for the structure? You cannot delete internal memory from a managed heap. In general, a native DLL should be allocated on the COM heap if it expects the caller to free memory or return a callback interface, such as IMalloc, to free the returned memory. This means that you need to get the memory address of the result in the form of IntPtr and use System.Runtime.InteropServices.Marshal to copy data from the native to the managed heap (maybe a structure) before freeing the memory.

Change the updated function signature: use public static extern int getStatus (ref SYSTEM_OUTPUT output); You do not allocate a bunch of COM in the native function, so there is no need.

0
source share

Have you considered adding a C ++ / CLI assembly to your project? This is an extremely simple and powerful way to bridge the gap between managed and unmanaged code. I use it quite a lot.

0
source share

All Articles