I am writing wrappers using C++/CLR. The managed C# class has a function signature as below
//C#
int WriteToInstrument(string command, ref string response, int stage);
I have to write a C++ wrapper to this function in something like the following signature
//C++
int WriteToInstrumentWrap(const char * command, char * response, int stage);
My question is: how can I handle the conversion from "ref string" in C# to char* in C++? Or how can I handle the situation that requires to take a ref string from C# that can be used in C/C++? Many thanks in advance.
I'll add some examples of code I've written this morning. In general, when speaking of returning objects (in the broad meaning where even a char* string is an object), the big questions in C/C++ are:
Who allocates the memory
How many elements are needed
How is the memory allocated (which allocator is used)
And as a corollary, how the memory must be freed
One last optional question is if the memory must be really freed: a method could return a pointer to an internal object that has a lifetime equal to the lifetime of the program and that mustn't be freed. For example:
const char* Message()
{
return "OK";
}
You mustn't free the memory returned by Message()!
This questions get even more complex when you are writing a library (a dll) that will be used by other programs: the malloc and the new that are used in a dll can be different/distinct from the malloc and the new used by the main program (or by another dll), so that you shouldn't free with your (main program) free the memory that is malloc(ed) by a dll.
There are three possible solutions to this particular problem:
Use a shared allocator, for example one given by the OS. Windows gives LocalAlloc and CoTaskMemAlloc. They are even accessible from .NET (Marshal.AllocHGlobal and Marshal.AllocCoTaskMem). In this way the main application can free the memory allocated by the dll
The API of your dll has a Free() method that must be used to free the memory allocated by the dll
The API of your dll has some methods like SetAllocator(void *(*allocator)(size_t)) and SetFree(void (*free)(void*)), so methods that store a function pointer, that the main application can use to set the allocator and free to be used by the dll, so that they are shared between the main application and the dll. The dll will use those allocators. Note that SetAllocator(malloc); SetFree(free) if done by the main application is perfectly legal: now the dll will use the main application's malloc, and not the dll's malloc!
Shortcut used in some example I'll give: the method has as a parameter the allocator (a function pointer) that will then be used
As an important sidenote: we are in 2018. It is at least 15 years that you should have forgotten of char* for strings in C for Windows. Use wchar_t. Always.
And finally some code :-)
Now... given (C# code):
int WriteToInstrument(string command, ref string response, int stage)
{
response = "The quick brown fox jumps over the lazy dog";
return 0;
}
Simple method that calls WriteToInstrument and then copies the response result to an ansi string (char*). The buffer is allocated by the caller, and is of size length. After the method is executed, length contains the number of characters used (including the terminating \0). The response is always \0 terminated. The problem here is that the response could get truncated and/or the caller could allocate a buffer too much big (that won't really protect it from the truncation problem, if it is unlucky :-) ). I'll repeat myself here: using char* for strings in 2018 is ancient technology.
// Utility method to copy a unicode string to a fixed size buffer
size_t Utf16ToAnsi(const wchar_t *wstr, char *str, size_t length)
{
if (length == 0)
{
return 0;
}
// This whole piece of code can be moved to a method
size_t length2 = WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, (int)length, nullptr, nullptr);
// WideCharToMultiByte will try to write up to *length characters, but
// if the buffer is too much small, it will return 0,
// **and the tring won't be 0-terminated**
if (length2 != 0)
{
return length2;
}
// Buffer too much small
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// We add a terminating 0
str[length - 1] = 0;
return length;
}
// Big bad error, shouldn't happen. Return 0 but terminate the string
str[0] = 0;
return 0;
}
Example of use:
char response[16];
size_t length = sizeof(response) / sizeof(char); // useless / sizeof(char) == / 1 by definition
WriteToInstrumentWrap1("cmd1", response, &length, 1);
std::cout << "fixed buffer char[]: " << response << ", used length: " << length << std::endl;
or (using std::vector<>/std::array<>)
//Alternative: std::array<char, 16> response;
std::vector<char> response(16);
size_t length = response.size();
WriteToInstrumentWrap1("cmd1", response.data(), &length, 1);
std::cout << "fixed buffer vector<char>: " << response.data() << ", used length: " << length << std::endl;
Simple method that calls WriteToInstrument and then copies the response result to an unicode string (wchar_t*). The buffer is allocated by the caller, and is of size length. After the method is executed, length contains the number of characters used (including the terminating \0). The response is always \0 terminated.
// in input length is the size of response, in output the number of characters (not bytes!) written to response
// (INCLUDING THE \0!). The string is always correctly terminated.
int WriteToInstrumentWrap2(const wchar_t *command, wchar_t *response, size_t *length, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
*length = (size_t)str2->Length < *length ? str2->Length : *length - 1;
memcpy(response, pch, *length * sizeof(wchar_t));
response[*length] = '\0';
*length++;
return res;
}
Example of use:
wchar_t response[16];
size_t length = sizeof(response) / sizeof(wchar_t);
WriteToInstrumentWrap2(L"cmd1", response, &length, 1);
std::wcout << L"fixed buffer wchar_t[]: " << response << L", used length: " << length << std::endl;
or (using std::vector<>/std::array<char, 16>)
//Alternative: std::array<wchar_t, 16> response;
std::vector<wchar_t> response(16);
size_t length = response.size();
WriteToInstrumentWrap2(L"cmd1", response.data(), &length, 1);
std::wcout << L"fixed buffer vector<wchar_t>: " << response.data() << ", used length: " << length << std::endl;
All the next examples will use char instead of wchar_t. It is quite easy to convert them. I'll repeat myself here: using char* for strings in 2018 is ancient technology. It is like using ArrayList instead of List<>
Simple method that calls WriteToInstrument, allocates the response buffer using CoTaskMemAlloc and copies the result to an ansi string (char*). The caller must CoTaskMemFree the allocated memory. The response is always \0 terminated.
// Memory allocated with CoTaskMemAlloc. Remember to CoTaskMemFree!
int WriteToInstrumentWrap3(const char *command, char **response, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = (char*)CoTaskMemAlloc(length * sizeof(char));
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Example of use:
char *response;
WriteToInstrumentWrap3("cmd1", &response, 1);
std::cout << "CoTaskMemFree char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with CoTaskMemFree!
CoTaskMemFree(response);
Simple method that calls WriteToInstrument, allocates the response buffer using a "private" "library" allocator and copies the result to an ansi string (char*). The caller must use the library deallocator MyLibraryFree to free the allocated memory. The response is always \0 terminated.
// Free method used by users of the library
void MyLibraryFree(void *p)
{
free(p);
}
// The memory is allocated through a proprietary allocator of the library. Use MyLibraryFree() to free it.
int WriteToInstrumentWrap4(const char *command, char **response, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = (char*)malloc(length);
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Example of use:
char *response;
WriteToInstrumentWrap4("cmd1", &response, 1);
std::cout << "Simple MyLibraryFree char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with the MyLibraryFree() method
MyLibraryFree(response);
Simple method that calls WriteToInstrument, allocates the response buffer using a settable (through the SetLibraryAllocator/SetLibraryFree methods) allocator (there is a default that is used if no special allocator is selected) and copies the result to an ansi string (char*). The caller must use the library deallocator LibraryFree (that uses the allocator selected by SetLibraryFree) to free the allocated memory or if it has setted a different allocator, it can directly use that deallocator. The response is always \0 terminated.
void *(*libraryAllocator)(size_t length) = malloc;
void (*libraryFree)(void *p) = free;
// Free method used by library
void SetLibraryAllocator(void *(*allocator)(size_t length))
{
libraryAllocator = allocator;
}
// Free method used by library
void SetLibraryFree(void (*free)(void *p))
{
libraryFree = free;
}
// Free method used by library
void LibraryFree(void *p)
{
libraryFree(p);
}
// The memory is allocated through the allocator specified by SetLibraryAllocator (default the malloc of the dll)
// You can use LibraryFree to free it, or change the SetLibraryAllocator and the SetLibraryFree with an allocator
// of your choosing and then use your free.
int WriteToInstrumentWrap5(const char *command, char **response, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = (char*)libraryAllocator(length);
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Example of use:
void* MyLocalAlloc(size_t size)
{
return LocalAlloc(0, size);
}
void MyLocalFree(void *p)
{
LocalFree(p);
}
and then:
// Using the main program malloc/free
SetLibraryAllocator(malloc);
SetLibraryFree(free);
char *response;
WriteToInstrumentWrap5("cmd1", &response, 1);
std::cout << "SetLibraryAllocator(malloc) char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Here I'm using the main program free, because the allocator has been set to malloc
free(response);
or
// Using the Windows LocalAlloc/LocalFree. Note that we need to use an intermediate method to call them because
// they have a different signature (stdcall instead of cdecl and an additional parameter for LocalAlloc)
SetLibraryAllocator(MyLocalAlloc);
SetLibraryFree(MyLocalFree);
char *response;
WriteToInstrumentWrap5("cmd1", &response, 1);
std::cout << "SetLibraryAllocator(LocalAlloc) char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Here I'm using diretly the Windows API LocalFree
LocalFree(response);
More complex method that calls WriteToInstrument but has as a parameter an allocator that will be used to allocate the response buffer. There is an addition parameter par that will be passed to the allocator. The method then will copy the result as an ansi string (char*). The caller must free the memory using a specific deallocator based on the allocator used. The response is always \0 terminated.
// allocator is a function that will be used for allocating the memory. par will be passed as a parameter to allocator(length, par)
// the length of allocator is in number of elements, *not in bytes!*
int WriteToInstrumentWrap6(const char *command, char **response, char *(*allocator)(size_t length, void *par), void *par, int stage)
{
auto str1 = gcnew String(command);
String ^str2 = nullptr;
int res = WriteToInstrument(str1, str2, 5);
pin_ptr<const Char> ppchar = PtrToStringChars(str2);
const wchar_t *pch = const_cast<wchar_t*>(ppchar);
// length includes the terminating \0
size_t length = WideCharToMultiByte(CP_ACP, 0, pch, -1, nullptr, 0, nullptr, nullptr);
*response = allocator(length, par);
WideCharToMultiByte(CP_ACP, 0, pch, -1, *response, length, nullptr, nullptr);
return res;
}
Examples of use (multiple allocator showed: vector<>, malloc, new[], unique_ptr<>):
Note the use of the par parameter.
template<typename T>
T* vector_allocator(size_t length, void *par)
{
std::vector<T> *pvector = static_cast<std::vector<T>*>(par);
pvector->resize(length);
return pvector->data();
}
template<typename T>
T* malloc_allocator(size_t length, void *par)
{
return (T*)malloc(length * sizeof(T));
}
template<typename T>
T* new_allocator(size_t length, void *par)
{
return new T[length];
}
template<typename T>
T* uniqueptr_allocator(size_t length, void *par)
{
std::unique_ptr<T[]> *pp = static_cast<std::unique_ptr<T[]>*>(par);
pp->reset(new T[length]);
return pp->get();
}
and then (note the fact that sometimes one of the parameter passed to WriteToInstrumentWrap6 is useless because we already have a pointer to the buffer):
{
std::vector<char> response;
char *useless;
WriteToInstrumentWrap6("cmd1", &useless, vector_allocator<char>, &response, 1);
std::cout << "vector char: " << response.data() << ", used length: " << response.size() << std::endl;
// The memory is automatically freed by std::vector<>
}
{
char *response;
WriteToInstrumentWrap6("cmd1", &response, malloc_allocator<char>, nullptr, 1);
std::cout << "malloc char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with free
free(response);
}
{
char *response;
WriteToInstrumentWrap6("cmd1", &response, new_allocator<char>, nullptr, 1);
std::cout << "new[] char: " << response << ", used length: " << strlen(response) + 1 << std::endl;
// Must free with delete[]
delete[] response;
}
{
std::unique_ptr<char[]> response;
char *useless;
WriteToInstrumentWrap6("cmd1", &useless, uniqueptr_allocator<char>, &response, 1);
std::cout << "unique_ptr<> char: " << response.get() << ", used length: " << strlen(response.get()) + 1 << std::endl;
// The memory is automatically freed by std::unique_ptr<>
}
In a high level overflow, you need C++/CLI (C++ that is using managed .NET code)
OK, you take a handle of type System::String (.NET) and get it's length property. Use that value to allocate a new buffer of size + 2 chars using malloc and memset to zero it out. lock the string, copy its contents and unlock it again.
There is a conversion operator to go from System::String ^ to MFC's CString, if that helps. It will make the code a single liner
Yes. But again,
CString unmanaged = CString(System::String ^) does all that for you.
Last night I was working on cleaning up some code I found that was a port of an old game I used to play. One of the tricks I used to clean up data was to get rid of the home-brew DataOffsetAttribute that was made for a struct and just make it a plain jane struct and then I can (later) convert it to something a little more useful. Kind of like a person would do with a DataLayer. This worked really good for fixed sized data from a file. Using this method I was about to convert between the two "data types".
public static byte[] ToByteArray<T>(this T dataStructure) where T : struct
{
int size = Marshal.SizeOf(dataStructure);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(dataStructure, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
public static T MarshalAs<T>(this byte[] rawDataStructure) where T : struct
{
var type = typeof(T);
int size = Marshal.SizeOf(type);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(rawDataStructure, 0, ptr, size);
T structure = (T)Marshal.PtrToStructure(ptr, type);
Marshal.FreeHGlobal(ptr);
return structure;
}
So then I started to wonder if this would work on another project I worked on a long time ago where the data was variable. Here is what I was hoping my data structure would look like
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RfidReaderResponse
{
public byte PreambleA;
public byte PreambleB;
public byte Length;
public byte LengthLRC;
public ushort ReaderId;
public byte CommandId;
public byte ErrorCode;
public byte[] Data;
public byte LRC;
}
I would probably combine the preamble bytes into a ushort and check if it is a specific value... but that is a different discussion. I have 3 known good responses that I used for testing back in the day. I put those into linqpad as well as my hopeful datastructure. So here is what I am currently using for testing
void Main()
{
var readerResponses = new string[]
{
//data is null because of error EC
"AA-BB-05-05-02-39-0C-EC-21",
//data is 44-00
"AA-BB-07-07-02-39-0C-00-44-00-8B",
//data is 44-00-20-04-13-5E-1A-A4-33-80
"AA-BB-0F-0F-02-39-10-00-44-00-20-04-13-5E-1A-A4-33-80-FB",
};
readerResponses
.Select(x=> x.ToByteArray().MarshalAs<RfidReaderResponse>())
.Dump();
}
now if I comment out the last two fields of the struct I get back what I expect for the first part of the response, but I am just lost on how to get back the data portion. I would prefer to have it as I have it with some magical attribute that the Marshaler understands but so far I can't figure it out. I have tried
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RfidReaderResponse
{
public byte PreambleA;
public byte PreambleB;
public byte Length;
public byte LengthLRC;
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
public IntPtr Data;
}
and
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RfidReaderResponse
{
public byte PreambleA;
public byte PreambleB;
public byte Length;
public byte LengthLRC;
public ushort ReaderId;
public byte CommandId;
public byte ErrorCode;
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_I1)]
public byte[] Data;
public byte LRC;
}
after a few hours of research. Both of which didn't work stating that my method couldn't make a size for my structure. So I dunno. What am I doing wrong?
EDIT
forgot the method for converting hex strings to byte arrays
public static byte[] ToByteArray(this string hexString)
{
hexString = System.Text.RegularExpressions.Regex.Replace(hexString, "[^0-9A-F.]", "").ToUpper();
if (hexString.Length % 2 == 1)
throw new Exception("The binary key cannot have an odd number of digits");
byte[] arr = new byte[hexString.Length >> 1];
for (int i = 0; i < hexString.Length >> 1; ++i)
{
arr[i] = (byte)((GetHexVal(hexString[i << 1]) << 4) + (GetHexVal(hexString[(i << 1) + 1])));
}
return arr;
}
private static int GetHexVal(char hex)
{
int val = (int)hex;
return val - (val < 58 ? 48 : 55);
}
If I do
char c = 'A';
byte[] b = BitConverter.GetBytes(c);
Length of b is 2.
However, if I have the following struct for interop purposes
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct MyStruct
{
int i;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
char[] c;
public int TheInt
{
get { return i; }
set { i = value; }
}
public string TheString
{
get { return new string(c); }
set { c = value.ToCharArray(); }
}
}
then do
MyStruct m = new MyStruct();
m.TheInt = 10;
m.TheString = "Balloons";
int mSize = Marshal.SizeOf(m);
mSize is 12, not 20 as I expected.
MSDN says char storage is 2 bytes.
The first example supports this.
Am I doing something wrong with my struct?
Am I missing something?
Because you are are marshaling, and by default, a char will get marshalled to an ANSI char instead of a Unicode char. So "balloon" is 8 characters, which is 8 bytes when ANSI encoded, plus 4 bytes for your int, which is 12.
If you want the size to be 20 for marshalling, change your StructLayout and set the ChatSet to Unicode:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
Now you will have your struct size as 20.
MSDN says char storage is 2 bytes.
That is true when we are talking about a CLR char, but not in the context of marshalling.
char is 2 bytes or 16-bit Unicode character (U +0000 to U +ffff)
char [] is a pointer type
int is 4 bytes
hence, about marshalling, I would pick vcsjones' answer.
This is My Code to user a long variable with it bytes, but when program runs, Exception Happens and show these:
An unhandled exception of type 'System.TypeLoadException' occurred in Test.exe
Additional information: Could not load type 'Test.MyU32' from assembly 'Test,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an
object field at offset 0 that is incorrectly aligned or overlapped by a non-object
field.
[StructLayout(LayoutKind.Explicit)]
public struct MyU32
{
[FieldOffset(0)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] Bytes;
[FieldOffset(0)]
public long Value;
}
Please help me how to handle it!
Your code case doesn't work because you're overlapping a reference and a value type(a 64 bit int). You can overlap different value types and different references, but you can't mix them.
But even when they work, such low level hacks are usually a bad idea in C#. I recommend using properties which do the transformation instead of low level unions.
Perhaps what you actually want is:
internal static class ByteIntegerConverter
{
public static UInt32 LoadLittleEndian32(byte[] buf, int offset)
{
return
(UInt32)(buf[offset + 0])
| (((UInt32)(buf[offset + 1])) << 8)
| (((UInt32)(buf[offset + 2])) << 16)
| (((UInt32)(buf[offset + 3])) << 24);
}
public static void StoreLittleEndian32(byte[] buf, int offset, UInt32 value)
{
buf[offset + 0] = (byte)value;
buf[offset + 1] = (byte)(value >> 8);
buf[offset + 2] = (byte)(value >> 16);
buf[offset + 3] = (byte)(value >> 24);
}
}
UInt32 value = ByteIntegerConverter.LoadLittleEndian32(buf, offset);
// do something with `value`
ByteIntegerConverter.StoreLittleEndian32(buf, offset, value);
This always uses little endian regardless of the computer's native endianness. If you want native endainness you could check with BitConverter.IsLittleEndian and use different shift constants if it is big endian.
I'm not perfectly sure, but I think the problem is caused by the overlap between a value type and a reference type. It should be possible to overlap only value types. Because if it was possible to overlap value types & reference types, you could change the reference directly. For obvious safety reason, it's not possible.
As byte[] is a reference type (as all arrays in .NET). You can't have Value overlapping Bytes.
If you are used to C, your structure (without the explicit layout) would be "similar" to:
struct MyU32
{
byte* Bytes;
long Value;
}
but it is not similar to:
struct MyU32
{
byte Bytes[4];
long Value;
}
I solve my problem
Tanx to All bodies that put time on it.
public struct MyU32
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] Bytes;
public uint Value
{
get { return ((uint)Bytes[0] + Bytes[1] << 8 + Bytes[2] << 16 + Bytes[3] << 24); }
set
{
Bytes[0] = (byte)(value & 0xFF);
Bytes[1] = (byte)(value>>8 & 0xFF);
Bytes[2] = (byte)(value>>16 & 0xFF);
Bytes[3] = (byte)(value>>24 & 0xFF);
}
}
}
I have an old client side application that is writing to named pipes using a VC 6.0 dll, and I have been asked to write an C# application to read the named pipe and process the request. I am able to receive the data in a byte array, but can't get the data in the pipe to match with the structures i have defined in C#.
old C struct
typedef struct
{
WORD WtYPE;
AB objEmbededStruct1;
BB objEmbededStruct2;
char szString[13];
union
{
char szString1[25];
char szSTring2[45];
char szString3[134];
}
BOOL bExist;
} myStruct1;
typedef struct
{
char szThisString1[2];
int iFlag1;
char szThisString2[11];
}AB;
typedef struct
{
HANDLE hEvents[2];
DWORD dw;
int ithisFlag;
}BB;
I have tried parsing the byte array, but the data is not where I expect it to be. For instance, the first string in the first embedded structure (AB) starts at byte[4] as opposed to byte[2] since a word maps to an unsigned int16. Then the first integer in the AB struct starts at byte[8] as opposed to byte[6]. So, is there a more efficient way to retrieve the data from the pipe and put it into the structure, or is parsing by bytes the correct way? If parsing the bytes is how it should be done, then what am I missing when trying to map where the data should be?
Thanks
after a suggestion from LU RD, i was able to put this solution together:
definition of VC 6 structs:
typedef struct
{
WORD WtYPE;
AB objEmbededStruct1;
BB objEmbededStruct2;
char szString[13];
union
{
char szString1[25];
char szSTring2[45];
char szString3[134];
}
BOOL bExist;
} myStruct1;
typedef struct
{
char szThisString1[2];
int iFlag1;
char szThisString2[11];
}AB;
typedef struct
{
HANDLE hEvents[2];
DWORD dw;
int ithisFlag;
}BB;
I needed to the using statement to the c# code:
using System.Runtime.InteropServices;
The struct definition in c# looks like this:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct RM_CLIENT_DATA
{
public UInt16 wtype;
AB objEmbededStruct1;
BB objEmbededStruct2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)]
char[] szString;
public Data objMyUnion //this is the structure substituted for the union
public int bExist;
}
//Union struct
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public struct Data
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = FILE_NAME_LENGTH + 1)]
[FieldOffset(0)]
public char[] szString1
[MarshalAs(UnmanagedType.ByValArray, SizeConst = DOCS_LEN + 1)]
[FieldOffset(0)]
public char[] szString2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 134)]
[FieldOffset(0)]
public char[] szString3;
}
[structLayout(LayoutKind.Sequential, charset = charSet.Ansi)]
public struct AB
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
char[] szThisString1;
int IFlag1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
char[] szThisString2;
}
[structLayout(LayoutKind.Sequential, charset = charSet.Ansi)]
public struct BB
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public UInt32[] hEvents;
public UInt32 dw;
public int ithisFlag;
}
the code to pull the stream from the named pipe into the struct looks like this:
const int BUFFER_SIZE=4096;
byte[] typeData = new byte[BUFFER_SIZE];
int iMaxData = ioStream.Read(typeData, 0, BUFFER_SIZE);
GCHandle objHandle = new GCHandle();
iMaxData = ioStream.Read(typeData, 0, BUFFER_SIZE); //read the stream
try
{
objHandle = GCHandle.Alloc(typeData, GCHandleType.Pinned);
objData = (RM_CLIENT_DATA)Marshal.PtrToStructure(objHandle.
AddrOfPinnedObject(),typeof(RM_CLIENT_DATA));
}
catch (Exception ex)
{
ErrorCode = -6;
ErrorMessage = string.Format("ReadMessageToGenericStruct: Error: {0}
attempting to move data into RM_CLIENT_DATA struct.", ex.Message);
return bResult;
}
finally
{
objHandle.Free();
}
to use the char[]'s in the structure, I used:
string myWorkString = new string( objData.szString);
to return the data to the pipe -- I reversed the process:
//get the size of the filled structure
int iStructSize = Marshal.SizeOf(objData);
//allocate the byte array to write the structure into
byte[] outBuffer = new byte[ iStructSize];
//create the GCHandle variable
GCHanlde objHandle = new GCHandle();
try{
//allocate a handle for the byte array
objHandle = GCHandle.Alloc(outBuffer, GCHandleType.Pinned);
//move your data to the byte array
Marshal.StructureToPtr( objData, objHandle.AddrOfPinnedObject(), false);
}
catch (Execeptiion ex)
{
//write error message here
}
finally
{
//free the handle
objHandle.Free();
}
//write the byte array to the stream
try
{
ioStream.Write(outBuffer, 0, iStructSize);
}
catch (Exception ex)
{
//write error message here
}
ioStream.Flush();
ioStream.Close();
the following link was a big help, thanks to that author as well!
Mastering c# structs