Calling un-managed code with pointer - c#

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

Related

How to get UID and GID on Linux using p/invoke?

I need some simple file operations on a Linux machine, for a service installer.
The code is .NET 5.0.
My current version uses Process.Start() to execute shell commands to change the owner of files and directories and set permissions.
This is quite slow (I use asynchronous methods), especially compared to Windows equivalents.
I see libc accessible to call from .NET has methods chmod and chown, however it wants uid and gid parameters. My application doesn't know the ids, at least without using shell for it.
So far I got something like this:
const string LIBC = "libc";
[DllImport(LIBC, SetLastError = true)]
private static extern int chmod(string path, uint mode);
[DllImport(LIBC, SetLastError = true)]
private static extern int chown(string path, int owner, int group);
So... how to get those 2 ints required?
UPDATE
Why anyone see this question (especially considering its title) as duplicate of question about similar, yet different things.
I know how to change owner and permissions of Linux files in many ways. The easiest way is to use Linux shell. The quickest and easiest way is to use Mono.Posix.NETStandard library, that call libc internally.
My specific question is HOW IT IS MADE? HOW DOES IT WORK?
To be even more specific:
Here's the Linux manual page for getpwnam():
https://man7.org/linux/man-pages/man3/getpwnam.3.html
How to just call it from C# using p/invoke? I see in many examples, that when they replace char* with string it somehow magically works. I created a struct like this:
public struct PASSWD {
public string pw_name; /* username */
public string pw_passwd; /* user password */
public int pw_uid; /* user ID */
public int pw_gid; /* group ID */
public string pw_gecos; /* user information */
public string pw_dir; /* home directory */
public string pw_shell; /* shell program */
};
...and tried to use it as out parameter for the signature.
I get no error, but it just doesn't work. The struct I get is empty.
So again, we are using Platform Invoke, in C#, we are calling libc and we want to get results from a structure. As far as I googled - it's not google-able. There is only Mono source code, that uses external module that implements what I need. I suspect they made it for performance reasons, also - using some special tools, because in comments stands that the code is generated.
My question is again, how, using Linux manual page definition create appropriate method signature for C# to be able to extract those 2 integers from getpwnam().
I also was curious if something like that might already exist in .NET itself, but I guess it doesn't.
So, I got rusty with p/invoke. My issue was I forgot, that when native function returns a pointer to a structure, there is no automatic conversion, I have to leave pointer in signature, so:
[DllImport(LIBC, SetLastError = true)]
internal static extern IntPtr getgrnam(string name);
[DllImport(LIBC, SetLastError = true)]
internal static extern IntPtr getpwnam(string name);
internal struct Group {
public string Name;
public string Password;
public uint Gid;
public IntPtr Members;
}
internal struct Passwd {
public string Name;
public string Password;
public uint Uid;
public uint Gid;
public string GECOS;
public string Directory;
public string Shell;
}
Lets create fully managed .NET style types:
public sealed class GroupInfo {
public string Name { get; }
public uint Id { get; }
public string[] Members { get; }
internal GroupInfo(Syscall.Group group) {
Name = group.Name;
Id = group.Gid;
Members = GetMembers(group.Members).ToArray();
}
private static IEnumerable<string> GetMembers(IntPtr members) {
IntPtr p;
for (int i = 0; (p = Marshal.ReadIntPtr(members, i * IntPtr.Size)) != IntPtr.Zero; i++)
yield return Marshal.PtrToStringAnsi(p)!;
}
}
public class UserInfo {
public string Name { get; }
public uint Uid { get; }
public uint Gid { get; }
public string? Directory { get; }
public string? Shell { get; }
internal UserInfo(Syscall.Passwd passwd) {
Name = passwd.Name;
Uid = passwd.Uid;
Gid = passwd.Gid;
Directory = passwd.Directory;
Shell = passwd.Shell;
}
}
And it can be used like this:
public static UserInfo? GetUserInfo(string name) {
var result = Syscall.getpwnam(name);
if (result != IntPtr.Zero) return new UserInfo(Marshal.PtrToStructure<Syscall.Passwd>(result));
return null;
}
public static GroupInfo? GetGroupInfo(string name) {
var result = Syscall.getgrnam(name);
if (result != IntPtr.Zero) return new GroupInfo(Marshal.PtrToStructure<Syscall.Group>(result));
return null;
}

Marshal size const array

I'm trying to have a stack allocated array inside a struct. Well the pointer I mean. But I'd like the allocation to be done without extra code because I know the size when I write the code (I don't want to do a bunch of new when I create my struct).
If I can even do it without unsafe context that's perfect.
I tried some stuff, but it's not doing fine. I'm brand new to C# so there is probably a way to do it that I didn't see !
public struct TestValue {int value; }
[StructLayout(LayoutKind.Sequential)]
public struct TestArray {
[MarshalAs(UnmanagedType.ByValArray, SizeConst=128)] public TestValue[] s1;
}
public struct TestSpan
{
Span<TestValue> data= stackalloc TestValue[10];
}
using System.Runtime.InteropServices;
public struct TestValue {int value; }
[StructLayout(LayoutKind.Sequential)]
public struct TestArray {
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst=128)] public TestValue[] s1;
}
public class Foo
{
void test()
{
TestArray test = new TestArray();
test.s1[10] = new TestValue();
}
}
I needed just a small change in the end!

How to pass char* in struct from c# to c++

It feels like my problem is similar to this.
So far, I have a struct defined in C++ like this:
typedef struct struct_type1
{
uint64 nameLen;
char * name;
} STATUSSTRUCT;
and a function defined as:
extern int _stdcall getStatus(STATUSSTRUCT * status);
and presumably the function like this:
int _stdcall getStatus(STATUSSTRUCT * status)
{
status->nameLen = someLength;
status->name = someName;
return 1;
}
Note that I can't actually change the C++ code (for various reasons) nor the header file.
My C# code looks like this:
public struct STATUSSTRUCT
{
public UInt64 nameLen;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4128)]
public byte[] name;
}
STATUSSTRUCT status;
[DllImport("test.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int getStatus(ref STATUSSTRUCT status);
public void refreshStatus() {
status = new STATUSSTRUCT();
status.nameLen = 4128;
status.name = new byte[4128];
getStatus(ref status);
}
However, calling refreshStatus is giving me System.AccessViolationException.
Can someone help me figure out how I can call this function in C++ from C#?
Your structure expects a pointer to an array; you are marshalling the array. One side of the transaction is expecting to get the address "123 Sesame Street" and you are providing an exact replica of the apartment building at that address. That's not going to work.
To get marshalling code correct you need to have a thorough and deep understanding of memory management in C#. My advice is that you obtain the services of an expert.
You may try to use StringBuilder instead of byte[] and LPStr instead of ByValArray:
public struct STATUSSTRUCT
{
public UInt64 nameLen;
[MarshalAs(UnmanagedType.LPStr, SizeConst = 4128)]
public StringBuilder name;
}
status = new STATUSSTRUCT();
status.nameLen = 4128;
status.name = new StringBuilder(4128);
getStatus(ref status);
https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.unmanagedtype(v=vs.110).aspx

Marshalling nested struct from C# to C++ DLL

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.

Struct within Struct, able to change inner Struct type

There isn't much explaining to this, this is what I have:
public struct PACKET_HEADER
{
public string computerIp;
public string computerName;
public string computerCustomName;
};
public struct PACKET
{
public PACKET_HEADER pktHdr;
public PACKET_DATA pktData;
};
public struct PACKET_DATA
{
public Command command;
public string data;
};
public struct DATA_MESSAGE
{
public string message;
};
public struct DATA_FILE
{
public string fileName;
public long fileSize;
};
Basically I want the data field in PACKET_DATA to be able to be either DATA_FILE or DATA_MESSAGE. I know the type needs to be changed but I don't know what to, is generics an option?
the end result should be so that I can do either:
pktData.data.fileName
or
pktData.data.message
EDIT
i could do:
public struct PACKET_DATA
{
public Command command;
public string data;
public DATA_MESSAGE data_message;
public DATA_FILE data_file;
};
and just set the data_message or file to null when ever i don't need them? how would this impact the serialization / byte array and the data being sent. If I used classes would I not have the same problem
EDIT 2
public struct PACKET_MESSAGE
{
public PACKET_HEADER pktHdr;
public Command command;
public DATA_MESSAGE pktData;
};
public struct PACKET_FILE
{
public PACKET_HEADER pktHdr;
public Command command;
public DATA_FILE pktData;
};
Edit 3
I have a sterilizer and de-sterilizer that works with my original example, if there are no hiccups with that then the actual serialization is done.
EDIT 4
everything seems to be working, apart from one things my serializer is getting "Attempted to read or write protected memory. This is often an indication that other memory is corrupt." gunna have a look at it when post my working solution :)
EDIT 5
public static byte[] Serialize(object anything)
{
int rawsize = Marshal.SizeOf(anything);
byte[] rawdatas = new byte[rawsize];
GCHandle handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
IntPtr buffer = handle.AddrOfPinnedObject();
Marshal.StructureToPtr(anything, buffer, false);
handle.Free();
return rawdatas;
}
public static object Deserialize(byte[] rawdatas, Type anytype)
{
int rawsize = Marshal.SizeOf(anytype);
if (rawsize > rawdatas.Length)
return null;
GCHandle handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
IntPtr buffer = handle.AddrOfPinnedObject();
object retobj = Marshal.PtrToStructure(buffer, anytype);
handle.Free();
return retobj;
}
FINAL
The structs:
public struct PACKET_HEADER
{
public string computerIp;
public string computerName;
public string computerCustomName;
};
public struct PACKET
{
public PACKET_HEADER pktHdr;
public PACKET_DATA pktData;
};
public struct PACKET_DATA
{
public Command command;
public IDATA data;
public T GetData<T>() where T : IDATA
{
return (T)(data);
}
}
public interface IDATA { }
public struct DATA_MESSAGE : IDATA
{
public string message;
}
public struct DATA_FILE : IDATA
{
public string fileName;
public long fileSize;
}
How to create a new Packet (probally could combine together tbh):
public static PACKET CreatePacket(Command command)
{
PACKET packet;
packet.pktHdr.computerIp = Settings.ComputerIP;
packet.pktHdr.computerName = Settings.ComputerName;
packet.pktHdr.computerCustomName = Settings.ComputerCustomName;
packet.pktData.command = command;
packet.pktData.data = null;
return packet;
}
public static PACKET CreatePacket(Command command, DATA_MESSAGE data_message)
{
PACKET packet;
packet.pktHdr.computerIp = Settings.ComputerIP;
packet.pktHdr.computerName = Settings.ComputerName;
packet.pktHdr.computerCustomName = Settings.ComputerCustomName;
packet.pktData.command = command;
packet.pktData.data = data_message;
return packet;
}
public static PACKET CreatePacket(Command command, DATA_FILE data_file)
{
PACKET packet;
packet.pktHdr.computerIp = Settings.ComputerIP;
packet.pktHdr.computerName = Settings.ComputerName;
packet.pktHdr.computerCustomName = Settings.ComputerCustomName;
packet.pktData.command = command;
packet.pktData.data = data_file;
return packet;
}
(de) serialization above.
Simple example:
PACKET packet = Packet.CreatePacket(command, data_file);
byte[] byData = Packet.Serialize(packet);
other end:
PACKET returnPacket = (PACKET)Packet.Deserialize(socketData.dataBuffer, typeof(PACKET));
// Get file
string fileName = returnPacket.pktData.GetData<DATA_FILE>().fileName;
long fileSize = returnPacket.pktData.GetData<DATA_FILE>().fileSize;
All seems to be working nice and dandy :)
This question needs a clear answer, so I'll try to sum up:
If you want to take a C# data structure and convert it into a byte array, you can do it with structs and Marshaling, or with classes (or structs, but why would you) and a serialization framework (like BinaryFormatter), or custom serialization logic (like with BinaryWriter). We could have a debate about which is better, but lets assume for now that we're going with structs, and we're using Marshaling. Although I will say, that structs are very limited, and should be used mainly as necessary for interop with Win32 API functions.
So the issue is, we have a container struct, which may contain one of two types of child structs. If you're going to Marshal a struct, things like generics and or using a common Interface for your child struct types are not going to fly. Basically you're only option is to have the container have both structs and a bool flag indicating which of the structures is to be used. This has the downside of increasing the size of your packets because you're sending the unused child struct as well.
In the case at hand, the result looks like this:
public struct PACKET_DATA
{
public Command command;
public string data;
public bool is_message_packet;
public DATA_MESSAGE data_message;
public DATA_FILE data_file;
};
That said, in your case using structs and Marshalling is only really going to work within your own process, because your structs contain Strings. When a struct contains pointers to non-fixedlength strings, those strings are allocated elsewhere and will not be part of the byte array you copy, only pointers to them will be. You also need to call Marshal.DestroyStructure at some point, with the IntPtr you passed to StructureToPtr, in order to clean up these string resources.
So the moral of the story: can you make structs that do what you asked initially: yes. Should you be using them like you are: no. Because you have a variable size data structure that you are trying to send over a network (I presume since the struct is called PACKET), structs are not going to work, you really need to use some kind of serialization framework or custom serialization logic.
public struct PACKET_DATA
{
public IData data;
public T GetData<T>() where T : IDATA
{
return (T)data;
}
}
public interface IDATA { }
public struct DATA_MESSAGE : IDATA
{
public string message;
}
public struct DATA_FILE : IDATA
{
public string fileName;
public long fileSize;
}
PACKET_DATA packetData = new PACKET_DATA();
packetData.data = new DATA_MESSAGE();
var message = packetData.GetData<DATA_MESSAGE>().message;
You can do it with generics, but then a type parameter will propagate to the PACKET struct which I'm guessing will make it awkward to work with and not be what you want.
What's the purpose of using structs here, rather than classes? Is it for interop? (In which case, the interop scenario will dictate the right solution). Or is it to avoid boxing / heap allocation?
How about defining a bogus interface that both structs inherit from? Of course this will not solve your serialization issues, but as was said, you'd probably need a custom serialization method anyway.
public interface IDataType
{
}
public struct PACKET_DATA
{
public Command command;
public IDataType data;
};
public struct DATA_MESSAGE : IDataType
{
public string message;
};
public struct DATA_FILE : IDataType
{
public string fileName;
public long fileSize;
};

Categories

Resources