Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call unmanaged Code from C# - returning a struct with arrays

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

like image 888
rdoubleui Avatar asked Oct 30 '09 16:10

rdoubleui


People also ask

Can CLR run unmanaged code?

Unmanaged code is nothing but unsafe code. If you recall, in C#, a code typically is run under the control of the Common Language Runtime (CLR) of the . NET frameworks. CLR helps in running the code and also provides a variety of services to make the development process easy.

How is unmanaged code executed?

The executable files of unmanaged code are generally in binary images, x86 code which is directly loaded into memory. The application written in VB 6.0, C, C++, etc are always in unmanaged code.

Can we write unmanaged code in C#?

No, there is no such thing as unmanaged C#. C# code will be always compiled into the IL code and executed by CLR. It is the case of managed code calling unmanaged code. Unmanaged code can be implemented in several languages C/C++/Assembly etc, but CLR will have no idea of what is happening in that code.

How do I call C++ code from C#?

2. If you want to use C++ in c# code directly, you can create a CLR(C++/CLI) project, then you can write c++ and c# code in the same project.


2 Answers

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;
        }
    }
}
like image 183
Stephen Martin Avatar answered Oct 12 '22 23:10

Stephen Martin


  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;
like image 34
PanJanek Avatar answered Oct 13 '22 00:10

PanJanek