How can I display a QByteArray in a TextArea? - qml

I'm trying to make an encryption/decryption tool for some configuration files that allows for in-place editing of file contents (via a GUI). The encryption/decryption process is working fine, but I've run into an issue displaying the content of the file for editing.
The file being decrypted contains hex values that represent an integer value (4 bytes), followed by a null terminated string. Example of this file could be the following (int value of 1 and string value of Test).
"010000005465737400"
The decrypted contents are stored into a QByteArray that is then displayed using a TextArea element. The problem is the TextArea stops displaying text when a 0x00 value is reached. I expected it to have displayed a 0 instead of stopping.
Is there a way to display the byte array properly?

In the following, I implement testData() to generate an ArrayBuffer with your 9 bytes. Then I implement byteArraytoHexString() which uses byteLength to iterate through the data and convert it to a hex string:
import QtQuick
import QtQuick.Controls
Page {
Frame {
width: parent.width
TextEdit {
text: arrayBufferToHexString(testData())
}
}
function testData() {
let codes = [
0x01, 0x00, 0x00, 0x00,
0x54, 0x65, 0x73, 0x74,
0x00
];
return codesToArrayByteArray(codes);
}
function codesToArrayByteArray(codes) {
let byteArray = new ArrayBuffer(codes.length);
for (let i = 0; i < codes.length; i++)
byteArray[i] = codes[i];
return byteArray;
}
function arrayBufferToHexString(byteArray) {
let result = [ ];
for (let i = 0; i < byteArray.byteLength; i++)
result.push(byteArray[i].toString(16).padStart(2, "0"));
return result.join("");
}
}
You can Try it Online!
I suspect the issue you're having is you treat the QByteArray as a string not an ArrayBuffer. Definitely, if it's converted to a string, then, the 0x00 byte may be considered as a NUL terminator and will prematurely terminate the data.
Because of your specific use case, you definitely want to avoid any string operation since the raw data must not be treated as a string.
Since you mentioned QByteArray, you would have C++ at your disposal.
I would highly recommend that you implement QByteArray to hex-string and hex-string back to QByteArray in C++ but expose these as Q_INVOKABLE methods in QML, e.g.
Q_INVOKABLE QString byteArrayToHexString(const QByteArray& buffer)
{
return QString::fromUtf8(byteArray.toHex());
}
Q_INVOKABLE QByteArray hexStringToByteArray(const QString& hexString)
{
returun QByteArray::fromHex(hexString.toUtf8());
}
If you do that, then it would enable QML to do the conversions in C++.

Related

How to efficiently marshal array of objects to native function using C++/CLI

I have array of objects holding primitive types and enums; how do I marshal a pointer to this data to a native function with the signature native_func(void* ptr[]).
array<System::Object^>^ values = gcnew array<System::Object>(64);
// ... populate the managed array with primitives ...
// data is pinned and won't be moved by the GC
pin_ptr<object> pinned = &values[0];
// not sure what do here... data is corrupted in the native code
native_func((void**)pinned);
Thanks!
EDIT. My second attempt was to do the following:
pin_ptr<object> pinned = &values[0];
void* testArray[64];
for (auto i = 0; i < values->Length; i++)
{
testArray[i] = (void*)Marshal::UnsafeAddrOfPinnedArrayElement(values, i);
}
native_func(testArray);
Now, the addresses stored in testArray are getting passed correctly to the native side but the contents of the memory is not what I am expecting. What am I doing wrong now?
Enums are not blittable so marshaling an array of objects require special consideration (i.e. you can't just pin_ptr the array and pass it over the native/managed boundary). I decided to use a VARIANT to hold the primitive & enum values and did so with the following code:
// allocate a managed array of size 64 (it's enough for my application)
array<System::Object^>^ values = gcnew array<System::Object>(64);
// stack allocate a native array of size 64
VARIANT nativeValueArray[64] = {};
// ... populate the managed array ...
for (auto i = 0; i < values->Length; i++)
{
Marshal::GetNativeVariantForObject(values[i], (IntPtr)(void*)&nativeValueArray[i]);
}
// pass the array of native VARIANTS to the native function "native_function"
native_function(nativeValueArray);
The native function's signature became
void native_function(VARIANT values[]);
There might be a more efficient way to do this but this is what I was able to come up with. Please let me know if you have a more efficient way to accomplish what am I doing.

C++/CLI Conversion of byte* to Managed Byte[]

I'm in a C++/CLI project, and I have a byte* variable that I want to fully convert into a managed array<byte> as efficiently as possible.
Currently, the only way that I've seen is to manually create the managed array<byte> object, and then copy individual bytes from the byte* variable, as shown below:
void Foo(byte* source, int bytesCount)
{
auto buffer = gcnew array<byte>(bytesCount);
for (int i = 0; i < bytesCount; ++i)
{
buffer[i] = source[i];
}
}
Is there any other way to do this more efficiently? Ideally, to not have to copy the memory at all.
If not, is there any way to do this more cleanly?
You can't create a managed array from an unmanaged buffer without copying.
However, you don't need to copy the individual bytes in a loop, though. You can pin the managed array (see pin_ptr) and then copy the bytes directly from the unmanaged buffer into the memory of the managed array, such as with memcpy() or equivalent.
void Foo(byte* source, int bytesCount)
{
auto buffer = gcnew array<byte>(bytesCount);
{
pin_ptr<byte> p = &buffer[0];
byte *cp = p;
memcpy(cp, source, bytesCount);
}
// use buffer as needed...
}

fatfs f_write returns FR_DISK_ERR when passing a pointer to data in a mail queue

I'm trying to use FreeRTOS to write ADC data to SD card on the STM32F7 and I'm using V1 of the CMSIS-RTOS API. I'm using mail queues and I have a struct that holds an array.
typedef struct
{
uint16_t data[2048];
} ADC_DATA;
on the ADC half/Full complete interrupts, I add the data to the queue and I have a consumer task that writes this data to the sd card. My issue is in my Consumer Task, I have to do a memcpy to another array and then write the contents of that array to the sd card.
void vConsumer(void const * argument)
{
ADC_DATA *rx_data;
for(;;)
{
writeEvent = osMailGet(adcDataMailId, osWaitForever);
if(writeEvent.status == osEventMail)
{
// write Data to SD
rx_data = writeEvent.value.p;
memcpy(sd_buff, rx_data->data, sizeof(sd_buff));
if(wav_write_result == FR_OK)
{
if( f_write(&wavFile, (uint8_t *)sd_buff, SD_WRITE_BUF_SIZE, (void*)&bytes_written) == FR_OK)
{
file_size+=bytes_written;
}
}
osMailFree(adcDataMailId, rx_data);
}
}
This works as intended but if I try to change this line to
f_write(&wavFile, (uint8_t *)rx_data->data, SD_WRITE_BUF_SIZE, (void*)&bytes_written) == FR_OK)
so as to get rid of the memcpy, f_write returns FR_DISK_ERR. Can anyone help shine a light on why this happens, I feel like the extra memcpy is useless and you should just be able to pass the pointer to the queue straight to f_write.
So just a few thoughts here:
memcpy
Usually I copy only the necessary amount of data. If I have the size of the actual data I'll add a boundary check and pass it to memcpy.
Your problem
I am just guessing here, but if you check the struct definition, the data field has the type uint16_t and you cast it to a byte pointer. Also the FatFs documentation expects a void* for the type of buf.
EDIT: Could you post more details of sd_buff

C/C++ DLL: Converting a const uint8_t to a String

I haven't seen C++ code in more than 10 years and now I'm in the need of developing a very small DLL to use the Ping class (System::Net::NetworkInformation) to make a ping to some remoteAddress.
The argument where I'm receiving the remoteAddress is a FREObject which then needs to be transformed into a const uint8_t *. The previous is mandatory and I can't change anything from it. The remoteAddress has to be received as a FREObject and later be transformed in a const uint8_t *.
The problem I'm having is that I have to pass a String^ to the Ping class and not a const uint8_t * and I have no clue of how to convert my const uint8_t * to a String^. Do you have any ideas?
Next is part of my code:
// argv[ARG_IP_ADDRESS_ARGUMENT holds the remoteAddress value.
uint32_t nativeCharArrayLength = 0;
const uint8_t * nativeCharArray = NULL;
FREResult status = FREGetObjectAsUTF8(argv[ARG_IP_ADDRESS_ARGUMENT], &nativeCharArrayLength, &nativeCharArray);
Basically the FREGetObjectAsUTF8 function fills the nativeCharArray array with the value of argv[ARG_IP_ADDRESS_ARGUMENT] and returns the array's length in nativeCharArrayLength. Also, the string uses UTF-8 encoding terminates with the null character.
My next problem would be to convert a String^ back to a const uint8_t *. If you can help with this as well I would really appreciate it.
As I said before, non of this is changeable and I have no idea of how to change nativeCharArray to a String^. Any advice will help.
PS: Also, the purpose of this DLL is to use it as an ANE (Air Native Extension) for my Adobe Air app.
You'll need UTF8Encoding to convert the bytes to characters. It has methods that take pointers, you'll want to take advantage of that. You first need to count the number of characters in the converted string, then allocate an array to store the converted characters, then you can turn it into System::String. Like this:
auto converter = gcnew System::Text::UTF8Encoding;
auto chars = converter->GetCharCount((Byte*)nativeCharArray, nativeCharArrayLength-1);
auto buffer = gcnew array<Char>(chars);
pin_ptr<Char> pbuffer = &buffer[0];
converter->GetChars((Byte*)nativeCharArray, nativeCharArrayLength-1, pbuffer, chars);
String^ result = gcnew String(buffer);
Note that the -1 on nativeCharArrayLength compensates for the zero terminator being included in the value.

Convert System::String to std::string in UTF8, which later converted to char* as c_str

I have a System::String^ variable in C++ code. This variable should be converted to std::string which is later converted to const char* via c_str.
// original string
System::String^ path = ...;
// convert to std::string
msclr::interop::marshal_context context;
std::string filename(context.marshal_as<std::string>(path));
// call API function that internally connects to sqlite3 using sqlite3_open as
// sqlite3_open(filename.c_str())
// https://www.sqlite.org/c3ref/open.html -
// const char *filename, /* Database filename (UTF-8) */
doCalculation(filename)
It works well with the ASCII paths, but fails if the path contains non-latin characters.
So Somehow I need to convert marshalled std::string from current implementation (ASCII?) to UTF8.
I've tried
std::wstring dbPath(context.marshal_as<std::wstring>(path));
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> convert;
std::string dbPathU8 = convert.to_bytes(dbPath);
but it does not work.
What you want to do is use the .Net methods to convert directly to UTF-8.
The available methods in the Encoding class aren't exactly what you're looking for (direct from managed String to unmanaged string or byte array), so we'll need an intermediary and some manual copying.
String^ path = ...;
// First, convert to a managed array of the bytes you want.
array<Byte>^ bytes = Encoding::UTF8->GetBytes(path);
// Then, copy those bytes from the managed byte array to an unmanaged string.
std::string str;
str.resize(bytes->Length);
Marshal::Copy(bytes, 0, IntPtr(str.data()), bytes->Length);
// OR, copy directly to the char* you want eventually.
char* chars = new char[bytes->Length + 1]; // or malloc(), or whatever.
Marshal::Copy(bytes, 0, IntPtr(chars), bytes->Length);
chars[bytes->Length] = '\0'; // null terminate.
// don't forget to free the buffer when you're done with it!
There are several GetBytes variants available, but the parameters to them seem to either be both managed, or both unmanaged. (String^ and array^, or char* and byte*, but not String^ and byte*.) Therefore, we have the Encoding class create a managed byte array, then we use the Marshal::Copy method to copy those bytes either to the unmanaged string object, or directly to a char*.