通过 C++ DLL 将 Arduino 字符串转换为 Excel BSTR 的随机字符
Random characters from casting Arduino string to Excel BSTR through c++ DLL
所以在一些 Arduino 代码中我是 Serial.print
一些这样的数字 (var1=##,var2=##,var3=##
)。我有一个 C++ DLL,我正在制作它来获取此信息并将其解析为这样的变体数组 ("var1=",##.##,"var2=",##.##,"var3=",##.##
),将字符串部分存储为变体 BSTR,将 #
s 存储为变体双打。这个变体数组来自 Excel,我的 C++ DLL 的目的是允许串行通信进出 Excel 和 Arduino 板。
我的问题是,我没有像我希望的那样取回信息,而是在最后收到了很多额外的乱码,无法弄清楚它们的来源。我之前包括了我的相关代码,省略了串行通信,因为我知道那部分肯定有效。现在,Arduino 代码只是发送 (##,##,##
) 然后 Serial.println();
使事情变得更简单,因为没有字符串,只有数字。
VBA代码:
'collects all rows available currently up to 1000(global numRows) lines each time, stores result in dataArray
Public Function getAllData()
Dim stringArray() As Variant
Dim variantArray() As Variant
ReDim stringArray(0 To 3000) As Variant
ReDim variantArray(0 To 3000) As Variant
If Main.dataCollectionActive Then
Dim staringDataRow As Integer
staringDataRow = currentDataRow
Dim returnValue As Long
returnValue = GetAllDataTypes(stringArray(), variantArray(), currentDataRow)
If Not (returnValue = 0) Then
If Not (returnValue = -1) Then
If printWhileCollectingData = True Then
' Call printVariantData
Dim row As Integer
Dim col As Integer
For row = startingDataRow To (currentDataRow - 1)
Main.dataBox.Text = Main.dataBox.Text & stringArray(row)
For col = 0 To (getNumColumns() - 1)
Main.Cells(row + startingRow, col + startingCol).Value = variantArray(col + (row * getNumColumns()))
Next col
Next row
End If
End If
End If
'only stoped in Main sheets code for stopDataButton or if the array has reached its limit
If Not ((currentDataRow * getNumColumns) = UBound(variantArray())) Then
Application.OnTime Now + TimeValue("00:00:01"), "getAllData"
End If
Main.Range("$L").Value = currentDataRow
End If
End Function
C++ DLL代码:
//end1 comes before end2, so end1 = i-1 and end2 = i characters
DLL_EXPORT bool WINAPI isEndLine(char end1, char end2){
//check for CRLF
if (end1 == '\r') {
if (end2 == '\n') {
return true;
}
}
//check for normal endLine [=12=]
else if ((end1 == '[=12=]')) {
return true;
}
//check for users own endLine character
else{
for(int i = 0; i < MAX_ENDLINE_LENGTH; i++){
if (end1 == endLine[i]){
return true;
}
}
}
return false;
}
//reads a single character from buffer, if there is nothing returned then the global, bufferAvailable is set to false, so that no more characters are asked for, for now
DLL_EXPORT char WINAPI readCharFromSerial() {
char dataChar[1];
DWORD dwBytesRead = 0; //number of data bytes read in
if(!ReadFile(hSerial, dataChar, 1, &dwBytesRead, NULL)) { //gets data if successful, if not then notifies user
ErrorExit("ReadFile, reading a character");
}
if (dwBytesRead == 0) {
bufferAvailable = false;
}
return dataChar[0]; //returns read in data
}
//reads a single line by pulling one character at a time until it finds the end of line character, if the buffer has characters in it
DLL_EXPORT void WINAPI readLineFromSerialPort(char* line, int length) {
bufferAvailable = true;
int i = 0;
line[i] = readCharFromSerial();
i++;
if (bufferAvailable){
do{
line[i] = readCharFromSerial();
i++;
}while((!isEndLine(line[i-2], line[i-1])) && (i < length));
if(line[i-2] == '\r') {
line[i-2] = '\n';
line[i-1] = '[=12=]';
}
if(!(i<length)){
MessageBoxA(NULL, (LPCSTR)("string length not long enough"), TEXT("readLineFromSerialPort"), MB_OK);
}
}
}
DLL_EXPORT void WINAPI PlaceDblInVarDbl(varArr* VD, double data) {
(VD->ptr[VD->index]).vt = VT_R8; //FIXME add function to do this
(VD->ptr[VD->index]).dblVal = data;
VD->index++;
}
DLL_EXPORT void WINAPI PlaceCharPtrInVarBstr(varArr* VD, char* cString) {
BSTR bstr = new OLECHAR[MAX_DATA_LENGTH];
const char* Cstring = (const char*)cString;
mbstowcs(bstr, Cstring, MAX_DATA_LENGTH);
VD->ptr[VD->index].vt = VT_BSTR;
SysReAllocString(&((VD->ptr[VD->index]).bstrVal), bstr);
VD->index++;
}
DLL_EXPORT int WINAPI ParseCharPtrToVarBstrAndVarDbl(varArr* VD, char* dataString) {
//FIXME perhaps make an array of chars for strings that are allowed without being coppied, this would be added to the if begging with !isdigit (like the '.')
bool hasLetters = false;
int endIndex = 0;
int startIndex = 0;
// MessageBoxA(NULL, (LPCSTR)("entered"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
while (!(isEndLine(dataString[endIndex], dataString[endIndex+1]))) {
hasLetters = false;
startIndex = endIndex;
while (!isDelim(dataString[endIndex]) && (!(isEndLine(dataString[endIndex-1], dataString[endIndex])))) {
if (!(isdigit(dataString[endIndex])) && !(dataString[endIndex] == ' ') && !(dataString[endIndex] == '.')) {
hasLetters = true;
}
endIndex++;
}
// MessageBoxA(NULL, (LPCSTR)("delimeter found"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
if((startIndex + 1 == endIndex) || (startIndex == endIndex)/* && !(isEndLine(dataString[endIndex-1], dataString[endIndex]))*/) {
//FIXME odd way to fix CRLF problem
// MessageBoxA(NULL, (LPCSTR)("triggered if"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
}
else if (hasLetters) { //string
char smallerString[MAX_DATA_LENGTH];
const char* DStr = (const char*)(&(dataString[startIndex]));
strncpy(smallerString, DStr, endIndex-startIndex+1);
smallerString[endIndex-startIndex+1] = '[=12=]';
PlaceCharPtrInVarBstr(VD, smallerString);
// MessageBoxA(NULL, (LPCSTR)("triggered hasLetters"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
}
else { //double
//FIXME remove whitespace
char* start = &dataString[startIndex];
char* eOS = &(dataString[endIndex]);
char** endOfString = &eOS;
double data = strtod(start, endOfString);
//FIXME do some error checking
PlaceDblInVarDbl(VD, data);
// MessageBoxA(NULL, (LPCSTR)("triggered does not hasLetters"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
}
endIndex++;
}
//, MessageBoxA(NULL, (LPCSTR)("exited"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
return VD->startingIndex - VD->index;
}
//first read a lines from the serial port and then parse it into dataArray, does this until there is no data in buffer or array is full
//returns how many rows that it read
DLL_EXPORT int WINAPI GetAllDataTypes(LPSAFEARRAY* unparsedData, LPSAFEARRAY* parsedData, int* currentDataRow) {
bufferAvailable = true;
varArr UPD;
OpenVariantSafeArray(&UPD, unparsedData, *currentDataRow); if (UPD.failed) { return -1; }
varArr PD;
OpenVariantSafeArray(&PD, parsedData, *currentDataRow); if (PD.failed) { return -1; }
while (bufferAvailable && ((PD.index + 10) < PD.uBound)) {
char dataString[MAX_DATA_LENGTH];
readLineFromSerialPort(dataString, MAX_DATA_LENGTH);
if (bufferAvailable) {
PlaceCharPtrInVarBstr(&UPD, dataString);
ParseCharPtrToVarBstrAndVarDbl(&PD, dataString);
}
}
CloseVariantSafeArray(&UPD, unparsedData); if (UPD.failed) { return -1; }
CloseVariantSafeArray(&PD, parsedData); if (PD.failed) { return -1; }
*currentDataRow += UPD.index - UPD.startingIndex;
bufferAvailable = true;
return UPD.index - UPD.startingIndex;
}
产生的错误:
这是在 Excel 中打印在文本框中时返回的未解析字符串。
0.00,0.01,0.00
0.10,0.02,0.01
0.20,0.03,0.02
0.30,0.04,0.03
0.40,0.05,0.04
0.50,0.06,0.05
0.60,0.07,0.06
0.70,0.08,0.07
0.80,0.09,0.08
0.90,0.10,0.09
1.00,0.10,0.10
然而,VBA for 循环在单元格中打印的已解析字符串。仍然有随机字符。看来我在 DLL 中的解析有问题,因为最后一个双精度数总是以字符串结束,然后它总是有随机字符串。
0.000 0.010 "0.00
"
- 0.100 0.020
"0.01
" - 0.200
0.030 "0.02
" -
0.300 0.040 "0.03
"
- 0.400 0.050
0.900 0.100 "0.09
"
- 0.000 0.100
"0.10
" - 0.100
0.110 "0.11
" -
0.200 0.120 "0.12
"
- 0.300 0.130
"0.13
" - 0.400
0.140 "0.14
" -
0.500 0.150 "0.15
"
- 0.600 0.160
"0.16
" -
"0.25
" - 0.600
0.240 "0.26
" -
0.700 0.250 "0.27
"
-
DLL代码中ParseCharPtrToVarBstrAndVarDbl()中的final endIndex++跳过字符串中的空终止符跳过定界符。解决方法是为字符串的末尾添加一个最终的 if 语句,如果它是 return.
所以在一些 Arduino 代码中我是 Serial.print
一些这样的数字 (var1=##,var2=##,var3=##
)。我有一个 C++ DLL,我正在制作它来获取此信息并将其解析为这样的变体数组 ("var1=",##.##,"var2=",##.##,"var3=",##.##
),将字符串部分存储为变体 BSTR,将 #
s 存储为变体双打。这个变体数组来自 Excel,我的 C++ DLL 的目的是允许串行通信进出 Excel 和 Arduino 板。
我的问题是,我没有像我希望的那样取回信息,而是在最后收到了很多额外的乱码,无法弄清楚它们的来源。我之前包括了我的相关代码,省略了串行通信,因为我知道那部分肯定有效。现在,Arduino 代码只是发送 (##,##,##
) 然后 Serial.println();
使事情变得更简单,因为没有字符串,只有数字。
VBA代码:
'collects all rows available currently up to 1000(global numRows) lines each time, stores result in dataArray
Public Function getAllData()
Dim stringArray() As Variant
Dim variantArray() As Variant
ReDim stringArray(0 To 3000) As Variant
ReDim variantArray(0 To 3000) As Variant
If Main.dataCollectionActive Then
Dim staringDataRow As Integer
staringDataRow = currentDataRow
Dim returnValue As Long
returnValue = GetAllDataTypes(stringArray(), variantArray(), currentDataRow)
If Not (returnValue = 0) Then
If Not (returnValue = -1) Then
If printWhileCollectingData = True Then
' Call printVariantData
Dim row As Integer
Dim col As Integer
For row = startingDataRow To (currentDataRow - 1)
Main.dataBox.Text = Main.dataBox.Text & stringArray(row)
For col = 0 To (getNumColumns() - 1)
Main.Cells(row + startingRow, col + startingCol).Value = variantArray(col + (row * getNumColumns()))
Next col
Next row
End If
End If
End If
'only stoped in Main sheets code for stopDataButton or if the array has reached its limit
If Not ((currentDataRow * getNumColumns) = UBound(variantArray())) Then
Application.OnTime Now + TimeValue("00:00:01"), "getAllData"
End If
Main.Range("$L").Value = currentDataRow
End If
End Function
C++ DLL代码:
//end1 comes before end2, so end1 = i-1 and end2 = i characters
DLL_EXPORT bool WINAPI isEndLine(char end1, char end2){
//check for CRLF
if (end1 == '\r') {
if (end2 == '\n') {
return true;
}
}
//check for normal endLine [=12=]
else if ((end1 == '[=12=]')) {
return true;
}
//check for users own endLine character
else{
for(int i = 0; i < MAX_ENDLINE_LENGTH; i++){
if (end1 == endLine[i]){
return true;
}
}
}
return false;
}
//reads a single character from buffer, if there is nothing returned then the global, bufferAvailable is set to false, so that no more characters are asked for, for now
DLL_EXPORT char WINAPI readCharFromSerial() {
char dataChar[1];
DWORD dwBytesRead = 0; //number of data bytes read in
if(!ReadFile(hSerial, dataChar, 1, &dwBytesRead, NULL)) { //gets data if successful, if not then notifies user
ErrorExit("ReadFile, reading a character");
}
if (dwBytesRead == 0) {
bufferAvailable = false;
}
return dataChar[0]; //returns read in data
}
//reads a single line by pulling one character at a time until it finds the end of line character, if the buffer has characters in it
DLL_EXPORT void WINAPI readLineFromSerialPort(char* line, int length) {
bufferAvailable = true;
int i = 0;
line[i] = readCharFromSerial();
i++;
if (bufferAvailable){
do{
line[i] = readCharFromSerial();
i++;
}while((!isEndLine(line[i-2], line[i-1])) && (i < length));
if(line[i-2] == '\r') {
line[i-2] = '\n';
line[i-1] = '[=12=]';
}
if(!(i<length)){
MessageBoxA(NULL, (LPCSTR)("string length not long enough"), TEXT("readLineFromSerialPort"), MB_OK);
}
}
}
DLL_EXPORT void WINAPI PlaceDblInVarDbl(varArr* VD, double data) {
(VD->ptr[VD->index]).vt = VT_R8; //FIXME add function to do this
(VD->ptr[VD->index]).dblVal = data;
VD->index++;
}
DLL_EXPORT void WINAPI PlaceCharPtrInVarBstr(varArr* VD, char* cString) {
BSTR bstr = new OLECHAR[MAX_DATA_LENGTH];
const char* Cstring = (const char*)cString;
mbstowcs(bstr, Cstring, MAX_DATA_LENGTH);
VD->ptr[VD->index].vt = VT_BSTR;
SysReAllocString(&((VD->ptr[VD->index]).bstrVal), bstr);
VD->index++;
}
DLL_EXPORT int WINAPI ParseCharPtrToVarBstrAndVarDbl(varArr* VD, char* dataString) {
//FIXME perhaps make an array of chars for strings that are allowed without being coppied, this would be added to the if begging with !isdigit (like the '.')
bool hasLetters = false;
int endIndex = 0;
int startIndex = 0;
// MessageBoxA(NULL, (LPCSTR)("entered"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
while (!(isEndLine(dataString[endIndex], dataString[endIndex+1]))) {
hasLetters = false;
startIndex = endIndex;
while (!isDelim(dataString[endIndex]) && (!(isEndLine(dataString[endIndex-1], dataString[endIndex])))) {
if (!(isdigit(dataString[endIndex])) && !(dataString[endIndex] == ' ') && !(dataString[endIndex] == '.')) {
hasLetters = true;
}
endIndex++;
}
// MessageBoxA(NULL, (LPCSTR)("delimeter found"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
if((startIndex + 1 == endIndex) || (startIndex == endIndex)/* && !(isEndLine(dataString[endIndex-1], dataString[endIndex]))*/) {
//FIXME odd way to fix CRLF problem
// MessageBoxA(NULL, (LPCSTR)("triggered if"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
}
else if (hasLetters) { //string
char smallerString[MAX_DATA_LENGTH];
const char* DStr = (const char*)(&(dataString[startIndex]));
strncpy(smallerString, DStr, endIndex-startIndex+1);
smallerString[endIndex-startIndex+1] = '[=12=]';
PlaceCharPtrInVarBstr(VD, smallerString);
// MessageBoxA(NULL, (LPCSTR)("triggered hasLetters"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
}
else { //double
//FIXME remove whitespace
char* start = &dataString[startIndex];
char* eOS = &(dataString[endIndex]);
char** endOfString = &eOS;
double data = strtod(start, endOfString);
//FIXME do some error checking
PlaceDblInVarDbl(VD, data);
// MessageBoxA(NULL, (LPCSTR)("triggered does not hasLetters"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
}
endIndex++;
}
//, MessageBoxA(NULL, (LPCSTR)("exited"), TEXT("ParseCharPtrToVarBstrAndVarDbl"), MB_OK);
return VD->startingIndex - VD->index;
}
//first read a lines from the serial port and then parse it into dataArray, does this until there is no data in buffer or array is full
//returns how many rows that it read
DLL_EXPORT int WINAPI GetAllDataTypes(LPSAFEARRAY* unparsedData, LPSAFEARRAY* parsedData, int* currentDataRow) {
bufferAvailable = true;
varArr UPD;
OpenVariantSafeArray(&UPD, unparsedData, *currentDataRow); if (UPD.failed) { return -1; }
varArr PD;
OpenVariantSafeArray(&PD, parsedData, *currentDataRow); if (PD.failed) { return -1; }
while (bufferAvailable && ((PD.index + 10) < PD.uBound)) {
char dataString[MAX_DATA_LENGTH];
readLineFromSerialPort(dataString, MAX_DATA_LENGTH);
if (bufferAvailable) {
PlaceCharPtrInVarBstr(&UPD, dataString);
ParseCharPtrToVarBstrAndVarDbl(&PD, dataString);
}
}
CloseVariantSafeArray(&UPD, unparsedData); if (UPD.failed) { return -1; }
CloseVariantSafeArray(&PD, parsedData); if (PD.failed) { return -1; }
*currentDataRow += UPD.index - UPD.startingIndex;
bufferAvailable = true;
return UPD.index - UPD.startingIndex;
}
产生的错误:
这是在 Excel 中打印在文本框中时返回的未解析字符串。
0.00,0.01,0.00 0.10,0.02,0.01 0.20,0.03,0.02 0.30,0.04,0.03 0.40,0.05,0.04 0.50,0.06,0.05 0.60,0.07,0.06 0.70,0.08,0.07 0.80,0.09,0.08 0.90,0.10,0.09 1.00,0.10,0.10
然而,VBA for 循环在单元格中打印的已解析字符串。仍然有随机字符。看来我在 DLL 中的解析有问题,因为最后一个双精度数总是以字符串结束,然后它总是有随机字符串。
0.000 0.010 "0.00 " - 0.100 0.020 "0.01 " - 0.200 0.030 "0.02 " - 0.300 0.040 "0.03 " - 0.400 0.050 0.900 0.100 "0.09 " - 0.000 0.100 "0.10 " - 0.100 0.110 "0.11 " - 0.200 0.120 "0.12 " - 0.300 0.130 "0.13 " - 0.400 0.140 "0.14 " - 0.500 0.150 "0.15 " - 0.600 0.160 "0.16 " - "0.25 " - 0.600 0.240 "0.26 " - 0.700 0.250 "0.27 " -
DLL代码中ParseCharPtrToVarBstrAndVarDbl()中的final endIndex++跳过字符串中的空终止符跳过定界符。解决方法是为字符串的末尾添加一个最终的 if 语句,如果它是 return.