8

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

I'd like to call an unmanaged function in a self-written C++ dll. This library reads the machine's shared memory for status information of a third party software. Since there are a couple of values, I'd like to return the values in a struct. However, within the struct there are char [] (Arrays of char with a fixed size). I now try to receive that struct from the dll call like this:

[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 post code from the c++ dll as well, I'm sure there's more to hunt down. The original struct STATUS_DATA has an array of four instances of the struct SYSTEM_CHARACTERISTICS and within that struct there are char[]s, that are not being filled (yet), resulting in a bad pointer. That's why I'm trying to extract a subset of the first SYSTEM_CHARACTERISTICS item 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'm getting an empty output struct and the return value ist not 0 as intended. It is rather a changing number with seven digits, which leaves me puzzled... Have I messed up in the dll? If I make the unmanaged code executable and debug it, I can see, that output is being filled with the appropriate values.

7 Answers 7

4
  1. Make sure your ReadyForConnect field is not filled up to 4 bytes. In my project it turned out all short int (2 bytes) fields were filled with dummy bytes to 4 bytes in unmanaged DLL. If that's the issue, you should marshall the struct this way:
    [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;
Sign up to request clarification or add additional context in comments.

1 Comment

Using the Pack option when declaring the struct will fix this for you
3
+50

When returning information in a struct the standard method is to pass a pointer to a struct as a parameter of the method. The method fills in the struct members and then returns a status code (or boolean) of some kind. So you probably want to change your C++ method to take a SYSTEM_OUTPUT* and return 0 for success or some 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;
        }
    }
}

2 Comments

I edited the post and the source to match your suggestions. Also posted a snippet of the unmanaged code.
and did i mention that I still have no clue why it won't work?
2

You aren't actually marshaling any data over to the managed side. When you declare output on the managed side, it's default value is null. Then, on the unmanaged side, you never allocate any memory for output. You should allocate some unmanaged memory, pass the pointer to that memory to your dll function, then marshal the pointer for that memory to your 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:

Your problem could be an issue with the difference between packing strategies. I've updated the struct definition.

4 Comments

Thank you for your hint, however I'm still having trouble in the c++ code. When being called from the managed side, the getStatus-Method returns a high number of 8 digits (as before), not 0 or any other defined return value (I defined two more in the if-statements, see above). That probably messes up the values. When I run the c++ code as an executable, however, it returns 0 as it should. Anything odd in the c++ code?
can you post the full c++ executable example?
I just compiled and ran both your example and mine, and received -2 on the managed side (because I don't have the file mapped).
Then it may be an issue with packing the struct. I've added a link and updated the code
1

EDIT: I am rewriting this whole answer.

I took all of both your C++ and C# code, dropped it into a solution and ran it -- and everything works for me. I didn't have your specific memory mapping stuff so I simulated it by filling pBuf with some fake data, and everything makes it back fine; both the return value and the output struct are correct.

Could something be amiss with your project settings? This sounds silly, but you mentioned running and debugging the unamnaged code; you are building a dll right?

2 Comments

Yes, I do. I just did a build as an application for testing purposes. Then changed it back to dll and built it again.
Unmanaged dlls are not copied automatically into the bin dirs. Are the dlls in the Release and Debug directory the same?
1

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

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

I have used it and it works fine.

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

With that you are not finished - you still have to convert a byte buffer to a struct, but you are all on the managed side and it will be easier.

1 Comment

thank you, I will give that a try definitely. I'll give feedback if that will work. I searched for memory mapping in c# earlier, but only found a .Net 4.0 class.
0

who allocated the memory for the structure? You cannot delete native memory from the managed heap. Generally speaking the native DLL should allocate on the COM heap if it expect the caller to free the memory, or return a callback interface like IMalloc to free the returning memory. That means you need to receive the result memory address as IntPtr and use System.Runtime.InteropServices.Marshal to copy data from native to managed heap (may be to a structure) before freeing the memory.

Edit for the updated function signature: use public static extern int getStatus(ref SYSTEM_OUTPUT output); You are not allocating on the COM heap in the native function, so out is unnecessary.

2 Comments

There is no memory allocation problem here. He is returning a struct not a pointer to a struct so the runtime uses the standard C method of marshaling a hidden pointer for methods that return values larger than 8 bytes (on x86).
The original question showed code that returns a pointer with no clear memory ownership. The op has modified the question with a new function signature and added the native code part
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.