在平时的开发中多多少少涉及 字符的处理
jstring&stringVSQString&string
QString & std::String
【4个字节】如果是以 TxZ[ 开头 【2个字节】后接长度
#include <stdio.h>
#include <unistd.h>
#include <inttypes.h>
#include <string.h>
// 格式:TxZ[len【两个字节】]
int main(){
char buf[100] ="TxZ[";
uint16_t len = 0x111;
memcpy(buf+4,&len,sizeof(len));
buf[4+sizeof(len)]=']';
buf[4+sizeof(len)+1]='\0';
printf("size = %lu,data=%s\n",strlen(buf),buf); // 这里有其他问题,后说
return 0;
}
现在需要 用QString 进行解析,直接打印看下效果
QString src = QString::fromStdString(buf);
qDebug()<<src;
// 以下就是结果
//"TxZ[\u0011\u0001]"
接下来我们采用QString 取出2个字节的长度
QString len_qstr = src.mid(4,2);
qDebug()<<len_qstr;
uint16_t q_len = (*(uint16_t*)len_qstr.toStdString().c_str());
qDebug("resolve len= %d",q_len);
// 结果
//"\u0011\u0001"
//resolve len= 273
上述表现很正常, 如果我把 长度改掉,这个时候你就会发现 错误了,这就是bug的所在
uint16_t len = 0x1F1;
// 代码不变
// 结果
//"TxZ[?"
//"?"
//resolve len= 49135
所以如果单元测试很重要,要不然悔之晚矣。那要 如何做到通用呢?
QByteArray q_ba = QByteArray(buf,4+sizeof(len)+1);
std::string s_str = std::string(q_ba.data(),q_ba.size());
int s_len = *(uint16_t*)s_str.substr(4,2).c_str();
qDebug("string 的方式 %d",s_len);
// 输出
//string 的方式 497
通过以上可以看出即使修改不同的长度,也能正确取出值。
测试用例如下
0x0 0x1 0x111 0x1F1 0xFF00 0xFFFF
经过上面的验证,不难发现 QString 是对数据做了处理导致 获取不到正确的数值了。
所以平常涉及操作字符数据的时候,需要使用std::string 或者 QByteArray 进行处理。
还有另外的方式
采用 可见字符 进行传输
本例中采用 数字转16进制进行传输
len_length =4;
char len_hex[5]={0};
memset(len_hex,0,sizeof(len_hex));
snprintf(len_hex,sizeof(len_hex),"%4x",len);
memcpy(buf+4,len_hex,len_length); //长度
通过以上的方式进行转换成可见字符,当然这个仅限数字,如果是其他的这种方案行不通的,可能就要涉及base64了。原理就是 转成可见字符
int main(int argc, char *argv[])
{
char buf[100] ="TxZ[";
uint16_t len = 0xFFFF;
int len_length = 0;
#ifdef USE_CHAR
typedef uint16_t INT;
len_length =sizeof(len);
qDebug("%lu",sizeof(len));
memcpy(buf+4,&len,sizeof(len));
#else
typedef int32_t INT;
len_length =4;
char len_hex[5]={0};
memset(len_hex,0,sizeof(len_hex));
snprintf(len_hex,sizeof(len_hex),"%4x",len);
memcpy(buf+4,len_hex,len_length); //长度
#endif
buf[4+len_length]=']';
buf[4+len_length+1]='\0';
fwrite(buf,4+len_length+1,1,stdout);
//////////// 以下使用 QString 方式处理的
QString src = QString::fromStdString(buf);
QString len_qstr = src.mid(4,len_length);
qDebug("使用QString取出的原串%s,裁剪后为%s",src.toStdString().c_str(),len_qstr.toStdString().c_str());
INT q_len=0;
#ifdef USE_CHAR
q_len = (*(INT*)len_qstr.toStdString().c_str());
#else
// 16进制转 10进制
sscanf(len_qstr.toStdString().c_str(),"%4x",&q_len);
#endif
qDebug("qString 的方式 len= %d",q_len);
/////// 以下为使用 std::string 处理的
QByteArray q_ba = QByteArray(buf,4+len_length+1);
std::string s_str = std::string(q_ba.data(),q_ba.size());
INT s_len = 0;
#ifdef USE_CHAR
s_len = *(INT*)s_str.substr(4,len_length).c_str();
#else
sscanf(s_str.substr(4,len_length).c_str(),"%4x",&s_len);
#endif
qDebug("string 的方式 len= %d",s_len);
return 0;
}
综上,一个原则在c++里面如果做 字符数据的传递 需要使用 QByteArray 来进行传递,QString 会进行处理导致出现问题。
上面有个遗漏的点忘了说了:
fwrite(buf,4+len_length+1,1,stdout);
打印应该用这种方式,虽然会出现乱码,但是比printf 遇到 \0 就结束来的太好。
JString & String
在java层面构造一个 byte数据 ,通过 string 传递和 byte传递 ,来查看区别
public static void main(String[] args) {
byte[] b = new byte[5];
b[0]= (byte) 0x00;
b[1]= (byte) 0x0F;
b[2]= (byte) 0xF0;
b[3]= (byte) 0xFF;
b[4] = ']';
for (int i = 0; i < b.length; i++) {
System.out.print(String.format("第%d个",i));
System.out.print(String.format("数据是:%s",b[i]));
System.out.println(String.format("十六进制是:%2x",b[i]));
}
System.out.println("---------------------");
String s = new String(b);
for (int i = 0; i < s.length(); i++) {
System.out.print(String.format("第%d个",i));
System.out.print(String.format("数据是:%s",s.charAt(i)));
System.out.println(String.format("十六进制是:%2x", ((byte) s.charAt(i))));
}
}
// 结果
/**
第0个数据是:0十六进制是: 0
第1个数据是:15十六进制是: f
第2个数据是:-16十六进制是:f0
第3个数据是:-1十六进制是:ff
第4个数据是:93十六进制是:5d
---------------------
第0个数据是: 十六进制是: 0
第1个数据是:十六进制是: f
第2个数据是:�十六进制是:fd
第3个数据是:�十六进制是:fd
第4个数据是:]十六进制是:5d
**/
通过上面可以看出 如果采用string包装了 数据,会导致数据的改变,所以如果涉及字符处理需要使用 byte[] 进行数据的传递,才能保持数据的完整性。
在JNI的开发中也是一样的,java层的数据丢到 c/c++里面
fun dosth(){
val b:ByteArray = ByteArray(5);
b[0]=0x00;
b[1]=0x0F;
b[2]= 0xF0.toByte();
b[3]= 0xFF.toByte();
b[4]= ']'.toByte();
native2Str(String(b));
native2byteArray(b);
}
//native
private external fun native2Str(str:String)
private external fun native2byteArray(b:ByteArray)
companion object {
init {
System.loadLibrary("dataprocessor")
}
}
c++层
#define JNI_BYTE_ARRAY_TO_STRING(_env, _arr, _str) \
do \
{ \
if (NULL == (_arr))break; \
jbyte* ___byte_array_data = (_env)->GetByteArrayElements((_arr), NULL); \
(_str).assign((const char*)___byte_array_data, (_env)->GetArrayLength((_arr))); \
(_env)->ReleaseByteArrayElements((_arr), ___byte_array_data, 0); \
} while(0)
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_CharsetUtilsActivity_native2byteArray(JNIEnv *env, jobject thiz,
jbyteArray b) {
// TODO: implement native2byteArray()
size_t length = (size_t) env->GetArrayLength(b);
jbyte* pBytes = env->GetByteArrayElements(b, NULL);
std::string str;
JNI_BYTE_ARRAY_TO_STRING(env,b,str);
for (int i = 0; i < str.length(); ++i) {
__android_log_print(ANDROID_LOG_DEBUG, "DataProcess", "第%d字符是:%c,十六进制是%2x\n", i, str[i],*(uint8_t *)&str[i]);
}
__android_log_print(ANDROID_LOG_DEBUG, "DataProcess", "分割线-------------------\n");
for (int i = 0; i < length; ++i) {
__android_log_print(ANDROID_LOG_DEBUG, "DataProcess", "第%d字符是:%c,十六进制是%2x\n", i, pBytes[i],*(uint8_t *)&pBytes[i]);
}
}
如果采用jbytearray 进行数据的传递才是正确的,jstring 什么的都是 有问题的。
总结
在做跨端,跨语言传输的时候,尽量采用原有的字节类型进行传递,而不要使用 string 进行传递,否则在对端解析数据的时候会面临解不开的问题。导致浪费青春。
场景:1. 在后台经过 加密给到 客户端的数据,需要在客户端进行解密。
- 在jni 开发的时候,比方
protobuffer这样的数据的时候 - 在qt开发的时候, 比方串口通讯的时候。