I am developing an app that generates binary blobs from its input. As there are many types binary blob, I want to define a parent class called Config, and then multiple child classes. The parent class will have a method that generates the binary blob, with each child class having a unique struct that defines the format of the blob.
Parent:
class Config
{
public struct BinaryFormat
{
public UInt16 _config_length;
}
// compile parameters to binary format
public byte[] CompileToBinary()
{
// find the binary format struct
??????
foreach (var field in typeof(BinaryFormat).GetFields(BindingFlags.Instance | BindingFlags.Public))
{
string identifier = field.Name;
if (identifier == "_config_length")
continue;
ConfigParameter param = FindParameterByIdentifier(identifier);
if (param == null)
throw new Exception("Struct field with no matching parameter (" + identifier + ")");
field.SetValue(null, param.value);
}
int size = Marshal.SizeOf(cfg);
cfg._config_length = (UInt16)size;
byte[] buffer = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(cfg, ptr, true);
Marshal.Copy(ptr, buffer, 0, size);
Marshal.FreeHGlobal(ptr);
return buffer;
}
}
Child:
class ChildConfig : Config
{
[StructLayout(LayoutKind.Sequential)]
public struct BinaryFormat
{
public UInt16 _config_length;
public sbyte config1;
public sbyte config2;
public sbyte config3;
}
}
In CompileToBinary() if I just create a new variable of type BinaryFormat it uses the struct from the parent class. How can I use the struct from the child class?
Or is this entirely the wrong way to go about this?
Solution, based on hints and code from m.rogalski.
Parent:
class Config
{
public dynamic binary_struct;
// compile parameters to binary format
public byte[] CompileToBinary()
{
FieldInfo[] fieldinfo = Type.GetType(GetType().FullName + "+BinaryFormat").GetFields(BindingFlags.Instance | BindingFlags.Public);
dynamic cfg = binary_struct;
foreach (var field in fieldinfo)
{
string identifier = field.Name;
if (identifier == "_config_length")
continue;
ConfigParameter param = FindParameterByIdentifier(identifier);
if (param == null)
throw new Exception("Struct field with no matching parameter (" + identifier + ")");
field.SetValue(cfg, Convert.ChangeType(param.value, field.FieldType));
}
int size = Marshal.SizeOf(cfg);
cfg._config_length = (UInt16)size;
byte[] buffer = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(cfg, ptr, true);
Marshal.Copy(ptr, buffer, 0, size);
Marshal.FreeHGlobal(ptr);
return buffer;
}
}
Child:
class ChildConfig : Config
{
public cfgSettings()
{
binary_struct = new BinaryFormat();
}
[StructLayout(LayoutKind.Sequential)]
private struct BinaryFormat
{
public UInt16 _config_length;
public sbyte Config1;
public sbyte Config2;
public sbyte Config3;
}
}
So first of all your ChildConfig class will throw very annoying warning about hiding member something...
class ChildConfig : Config
{
[StructLayout(LayoutKind.Sequential)]
public new struct BinaryFormat // now it wont
{
public UInt16 _config_length;
public sbyte config1;
public sbyte config2;
public sbyte config3;
}
}
Second thing is that in C# you can get type by specifying it's "path":
namespace.class.insideclass+inside_structure
But the best thing is that most of the methods are invoked in to "top-most" object so for example if you call GetType() from Config it will return Config's class Type but if called from ChildConfig it will return ChildConfig's class Type ( brilliant as ... ).
Knowing thease things you should consider rebuilding your logic a bit:
// this was originaly
//foreach (var field in typeof(BinaryFormat).GetFields(BindingFlags.Instance | BindingFlags.Public))
// this should work with every BinaryFormat you want
foreach (var field in Type.GetType(GetType().FullName + "+BinaryFormat").GetFields(BindingFlags.Instance | BindingFlags.Public))
To create an instance you can add method inside the base Config object :
public object CreateInstance()
{
return Activator.CreateInstance(Type.GetType(GetType().FullName + "+BinaryFormat"));
}
And then use it inside your derived classes:
class ChildConfig : Config
{
private BinaryFormat _format = (BinaryFormat)CreateInstance();
}
Check it out here
Use virtual/override or abstract/override method
Define a method which depends on the BinayFormat either as a parameter or use the protected variable. Then implement it in the child class. This way the functionality will be accessed only from the child class with the child's BinayFormattype.
But this has a downside, you wont be able to access the parent struct type. Will you ever use the parent struct type? If you want to use them interchangeably you might want both of them depend on an common interface.
Related
I'm trying to marshal a class instance from and to byte array using these methods:
Marshal to byte array:
public static byte[] MakeArrayFromCat(Cat cat)
{
var size = Marshal.SizeOf(typeof(Cat));
var bytes = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(cat, ptr, false);
Marshal.Copy(ptr, bytes, 0, size);
Marshal.FreeHGlobal(ptr);
return bytes;
}
Marshal to class instance:
public static Cat MakeCatFromArray(byte[] b)
{
var size = b.Length;
var bytes = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(bytes, 0, ptr, size);
var cat = (Cat)Marshal.PtrToStructure(ptr, typeof(Cat));
Marshal.FreeHGlobal(ptr);
return cat;
}
Here's my class:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class Cat
{
public string Name { get; set; }
public int Weight { get; set; }
public int Age { get; set; }
public Cat (string name, int age, int weight)
{
Name = name;
Age = age;
Weight = weight;
}
}
So when I run the presented methods the program throws MissingMethodException meaning that there's no constructor that takes no arguements. I get it on this line (in MakeCatFromArray method):
var cat = (Cat)Marshal.PtrToStructure(ptr, typeof(Cat));
I've tried to add a default constructor, but what I get from it doesn't live up to what I expect: It gives me an instance with all its properties empty, although I marshalled an instance with non-default values. Thus I've got two questions:
1.How do I do marshalling properly in this case?
2.Is there any better way of converting an instance of a class into byte array without marking it as [Serializable] ?
The problem is with this:
Marshal.Copy(bytes, 0, ptr, size);
You are trying to create Cat from empty bytes array.
There should be b instead of bytes:
Marshal.Copy(b, 0, ptr, size);
I am trying to share a structure coming from C++ to C# using memory mapped file. So far I managed to write on the file, but I am unable to read the content in C#.
SendData in C++
struct Bus_1553 // this is the structure to send
{
string name;
int directions;
};
struct Bus_1553* p_1553; // set the pointer to it
HANDLE handle; // create the handle
// here we define the data to send
string name = "IFF";
int directions = 3;
bool startShare() // Open the shared memory
{
try
{
handle = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(Bus_1553), L"DataSend");
p_1553 = (struct Bus_1553*) MapViewOfFile(handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(Bus_1553));
return true;
}
catch (...)
{
return false;
}
}
int main()
{
if (startShare() == true)
{
while (true)
{
if (p_1553 != 0) // populate the memory
{
p_1553->name = name;
p_1553->directions = directions;
}
else
puts("create shared memory error");
}
}
if (handle != NULL)
CloseHandle(handle);
return 0;
}
Trying to read in C#
namespace sharedMemoryGET
{
class sharedMemoryGET
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct Bus_Data_1553
{
public string name;
public int directions; // which directions used
}
public static MemoryMappedFile mmf;
public static MemoryMappedViewStream mmfvs;
static public bool MemOpen() // open the mapped file
{
try
{
mmf = MemoryMappedFile.OpenExisting("DataSend");
return true;
}
catch
{
return false;
}
}
public static void readData()
{
if (MemOpen())
{
using (var accessor = mmf.CreateViewAccessor())
{
accessor.Read(0, out Bus_Data_1553 a);
Console.WriteLine(a.name);
Console.WriteLine(a.directions);
}
}
}
}
}
When a string is present in the structure to share, I have the following error:
The specified Type must be a struct containing no references.
When I remove the string and share only the int directions, i get a value of 0. Can someone help me figure this out?
Let's start with what's wrong with the C++ version. I'll bold this to make sure nobody ever passes over this, it's very important: NEVER WRITE POINTERS TO DISK
std::string is a wrapper around a pointer (2 pointers actually) that handle allocation and reallocation for you as needed. You absolutely cannot write them to a "file" anywhere, you must write the contents of those pointers instead.
One simplistic way (and prevalent in C) to do this is to simply define a buffer large enough to hold your data and then use as much of it as needed:
struct Bus_1553 // this is the structure to send
{
char name[128];
int directions;
};
To write to name, use strcpy_s or your OS equivalent.
Now once you write this structure in C++ to your shared file, reading it in C# is about letting the system (the marshaller) decode that soup of bytes into useful managed objects. You do this by using attributes on your structure and field definition:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct Bus_Data_1553
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string name;
public int directions; // which directions used
}
Also you don't need unsafe for this if you use the marshaller properly.
I have 2 libraries one is c++ and the other one c#.
Name of C++ library->LibA
Name of C#->LibB
In LibA, 2 main APIs will be there:
Serialize-> Serialize API will generate IStream as output with the given inputs.
Deserialize-> Deserialize API will take IStream as input and deserializes the stream and gets actual data from it.
#pragma once
struct GPosition
{
double x;
double y;
double z;
};
struct GUnitVector
{
double x;
double y;
double z;
};
struct GLine
{
GPosition m_rootPoint; /* Point on the line, should be in region of interest */
GUnitVector m_direction; /* Gradient */
double m_start; /* Parameter of start point, must be <= m_end */
double m_end; /* Parameter of end point */
};
class GraphicSerializer
{
public:
GUID objectID;
uint32_t aspectID;
uint32_t controlFlag;
vector<const GLine *> geomVector;
void Serialize(IStream &pStream);
void Deserialize(IStream* pStream);
};
In LibB, 4 APIs will be there:
Object GetObjectFromStream(Stream s)-> Takes stream as input and deserializes it and returns Objects
PutToDB(Object A)-> persists the given object to DB
Stream GetStreamFromObject(Object a)-> Takes object as input serializes it and returns it.
Object GetFromDB(objectID)-> Gets the object from DB based on id
public class CommonBase
{
public Guid id { get; set; };
public byte[] Bytes { get; set; } //contains aspect, control flag, vec<GLine>
};
public interface IGraphicRepository
{
CommonBase Get(Guid guid);
bool Put(CommonBase graphic);
}
public static class GraphicStreamUtility
{
public static CommonBase GetCommonBaseFromStream(Stream stream);
public static void SerializeCommonBaseToStream(CommonBase obj, Stream stream);
}
Now I'm writing C++/CLI to use stream generated by libA in libB and vice versa. So that I can persist and retrieve the objects to and from DB.
Can anyone please let me know how to convert IStream to .Net Stream and .Net stream to IStream.
Stream^ CGDMStreamCLI::ConvertIStreamToStream(IStream *pStream)
{
ULARGE_INTEGER ulSize{};
IStream_Size(pStream, &ulSize);
size_t size = ulSize.QuadPart;
auto buffer = std::make_unique<char[]>(size);
ULONG numRead{};
HRESULT hr = pStream->Read(buffer.get(), size, &numRead);
if (FAILED(hr) || size != numRead)
{
throw std::exception("Failed to read the stream");
}
cli::array<Byte>^ byteArray = gcnew cli::array<Byte>(numRead+2);
// convert native pointer to System::IntPtr with C-Style cast
Marshal::Copy((IntPtr)buffer.get(), byteArray, 0, numRead);
return gcnew System::IO::MemoryStream(byteArray);
}
I am calling a C library from a C# code. The function I am calling take as parameter a struct containing arrays of struct :
struct Example1Struct
{
char* a;
uint16_t b;
AnotherStruct* c;
}
c here is an array of pointer to AnotherStruct.
the struct in my C# code look like this
public struct Example1Struct
{
public IntPtr StationName;//is char*
public UInt16 IdCode;
public IntPtr AnotherStruct; //array of struct AnotherStruct
}
public static IntPtr MarshalToPointer(object data)
{
Type valueType = data.GetType();
IntPtr buf = IntPtr.Zero;
if (valueType.IsArray)
{
if (data is char[])
{
var d = data as char[];
buf = Marshal.AllocHGlobal(Marshal.SizeOf(d.GetType().GetElementType()) * d.Length);
}
else if (data is char[,])
{
var d = data as char[,];
buf = Marshal.AllocHGlobal(Marshal.SizeOf(d.GetType().GetElementType()) * d.Length);
}
else
{
buf = Marshal.AllocHGlobal(Marshal.SizeOf(data.GetType().GetElementType()) * count);
long LongPtr = buf.ToInt64(); // Must work both on x86 and x64
for (int I = 0; I < data.Lenght; I++)
{
IntPtr RectPtr = new IntPtr(LongPtr);
Marshal.StructureToPtr(data[I], RectPtr, false); // You do not need to erase struct in this case
LongPtr += Marshal.SizeOf(typeof(Rect));
}
}
return buf;
}
else
buf = Marshal.AllocHGlobal(Marshal.SizeOf(data));
Marshal.StructureToPtr(data, buf, false);
return buf;
}
my problem here is that I cannot cast data (who is an array of AnotherStruct) to object[] , neither in IEnumerable. So I cannot access to data[I] and don't have data.Lenght
Any idea ?
Usually I'd recommend using the MarshalAs attribute rather than writing manual marshalling code. It looks like:
public struct Example1Struct
{
public IntPtr StationName;//is char*
public UInt16 IdCode;
public IntPtr AnotherStruct; //array of struct AnotherStruct
}
Could be:
public struct Example1Struct
{
[MarshalAs(UnmanagedType.LPStr)]
public string StationName;
public UInt16 IdCode;
[MarshalAs(UnmanagedType.LPArray)]
public AnotherStruct[] OtherStructs;
}
And the marshaller should do the right thing for you when you pass it to unmanaged code.
You can get the length of the array like this:
if (data is Array a)
Console.WriteLine(a.Length);
Arrays in c# always derive from Array, so you can cast it to that.
But if possible in your real code, I'd recommend Damien's answer
Ok, In C++ I could create a struct object that contains a bunch of value types and when needed write it out to a binary file in a single write statement like this:
struct DataFileHeader {
unsigned short Id;
unsigned short Type;
unsigned int Count;
unsigned int Offset;
};
...
struct DataFileHeader dataFileHeader;
...
rc = _write(fileHandle, &dataFileHeader, 12);
Is there any way to do that in c#? I've converted my structs to classes in c# and am working with the BinaryFormatter and trying to serialize the object but that adds a bunch of text and other stuff to the stream. I just want to write out the all the value fields in the object. Is this possible or do I have to write out each field on the object separatly?
void Main()
{
var fileHeader = new DataFileHeader()
{
Count = 10,
Id = 10,
Offset = 10,
Type = 10
};
Serialize(fileHeader).Dump();
}
// Define other methods and classes here
public byte[] Serialize<TObject>(TObject obj)
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
byte[] ret = null;
using (MemoryStream ms = new MemoryStream())
{
binaryFormatter.Serialize(ms, obj);
ret = ms.ToArray();
}
return ret;
}
[Serializable]
public struct DataFileHeader
{
public short Id;
public short Type;
public int Count;
public int Offset;
};
If that doesn't work I think doing it individually might be the only way.
BitConverter.GetBytes(fileHeader.Count).Concat(BitConverter.GetBytes(fileHeader.Count)).Concat(BitConverter.GetBytes(fileHeader.Count)).Concat(BitConverter.GetBytes(fileHeader.Count)).Dump();