Marshalling nested struct from C# to C++ DLL - c#

I am trying to call a function from C# to a .DLL written in Borland C++ whose signature is:
extern "C" __declspec(dllexport) ls50errortype __stdcall Ls50P2Open(ls50p2apiconfiginfostruct &configinfo);
The corresponding call in C#:
[DllImport("C:\\Lumistar\\LDPS_8x\\Ls50P2_Dll.dll", EntryPoint = "Ls50P2Open", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall, CharSet = CharSet.Ansi, SetLastError = true)]
public static extern void Ls50P2Open(ref ls50p2apiconfiginfostruct configinfo);
The structure of interest (ls50p2apiconfiginfostruct) is composed of nested structures and enums (copied from the C++ header):
typedef enum
{
MFMODEL_LS50P1,
MFMODEL_4422PCI,
MFMODEL_LS50P2,
MFMODEL_LS70P2,
MFMODEL_LS5070,
MFMODEL_LAST
}ecardmodel;
typedef enum
{
CHANNELDEVICE_NONE,
CHANNELDEVICE_50,
CHANNELDEVICE_70,
CHANNELDEVICE_LAST
}ls50p2channeldevicetype;
typedef enum
{
LS50V2DCARD_NONE,
LS50V2DCARD_40V1_10,
LS50V2DCARD_40V1_20,
LS50V2DCARD_40V2_10,
LS50V2DCARD_40V2_20,
LS50V2DCARD_38,
LS50V2DCARD_LAST
}ls50p2daughtercardtype;
typedef struct
{
bool HasDaughterCard;
ls50p2daughtercardtype DCardType;
bool SpecialStatusCapable;
int MaxBitsyncInputs;
bool HasBitsyncConfidenceLevel;
bool HasBitsync2ndCh;
bool SpecialStatusCapable2ndCh;
bool HasBitsyncConfidenceLevel2ndCh;
ls50p2daughtercardtype DCardType2ndCh;
int MaxBitsyncInputs2ndCh;
}ls50p2daughtercardinfostruct;
typedef struct
{
ecardmodel DeviceModel;
ls50p2channeldevicetype ChannelDataTypeAry[2];
ls50p2daughtercardinfostruct DaughterCardInfo;
bool HasExtendedBertPatterns;
int FirmwareVersionAry[2];
int NumPremodFiltersAry[2];
double Ls50SimPreModFilterKhzAry[2][LS50V2_MAX50SIMPREMODFILTERS];
double Ls50SimMinFmDeviationKhzAry[2];
double Ls50SimMaxFmDeviationKhzAry[2];
}ls50p2cardconfigstruct;
typedef struct
{
unsigned char *DataBuf;
HANDLE hNewDataRdy;
DWORD MaxBufLength;
DWORD CurrentBufLength;
int NumHeaderBytes;
}ls50p2carddatastruct;
typedef struct
{
ls50p2cardconfigstruct CardConfigInfo[MAXMFCARDS];
int Ls50P2CardCount;
ls50p2carddatastruct DataInfo[MAXMFCARDS][2];
}ls50p2apiconfiginfostruct;
Here's the corresponding struct in C#:
public enum ecardmodel
{
MFMODEL_LS50P1,
MFMODEL_4422PCI,
MFMODEL_LS50P2,
MFMODEL_LS70P2,
MFMODEL_LS5070,
MFMODEL_LAST
}
public enum ls50p2channeldevicetype
{
CHANNELDEVICE_NONE,
CHANNELDEVICE_50,
CHANNELDEVICE_70,
CHANNELDEVICE_LAST
};
public enum ls50p2daughtercardtype
{
LS50V2DCARD_NONE,
LS50V2DCARD_40V1_10,
LS50V2DCARD_40V1_20,
LS50V2DCARD_40V2_10,
LS50V2DCARD_40V2_20,
LS50V2DCARD_38,
LS50V2DCARD_LAST
}
[StructLayout(LayoutKind.Sequential)]
public struct ls50p2daughtercardinfostruct
{
public bool HasDaughterCard;
public ls50p2daughtercardtype DCardType;
public bool SpecialStatusCapable;
public int MaxBitsyncInputs;
public bool HasBitsyncConfidenceLevel;
public bool HasBitsync2ndCh;
public bool SpecialStatusCapable2ndCh;
public bool HasBitsyncConfidenceLevel2ndCh;
public ls50p2daughtercardtype DCardType2ndCh;
public int MaxBitsyncInputs2ndCh;
}
[StructLayout(LayoutKind.Sequential)]
public struct ls50p2cardconfigstruct
{
public ecardmodel DeviceModel;
public ls50p2daughtercardtype[] ChannelDataTypeAry;
public ls50p2daughtercardinfostruct DaughterCardInfo;
public bool HasExtendedBertPatterns;
public int[] FirmwareVersionAry;
public int[] NumPremodFiltersAry;
public double[] Ls50SimPreModFilterKhzAry;
public double[] Ls50SimMinFmDeviationKhzAry;
public double[] Ls50SimMaxFmDeviationKhzAry;
}
[StructLayout(LayoutKind.Sequential)]
public struct ls50p2carddatastruct
{
public StringBuilder DataBuf;
public IntPtr hNewDataRdy;
public uint MaxBufLength;
public uint CurrentBufLength;
public int NumHeaderBytes;
}
[StructLayout(LayoutKind.Sequential)]
public struct ls50p2apiconfiginfostruct
{
public ls50p2cardconfigstruct[] CardConfigInfo;
public int Ls50P2CardCount;
public ls50p2carddatastruct[,] DataInfo;
}
Here's the code in C# that I use to call the function:
ls50p2apiconfiginfostruct lscfg = new ls50p2apiconfiginfostruct();
lscfg.CardConfigInfo = new ls50p2cardconfigstruct[8];
for (int i = 0; i < 8; i++)
{
lscfg.CardConfigInfo[i].ChannelDataTypeAry = new ls50p2daughtercardtype[2];
}
lscfg.DataInfo = new ls50p2carddatastruct[8, 2];
Ls50P2Open(ref lscfg);
I have tried making this struct in C# but I haven't had much success (problems with enums, 2D arrays, fixed sized buffers). What is the correct way to create this structure in C#? Would this need to be done in an unsafe context?
Now for some reason I get the following error when running the code:
An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in Library.dll
Additional information: Old format or invalid type library. (Exception from HRESULT: 0x80028019 (TYPE_E_UNSUPFORMAT))

What does your C# structure looks like. Are you using the StructLayoutAttribute?
http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.structlayoutattribute.aspx
You can use it with the option sequential so you just would have to fill your c# structure with fields in the right order.

I believe the array problem is more or less answered here; Improper marshaling: C# array to a C++ unmanaged array
The accepted answer shows how to safely marshal a dynamically allocated array.
As for the enums, they shouldn't pose any problems, there is a clean 1:1 mapping. In fact, I would do it as described in this msdn post; http://blogs.msdn.com/b/abhinaba/archive/2007/08/27/sharing-enums-across-c-and-c.aspx
You can simply define all your enums in a .cs file then include it in both projects and everything will work fine.

Related

Marshaling C# struct to C++ dll - different values

I have a DLL written in C++ and I am trying to call a function that expects a struct from my C# project.
Here's its declaration
int _stdcall InitialiseMotors(int i_nBoard, Array_Of_Structures moveParameters)
And here is the declaration of the struct
typedef struct MoveMotorSolenoidParametersTag
{
bool SetDirectionForMotor;
int SetNumberOfStepsForMotor;
bool TurnSolenoidsON_OFF;
} MoveMotorSolenoidParameters;
The corresponding call in C#:
[DllImport(DllBeingUsed)]
private static extern int InitialiseMotors(int i_nBoard,
DllDatatypes.MoveMotorSolenoidParameters moveMotorSolenoidParameters);
And the definition of my structure:
[StructLayout(LayoutKind.Sequential)]
public struct MoveMotorSolenoidParameters
{
public bool SetDirectionForMotor;
public int SetNumberOfStepsForMotor;
public bool TurnSolenoidsON_OFF;
public MoveMotorSolenoidParameters(bool setDirectionForMotor = true,
int setNumberofStepsForMotor = 10,
bool turnSolenoidsON_OFF = true )
{
SetDirectionForMotor = setDirectionForMotor;
SetNumberOfStepsForMotor = setNumberofStepsForMotor;
TurnSolenoidsON_OFF = turnSolenoidsON_OFF;
}
}
Even though I am passing the values true, 10 and true, my DLL seems to be getting different values. true, 167772160 and false. I can see these values by putting a breakpoint on the DLL. I checked the values before the DLL function is called and they are correct. So I am assuming something is happening during the marshalling.
QuickWatch of C++ struct
Any ideas why this is happening?

How to handle the content between unmanaged dll and C#

Hi i got problems to handle information between an unmanaged dll and my C# code.
The function in my C dll is:
int import_large_string(struct sha_struct *sha_s,
const char *large_string);
That import the large_string and transform into sha_s, the struct sha_struct is the following:
struct sha_struct
{
struct information *info;
struct info_cfg cfg;
};
And the information struct is something like
struct information
{
int version;
char serial_number[16];
uint16_t exp_date;
};
In C# i imported the function import_large_string like this:
[DllImport("functionalDLL.dll", EntryPoint = "import_large_string", CharSet = CharSet.Ansi)]
public static int import_large_string(ref sha_struct sha_s, string large_string);
When i called the function, it suppose to store information in sha_struct sha_s, but i only have a random 8 digit number in sha_s.information.version not in the whole struct, what am I doing wrong?
Edited
My structs in C# are:
[StructLayout(LayoutKind.Sequential)]
public struct sha_struct
{
public information t;
public info_cfg cfg;
};
[StructLayout(LayoutKind.Sequential)]
public struct information
{
public int version;
public string serial_number;
public ushort exp_date;
};
[StructLayout(LayoutKind.Sequential)]
public struct info_cfg
{
public string release_version;
public string release_string;
public string release_password;
};
As pointed out by #DavidHeffernan, you want to use the MarshalAs attribute on your string field:
[StructLayout(LayoutKind.Sequential)]
public struct information
{
public int version;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=16)]
public string serial_number;
public ushort exp_date;
};
As illustrated on this documentation link.

Calling un-managed code with pointer

I have a C# project that makes calls out to an unmanaged C++ dll. The wrapper and most of the calls are working OK, so I know that I have the basic structure of how everything ties together OK, but there is one specific call that is giving me fits. The API call requires a pointer to a structure, which contains a list of configuration data.
Here is the call:
m_status = m_XXXXBox.SetConfig(m_channelId, ref SCONFIG_LIST);
Where SCONFIG_LIST is the structure containing the data...
The issue specifically relates to SCONFIG_LIST
Here is the documentation directly from the spec for this API :
Points to the structure SCONFIG_LIST, which is defined as follows:
typedef struct
{
unsigned long NumOfParams; /* number of SCONFIG elements */
SCONFIG *ConfigPtr; /* array of SCONFIG */
} SCONFIG_LIST
where:
NumOfParms is an INPUT, which contains the number of SCONFIG elements in the array
pointed to by ConfigPtr.
ConfigPtr is a pointer to an array of SCONFIG structures.
The structure SCONFIG is defined as follows:
typedef struct
{
unsigned long Parameter; /* name of parameter */
unsigned long Value; /* value of the parameter */
} SCONFIG
Here are the 2 structures that I defined in C#:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct SConfig
{
public int Parameter;
public int Value;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct SConfig_List
{
public int NumOfParams;
// public List<SConfig> sconfig = new List<SConfig>(); // This throws compile time error
public List<SConfig> sconfig;
}
I know you can not have field initializers in structs, but I cant seem to figure out how to initialize the sconfig in the struct externally...
Here is snippet from the calling method
SConfig_List myConfig = new SConfig_List();
SConfig configData = new SConfig();
configData.Parameter = 0x04;
configData.Value = 0x10;
myConfig.NumOfParams = 1;
myConfig.sconfig.Add(configData);
This throws an error at runtime of "object reference not set to an instance of an object"
I understand this error because sconfig has not been initialized - I just cant figure out how to do that....
So my next thought was to get around this, I would just create the SCONFIG_LIST struct like this (without the list inside) - My reasoning for this is that I now do not have to initialize the object, and I could just make multiple calls to dll with a NumOfParams = 1, rather than NumOfParams > 1 and having the dll loop through the struct data.
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct SConfig_List
{
public int NumOfParams;
public SConfig sconfig;
}
And here is how I called the method
configData.Parameter = 0x04;
configData.Value = 0x10;
myConfig.NumOfParams = 1;
myConfig.sconfig.Parameter = configData.Parameter;
myConfig.sconfig.Value = configData.Value;
m_status = m_XXXXBox.SetConfig(m_channelId, ref myConfig);
This got rid of the errors to this point, now on the the actual method that calls the dll
There are still several questions / issues surrounding the Marshalling, but here it is:
public XXXXErr SetConfig(int channelId, ref SConfig_List config)
{
unsafe
{
IntPtr output = IntPtr.Zero;
IntPtr input = Marshal.AllocHGlobal(Marshal.SizeOf(config));
Marshal.StructureToPtr(config, input, true);
XXXXErr returnVal = (XXXXErr)m_wrapper.Ioctl(channelId, (int)Ioctl.SET_CONFIG, input, output);
return returnVal;
}
}
This gets past all of the initial setup without error, but when I try to actually invoke the dll I get an error : Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
I know this is a mouthful, and I really don't even know exactly what to ask as I am sure there are multiple issues within this post, but any ideas on getting me on the right track?
I have tried so many things at this point I am at a loss, and I just need some direction. I am not looking for a "do this for me" type answer, but rather an explanation and maybe some pointers on getting this done. As with all things, I am sure there are multiple ways of accomplishing the task - maybe a way that works, but is not good form, and a longer more complicated way that may be "better practice"
Any and all suggestions / comments will be greatly appreciated. And if I excluded any relevant data that is required to help me solve this riddle let me know and I will provide what I can.
I want to thank the responses so far.
I have been trying every combination to try and resolve this myself but I have not had any luck so far. I have found quite a few ways that do NOT work, however :-)
I have tried various combinations of "unsafe" - "MarshalAs", "StructLayout" and several other things I found on the web, now I am begging for mercy.
I have successfully implemented several other calls to this unmanaged dll, but all of them use simple integer pointers etc. My problem is passing the pointer to a Struct containing an array of another struct. If you look at the very top of my original question you can see the documentation from the dll and how it wants thing structured. There is NO return value, I am merely trying to pass some configuration settings to a device through this dll.
I am going to post a framework of my entire project so that maybe I can get someone to hold my hand through this process, and hopefully help others in the future trying to solve this type of issue as well.
Here is skeleton of Wrapper (not all functions displayed)
using System;
using System.Runtime.InteropServices;
namespace My_Project
{
internal static class NativeMethods
{
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);
}
internal class APIDllWrapper
{
private IntPtr m_pDll;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int APIIoctl(int channelId, int ioctlID, IntPtr input, IntPtr output);
public APIIoctl Ioctl;
//extern ā€œCā€ long WINAPI APIIoctl
//(
//unsigned long ChannelID,
//unsigned long IoctlID,
//void *pInput,
//void *pOutput
//)
public bool LoadAPILibrary(string path)
{
m_pDll = NativeMethods.LoadLibrary(path);
if (m_pDll == IntPtr.Zero)
return false;
pAddressOfFunctionToCall = NativeMethods.GetProcAddress(m_pDll, "APIIoctl");
if (pAddressOfFunctionToCall != IntPtr.Zero)
Ioctl = (APIIoctl)Marshal.GetDelegateForFunctionPointer(
pAddressOfFunctionToCall,
typeof(APIIoctl));
return true;
}
public bool FreeLibrary()
{
return NativeMethods.FreeLibrary(m_pDll);
}
}
}
And Here is the class that defines the hardware I am trying to communicate with
namespace My_Project
{
public class APIDevice
{
public string Vendor { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
}
Interface
using System.Collections.Generic;
namespace My_Project
{
public interface I_API
{
APIErr SetConfig(int channelId, ref SConfig_List config);
}
}
The actual Class containing the API Code - this is where the error is, I know that how I have the IntPtrs now is Not correct - But this displays what I am trying to do
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace My_Project
{
public class API : I_API
{
private APIDevice m_device;
private APIDllWrapper m_wrapper;
public APIErr SetConfig(int channelId, ref SConfig_List config)
{
IntPtr output = IntPtr.Zero;
IntPtr input = Marshal.AllocHGlobal(Marshal.SizeOf(config));
Marshal.StructureToPtr(config, input, true);
APIErr returnVal = (APIErr)m_wrapper.Ioctl(channelId, (int)Ioctl.SET_CONFIG, input, output);
return returnVal;
}
}
}
Here is class containing the definitions for the Structs I am using
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace My_Project
{
public enum APIErr
{
STATUS_NOERROR = 0x00,
ERR_BUFFER_EMPTY = 0x10,
ERR_BUFFER_FULL = 0x11,
ERR_BUFFER_OVERFLOW = 0x12
}
public struct SConfig
{
public int Parameter;
public int Value;
}
public struct SConfig_List
{
public int NumOfParams;
public SConfig[] sconfig;
public SConfig_List(List<SConfig> param)
{
this.NumOfParams = param.Count;
this.sconfig = new SConfig[param.Count];
param.CopyTo(this.sconfig);
}
}
}
And finally - the actual application calling the dll through the wrapper
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using My_Project;
namespace Test_API
{
public class Comm
{
private I_API m_APIBox;
APIErr m_status;
int m_channelId;
bool m_isConnected;
public Comm(I_API apiInterface)
{
m_APIBox = apiInterface;
m_isConnected = false;
m_status = APIErr.STATUS_NOERROR;
}
public bool ConfigureDevice()
{
SConfig tempConfig = new SConfig();
tempConfig.Parameter = 0x04;
tempConfig.Value = 0x10;
SConfig_List setConfig = new SConfig_List(tempConfig);
m_status = m_APIBox.SetConfig(m_channelId, ref setConfig);
if (m_status != APIErr.STATUS_NOERROR)
{
m_APIBox.Disconnect(m_channelId);
return false;
}
return true;
}
}
}
You cannot marshal List<>, it must be an array. An array is already marshaled as a pointer so you don't have to do anything special. Go easy on the Pack, no need for the unsafe keyword.
You could add a constructor to the struct to make it easy to initialize it from a List<>. Like this:
[StructLayout(LayoutKind.Sequential)]
public struct SConfig {
public int Parameter;
public int Value;
}
[StructLayout(LayoutKind.Sequential)]
public struct SConfig_List {
public int NumOfParams;
public SConfig[] sconfig;
public SConfig_List(List<SConfig> param) {
this.NumOfParams = param.Count;
this.sconfig = new SConfig[param.Count];
param.CopyTo(this.sconfig);
}
}
To initialize list you just have to add line:
myConfig.sconfig = new List<SConfig>()
before you start adding elements into it.
I started another thread because I was asking the wrong question due to my inexperience
The working solution is here
Marshal array of struct and IntPtr
Thanks for the help
-Lee

Error: Calling C++ dll function in C#

I am trying to use functions in C++ dll from C#, but I got an error: "attempt to read or write protected memory. This is often indication that other memory is corrupt"
Anyone know how to fix ?
Here is C++ functions:
typedef void *DGNHandle;
__declspec(dllexport) DGNHandle CPL_DLL DGNOpen( const char *, int );
__declspec(dllexport) DGNElemCore CPL_DLL *DGNReadElement( DGNHandle )
Here is structure in C++:
typedef struct {
int offset;
int size;
int element_id; /*!< Element number (zero based) */
int stype; /*!< Structure type: (DGNST_*) */
int level; /*!< Element Level: 0-63 */
int type; /*!< Element type (DGNT_) */
int complex; /*!< Is element complex? */
int deleted; /*!< Is element deleted? */
int graphic_group; /*!< Graphic group number */
int properties; /*!< Properties: ORing of DGNPF_ flags */
int color; /*!< Color index (0-255) */
int weight; /*!< Line Weight (0-31) */
int style; /*!< Line Style: One of DGNS_* values */
int attr_bytes; /*!< Bytes of attribute data, usually zero. */
unsigned char *attr_data; /*!< Raw attribute data */
int raw_bytes; /*!< Bytes of raw data, usually zero. */
unsigned char *raw_data; /*!< All raw element data including header. */
} DGNElemCore;
And below converted codes are in C#:
[StructLayout(LayoutKind.Sequential )]
public class DGNElemCore
{
public int attr_bytes;
public byte[] attr_data;
public int color;
public int complex;
public int deleted;
public int element_id;
public int graphic_group;
public int level;
public int offset;
public int properties;
public int raw_bytes;
public byte[] raw_data;
public int size;
public int style;
public int stype;
public int type;
public int weight;
}
[DllImport("DgnLib.dll", EntryPoint = "DGNOpen")]
public static extern IntPtr DGNOpen(string fileName, int bUpdate);
[DllImport("DgnLib.dll", EntryPoint = "DGNReadElement")]
public static extern DGNElemCore DGNReadElement(IntPtr DGNHandle)
Codes for testing:
DGNElemCore element = new DGNElemCore();
element = DgnFile.DGNReadElement(dgnFile.oDgnFile) **//Throw error**
Your declaration of DGNElemCore in your C# code is wrong - it needs to exactly match your C structure (especially in size) as otherwise the marshalling code will attempt to marshal memory incorrectly. An example definition which will work (as in not cause problems during marshalling) would be the following
[StructLayout(LayoutKind.Sequential )]
public class DGNElemCore
{
int offset;
int size;
int element_id;
int stype;
int level;
int type;
int complex;
int deleted;
int graphic_group;
int properties;
int color;
int weight;
int style;
int attr_bytes;
IntPtr attr_data;
int raw_bytes;
IntPtr raw_data;
}
Note in particular
The order of members in the C# classes match those in the C struct (although this wont cause an error when calling your function it will give you incorrect values when accessing the members of the marshalled struct)
The char* fields are marshalled as IntPtrs - attempting to marshal pointers to arrays as arrays won't work by default as arrays are larger than pointers, resulting in the marshaller attempting to marshal more memory than is available.
Also I've noticed that your P/Invoke method declarations are wrong. The DGNOpen function returns the structure itself (not a pointer) and so should look more like the following.
public static extern DGNElemCore DGNOpen(string fileName, int bUpdate);
The DGNReadElement function accepts a struct (not a pointer) and returns a pointer to that strucut (not a struct) and so should look more like this
public static extern IntPtr DGNReadElement(DGNHandle handle);
Attributes can be used to change the way that the marshaller works, which can in turn be used to alter the signature of these methods, however if you do this you need to be careful to ensure that the marshalling will still match up to your C++ function declarations.
The problem is that the #include headers might contain declarations that can be misinterpreted by the C++/CLI compilers. C function declarations for example. Best thing to do is to the tell the compiler explicitly about it.
#pragma managed(push, off)
#include "c_include.h"
#pragma managed(pop)
Then you can use C++ libraries from within C++/CLI application like you do with C++ apps. The only thing I always try to do is to wrap 3rd library behind Proxy or Facade design pattern, so that the client would always work with managed classes. This is especially important if your C++/CLI app is a library used by other .NET apps.
Moreover, normally public API classes (or functions) of your DLL should be exposed using following construction:
#ifdef YOUR_DLL_EXPORTS
#define YOUR_API __declspec(dllexport)
#else
#define YOUR_API __declspec(dllimport)
#endif
class YOUR_API ClassToExpose {};
Hope this help

calling unmanaged function char returns char *

I have a function in unmanaged C/C++ code (dll) that returns a structure containing a char array. I created C# struct to receive this return value uppon calling the function. And uppon calling this function i get 'System.Runtime.InteropServices.MarshalDirectiveException'
This is C declaration:
typedef struct T_SAMPLE_STRUCT {
int num;
char text[20];
} SAMPLE_STRUCT;
SAMPLE_STRUCT sampleFunction( SAMPLE_STRUCT ss );
This is C# declaration:
struct SAMPLE_STRUCT
{
public int num;
public string text;
}
class Dllwrapper
{
[DllImport("samplecdll.dll")]
public static extern SAMPLE_STRUCT sampleFunction(SAMPLE_STRUCT ss);
}
I am using 1-byte ASCII.
Does anyone has a hint or a solution on how to do this?
The trick to converting a C array member is to use the MarshalAs(UnmanagedType.ByValTStr). This can be used to tell the CLR to marshal the array as an inlined member vs. a normal non-inlined array. Try the following signature.
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet=System.Runtime.InteropServices.CharSet.Ansi)]
public struct T_SAMPLE_STRUCT {
/// int
public int num;
/// char[20]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst=20)]
public string text;
}
public partial class NativeMethods {
/// Return Type: SAMPLE_STRUCT->T_SAMPLE_STRUCT
///ss: SAMPLE_STRUCT->T_SAMPLE_STRUCT
[System.Runtime.InteropServices.DllImportAttribute("<Unknown>", EntryPoint="sampleFunction")]
public static extern T_SAMPLE_STRUCT sampleFunction(T_SAMPLE_STRUCT ss) ;
}
This signature is brought to you by the PInovke Interop Assistant (link) available on CodePlex. It can automatically translate most PInvoke signatures from native code to C# or VB.Net.
This is not an easy structure for P/Invoke to marshal: it's easier to marshal structures that contain char* instead of char[] (although you're then left with the problem of allocating the char* from unmanaged code and later freeing it from managed code).
Assuming you're sticking with the current design, one option is to declare the string array as:
public fixed char text[20];
Unfortunately you must then add the unsafe keyword to any code that accesses this array.
Struct definition in C:
#pragma pack(push, 1)
typedef struct T_SAMPLE_STRUCT {
int num;
char text[20];
};
#pragma pack(pop)
Definition in C#:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct T_SAMPLE_STRUCT
{
[MarshalAs(UnmanagedType.I4)]
public int num;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public string text;
}
I have managed to do this by separating function to:
void receiveStruct( SAMPLE_STRUCT ss )
void returnStruct(SAMPLE_STRUCT &ss)
I have changed struct definition as JaredPar told me:
[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct T_SAMPLE_STRUCT
{
/// int
public int num;
/// char[20]
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 20)]
public string text;
}
And now it works.
Thanks!
As an interesting aside, since you already have the answer, the basic issue here is that the struct just needs to be the right size. Since managed types don't have inline arrays, you just have to make up the space that it would otherwise need. When you write in C++/CLI, you'll often see the StructLayoutAttribute with an explicit Size parameter. This makes the runtime allocate the right amount of memory for the type, which allows it to be blittable to the native side. It follows that these should work, as well:
[StructLayout(LayoutKind.Sequential, Size=24)]
public struct T_SAMPLE_STRUCT
{
public int num;
// to get the string here, you'd need to get a pointer
public char firstChar;
}
// or
[StructLayout(LayoutKind.Sequential)]
public struct T_SAMPLE_STRUCT
{
public int num;
public byte c0;
public byte c1;
public byte c2;
public byte c3;
public byte c4;
public byte c5;
public byte c6;
public byte c7;
public byte c8;
public byte c9;
public byte c10;
public byte c11;
public byte c12;
public byte c13;
public byte c14;
public byte c15;
public byte c16;
public byte c17;
public byte c18;
public byte c19;
}
Of course, these are much harder to use from managed code (you'd need to copy memory or use pointers), but they illustrate the concept of a blittable type, which is primarily how types are passed between native and managed code.
First you have to put
StructLayout[Sequential] attribute on your struct and I think it will work
[ StructLayout( LayoutKind.Sequential, CharSet=CharSet.Ansi )]
struct SAMPLE_STRUCT
{
public int num;
[ MarshalAs( UnmanagedType.ByValArray, SizeConst=20 )]
public char[] text;
}

Categories

Resources