问题描述:
在开发过程中,需要实现获取剪切板文本的方法,使用Clipboard.GetDataObject有时候会乱码
// GetDataObject获取当前剪贴板上的数据
IDataObject data = Clipboard.GetDataObject();
// 将数据与指定的格式进行匹配,返回bool
if (data.GetDataPresent(DataFormats.Text))
{
string text = (string)data.GetData(DataFormats.Text);
经测试发现,乱码的文本是UTF-8编码的,而中文的电脑上默认是GBK编码,所以获取的剪切板内容转换为string的过程中错误地将UTF-8编码的文本按照GBK编码解析,导致了乱码。
解决方法:
既然.NET获取的剪切板是string,那么我们只要获取剪切板的实际字节,检测它的编码,然后将它转换回GBK编码,因为我不知道如何用.NET获取剪切板实际数据,所以采用C语言实现获取剪切板原始内容,然后.NET调用C语言的dll库实现获取剪切板内容,代码如下:
C语言实现clipget.dll
#include <Windows.h>
#include <stdio.h>
//判断字符串是否为GBK
int isGBK(unsigned char* buf, int size) {
int start = 0;
int end = size;
int c = 0;
int type = 0;
while (start < end) {
c = buf[start];
switch(type) {
case 0:
if (c >= 0x81 && c <= 0xfe) {
type = 1;
} else if (c < 128) {
// ASCII character
} else {
return 0; // Not a valid GBK character
}
break;
case 1:
if (c != 0x7f && c >= 0x40 && c <= 0xfe) {
type = 0;
} else {
return 0; // Not a valid GBK character
}
break;
}
start++;
}
return 1; // All characters are valid GBK characters
}
int isGB2312(unsigned char* buf, int size) {
int isGBK = 1;
int start = 0;
int end = size;
int c = 0;
int c2 = 0;
int type = 0;
unsigned char buffer[100];
int i = 0;
while (start < end) {
c = buf[start];
buffer[i++] = c;
switch (type) {
case 0:
if (c >= 0xa1 && c <= 0xf7) {
type = 1;
} else if (c < 128) {
} else {
isGBK = 0;
goto GBWHILE;
}
break;
case 1:
if (c >= 0xa1 && c <= (0xa0 + 94)) {
type = 0;
} else {
isGBK = 0;
goto GBWHILE;
}
break;
}
start++;
}
GBWHILE:
// printf("isGb: %d\n", start);
return isGBK;
}
//将UTF-8转换为GBK
char* utf8_to_gbk(char* utf8) {
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
wchar_t* wstr = (wchar_t*)malloc(sizeof(wchar_t) * len);
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, len);
len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
char* str = (char*)malloc(sizeof(char) * len);
WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);
free(wstr);
return str;
}
__declspec(dllexport) char* clipget(){
// printf("剪贴板数据获取\n");
// 打开剪贴板
if (!OpenClipboard(NULL)) {
// printf("无法打开剪贴板\n");
return "";
}
// 获取剪贴板中的文本数据
HANDLE handle = GetClipboardData(CF_TEXT);
if (handle == NULL) {
// printf("无法获取剪贴板文本数据\n");
return "";
}
// 锁定内存并获取剪贴板文本
char* temp = (char*)GlobalLock(handle);
char* text = NULL;
//复制temp中的内容到text中
text = (char*)malloc(strlen(temp) + 1);
strcpy(text, temp);
if (text == NULL) {
// printf("无法锁定内存\n");
return "";
}
//判断文本是否为GBK编码
if (isGB2312(text, strlen(text))) {
// printf("文本为GBK编码\n");
} else {
//转换编码为GBK
char* gbk = utf8_to_gbk(text);
//判断gbk中?数量是否大于2,如果不大于,就赋值给text,否则释放
int len = strlen(gbk);
int count = 0;
for(int i=0;i<len;i++){
if(gbk[i] == '?'){
count++;
}
}
if(count<=2){
text = gbk;
}else{
free(gbk);
}
}
// 输出剪贴板文本数据
// printf("%s\n", text);
// 解锁内存并关闭剪贴板
GlobalUnlock(handle);
CloseClipboard();
return text;
}
/*
int main() {
// printf("剪贴板数据获取\n");
// 打开剪贴板
if (!OpenClipboard(NULL)) {
// printf("无法打开剪贴板\n");
return 1;
}
// 获取剪贴板中的文本数据
HANDLE handle = GetClipboardData(CF_TEXT);
if (handle == NULL) {
// printf("无法获取剪贴板文本数据\n");
return 1;
}
// 锁定内存并获取剪贴板文本
char* text = (char*)GlobalLock(handle);
if (text == NULL) {
// printf("无法锁定内存\n");
return 1;
}
//判断文本是否为GBK编码
if (isGB2312(text, strlen(text))) {
// printf("文本为GBK编码\n");
} else {
//转换编码为GBK
char* gbk = utf8_to_gbk(text);
text = gbk;
}
// 输出剪贴板文本数据
printf("%s\n", text);
// 解锁内存并关闭剪贴板
GlobalUnlock(handle);
CloseClipboard();
return 0;
}
*/
.NET调用clipget.dll
// 声明 clipget 方法的签名
[DllImport("clipget.dll")]
private static extern IntPtr clipget();
public string getClipString()
{
// 调用 clipget 方法并获得返回的 char* 指针
IntPtr ptr = clipget();
// 将 char* 指针转换为 string
string result = Marshal.PtrToStringAnsi(ptr);
// 释放内存
//Marshal.FreeCoTaskMem(ptr);
return result;
}
调用方法
string dllPath = "clipget.dll";
if (File.Exists(dllPath))
{
string text = getClipString();
//将剪切板内容赋值到控件上
textBox_input.Text = text;
}
else
{
// ... 如果dll库不存在,.NET实现
}