C#调Qt C++封装的dll以及C++回调
最近这几天一直在研究用C++封装成dll然后在winform里调用,包括调用C++里的回调函数,记录一下,学习的过程
我这个工程是用Qt C++封装一个往Linux上传文件的dll,然后在winform里调用,过程中遇到很多坑,简单记录一下:
1. 需要注意的就是C#和C++的字段对应关系,C#里用string,这里传参的时候是char*。
2. C#默认编码是GB2312,Qt默认编码是UTF-8,导致中文传递时出现乱码,我在处理方法是:在本地把string转成utf8编码的byte数组,然后再用vs的默认编码转成string,这样传递过去之后,就是正确的中文了,见文档最下方的ConvertToCppStr方法和ConvertFromCppStr方法
3. DllImport时,dll设置相对路径时提示找不到文件,百度了一下,dll依赖其他dll,寻找其他依赖dll时回去三个地方找:
① exe文件所在路径 ② windows/system32 ③ 系统设置的环境变量路径
4. 第二点关于中文转换的问题,部分中文还是无法正确转换,后来百度发现了另外两种处理方式,见另一篇博文:C#调用C++封装的dll返回字符串
windows报出的错误基本都能百度到,C++那边报错就很费解了,我是通过百度和写日志的方法,定位问题语句,花了很多时间
Qt怎么新建dll工程很简单,这里省略了,直接上代码:
C++部分 主类 .h文件 关键代码
#ifndef UPLOADFILE_H
#define UPLOADFILE_H
#include "uploadfile_global.h"
#include <QMap>
//定义上传进度方法 和C#保持一致,回头C#定义的委托要传入给此对象
typedef void (*SetProgress)(int, double, const char*);
class UPLOADFILESHARED_EXPORT UploadFile : public QObject
{
Q_OBJECT
public:
UploadFile();
~UploadFile();
//添加文件
void addFile(int index, const char* filePath);
//打开连接
bool OpenConn(const char* host, const char* username, const char* pwd, short port, const char* rootpath, char* &error);
//上传
bool Upload();
//设置回调函数 C#的委托作为参数传入
void setCallBackFunc(SetProgress);
private:
//上传进度回调函数 C#的委托赋给该变量 然后执行该方法
SetProgress ProgressCallBackFunc;
public slots:
//接收上传进度
void setProgress(int index, curl_off_t unow, curl_off_t utotal, double speed);
};
对应的.cpp文件 关键代码
#include "UploadFile.h"
#include "qmd5.h"
#include <qfileinfo.h>
#include <QMetaType>
//构造函数
UploadFile::UploadFile()
{
CUrlFtp::initalCurl();
m_ftp = new CUrlFtp();
//注册Curl里的数据类型
qRegisterMetaType<curl_off_t>("curl_off_t");
//连接Curl上传进度的信号槽
bool b = connect(m_ftp, &CUrlFtp::sendProgress, this, &UploadFile::setProgress, Qt::DirectConnection);
}
//析构函数
UploadFile::~UploadFile()
{
CUrlFtp::clearCurl();
if(m_ftp != NULL)
{
delete m_ftp;
m_ftp = NULL;
}
}
//打开远程连接
bool UploadFile::OpenConn(const char* host, const char* username, const char* pwd, short port, const char* rootpath, char* &error)
{
if(m_connInfo == NULL) m_connInfo = new ConnInfo();
m_connInfo->hostName = host;
m_connInfo->userName = username;
m_connInfo->password = pwd;
m_connInfo->rootPath = rootpath;
if(m_ssh == NULL)
{
m_ssh = new SshTools();
}
if(m_ssh->connectToServer(host, username, pwd) != 0)
{
m_ssh->closeSsh();
delete m_ssh;
m_ssh = NULL;
const char* c = m_ssh->getLastError();
strcpy(error, c);
return false;
}
if(m_ftp->connect(username, pwd, host, port, 5) != 0)
{
const char* c = m_ftp->getLastError();
strcpy(error, c);
return false;
}
return true;
}
//上传文件的方法
bool UploadFile::Upload()
{
if(m_connInfo == NULL)
{
return false;
}
foreach(int index, m_filePathMap.keys())
{
UploadFileInfo *info = new UploadFileInfo();
info->index = index;
info->filePath = m_filePathMap[index];
QFileInfo f(QString::fromStdString(info->filePath));
info->remoteFilePath = m_connInfo->rootPath;
info->remoteFilePath.append("/");
info->remoteFilePath.append(f.fileName().toStdString());
info->ftp = m_ftp;
info->ssh = m_ssh;
m_uploadFileInfoMap.insert(index, info);
//多线程上传
TransferFileThread* thread = new TransferFileThread(info);
m_uploadThreadMap.insert(index, thread);
thread->start();
}
}
//上传进度槽函数
void UploadFile::setProgress(int index, curl_off_t unow, curl_off_t utotal, double speed)
{
if(!m_uploadFileInfoMap.contains(index) || utotal == 0) return;
UploadFileInfo* info = m_uploadFileInfoMap[index];
//进度
double progress = unow*1.0/utotal*100.0;
if(progress - info->progress > 0.1 || progress == 100)
{
info->progress = progress;
ProgressCallBackFunc(index, progress, sp.c_str());
}
}
//设置回调函数 参数是C#定义的委托
void UploadFile::setCallBackFunc(SetProgress progressFunc)
{
ProgressCallBackFunc = progressFunc;
}
以上是C++中主类的关键代码,下面新建一个C++的类文件,作为C#和C++通信的接口,看代码:
.h文件
#ifndef FILEUPLOADCALLER_H
#define FILEUPLOADCALLER_H
#include <QObject>
#include "UploadFile.h"
extern "C" {
//创建文件上传类 C#端调用该方法,获取主类的对象指针
extern Q_DECL_EXPORT UploadFile* CreateUploadFileClass();
//清除文件上传类 释放C++里创建的对象 主类对象指针作为参数传递进来,在C++端销毁
extern Q_DECL_EXPORT void DisposeUploadFileClass(UploadFile*);
//添加要上传的文件
extern Q_DECL_EXPORT void AddFilePath(UploadFile*, int index, char* filePath);
//打开连接
extern Q_DECL_EXPORT bool OpenCon(UploadFile*, char* host, char* userName, char* pwd, short port, char* uploadRootPath, char* &error);
//上传文件
extern Q_DECL_EXPORT bool UploadF(UploadFile*);
//设置回调函数 外部调用 C#端创建的委托作为参数传进来
extern Q_DECL_EXPORT void SetCallBackFunc(UploadFile*, SetProgress progressCallBackFunc);
}
#endif // FILEUPLOADCALLER_H
.cpp代码
#include "FileUploadCaller.h"
//创建文件上传类
UploadFile* CreateUploadFileClass()
{
UploadFile* f = new UploadFile();
return f;
}
//清除文件上传类
void DisposeUploadFileClass(UploadFile* uploadClass)
{
if(uploadClass != NULL)
{
delete uploadClass;
uploadClass = NULL;
}
}
//添加文件
void AddFilePath(UploadFile* uploadClass, int index, char* filePath)
{
uploadClass->addFile(index, filePath);
}
//打开连接
bool OpenCon(UploadFile* uploadClass, char* host, char* userName, char* pwd, short port, char* uploadRootPath, char* &error)
{
return uploadClass->OpenConn(host, userName, pwd, port, uploadRootPath, error);
}
//上传文件
bool UploadF(UploadFile* uploadClass)
{
return uploadClass->Upload();
}
//设置回调函数 外部调用
void SetCallBackFunc(UploadFile* uploadFile, SetProgress progressCallBackFunc)
{
uploadFile->setCallBackFunc(progressCallBackFunc);
}
C#端代码 使用时创建这个类对象 调用其方法就行了
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Tamir.SharpSsh;
namespace ORBP.Archive.USL
{
/// <summary>
/// 通过调用Qt C++封装的dll进行文件上传的类
/// </summary>
public class UploadFileClass
{
#region 调用c++上传dll相关方法
/// <summary>
/// 上传进度委托
/// </summary>
/// <param name="index">文件索引</param>
/// <param name="progress">进度百分比</param>
/// <param name="speed">速度</param>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ProgressCallBack(int index, double progress, string speed);
/// <summary>
/// 文件完整性委托
/// </summary>
/// <param name="index"></param>
/// <param name="complete"></param>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void CompleteCallBack(int index, bool complete);
/// <summary>
/// 创建上传类
/// </summary>
/// <returns></returns>
[DllImport(@"UploadFile.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr CreateUploadFileClass();
/// <summary>
/// 销毁上传类
/// </summary>
/// <param name=""></param>
[DllImport("UploadFile.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void DisposeUploadFileClass(IntPtr ptr);
/// <summary>
/// 添加文件
/// </summary>
/// <param name="filePath"></param>
[DllImport("UploadFile.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
//public static extern void AddFilePath(IntPtr ptr, string filePath, ref string err);
public static extern void AddFilePath(IntPtr ptr, int index, string filePath);
/// <summary>
/// 打开连接
/// </summary>
/// <param name="host"></param>
/// <param name="username"></param>
/// <param name="pwd"></param>
/// <param name="port"></param>
/// <param name="rootpath"></param>
/// <returns></returns>
[DllImport("UploadFile.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern bool OpenCon(IntPtr ptr, string host, string username, string pwd, short port, string rootpath, ref string error);
/// <summary>
/// 上传文件
/// </summary>
/// <param name="ptr"></param>
/// <param name="filePath"></param>
/// <returns></returns>
[DllImport("UploadFile.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern bool UploadF(IntPtr ptr);
/// <summary>
/// 上传进度进度和完整性委托函数
/// </summary>
/// <param name="cb1"></param>
/// <param name="cb2"></param>
[DllImport("UploadFile.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void SetCallBackFunc(IntPtr ptr, ProgressCallBack cb1);
#endregion 调用c++上传dll相关方法
#region 变量
/// <summary>
/// 表示c++ dll中的上传类指针
/// </summary>
private IntPtr m_uploadFileClass = IntPtr.Zero;
/// <summary>
/// 上传进度委托 这里定义成static 否则会报错
/// </summary>
private static ProgressCallBack m_progressCallBackDel;
#endregion 变量
#region 构造函数
/// <summary>
/// 上传文件处理类构造函数
/// </summary>
public UploadFileClass()
{
//创建C++类对象指针
m_uploadFileClass = CreateUploadFileClass();
//new委托对像
m_progressCallBackDel = new ProgressCallBack(GetProgress);
//委托传递给C++
SetCallBackFunc(m_uploadFileClass, m_progressCallBackDel);
}
#endregion 构造函数
#region 公共方法
/// <summary>
/// 销毁c++ dll中类对象指针
/// </summary>
public void DisposeUploadClass()
{
DisposeUploadFileClass(m_uploadFileClass);
m_uploadFileClass = IntPtr.Zero;
}
/// <summary>
/// 添加文件
/// </summary>
/// <param name="index">文件索引 不能重复</param>
/// <param name="filePath"></param>
public void AddFile(int index, string filePath)
{
filePath = this.ConvertToCppStr(filePath);
AddFilePath(m_uploadFileClass, index, filePath);
}
/// <summary>
/// 连接远程服务器
/// </summary>
/// <param name="host"></param>
/// <param name="username"></param>
/// <param name="pwd"></param>
/// <param name="port"></param>
/// <param name="rootpath"></param>
/// <returns></returns>
public bool Connect(string host, string username, string pwd, short port, string rootpath)
{
try
{
string str = this.ssh_conn(host, username, pwd, "");
string error = "";
bool s = OpenCon(m_uploadFileClass, host, username, pwd, port, rootpath, ref error);
if (!s)
{
error = this.ConvertFromCppStr(error);
throw new Exception(error);
}
return true;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 上传文件
/// </summary>
/// <returns></returns>
public void Upload()
{
try
{
bool s = UploadF(m_uploadFileClass);
}
catch (Exception ex)
{
throw ex;
}
}
#endregion 公共方法
#region 私有方法
/// <summary>
/// 将字符串转成C++可识别的字符串(文件名带中文要做转换)
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private string ConvertToCppStr(string str)
{
byte[] b = System.Text.Encoding.UTF8.GetBytes(str);
str = System.Text.Encoding.Default.GetString(b);
return str;
}
/// <summary>
/// 将C++字符串转成VS可识别的字符串(C++传出参数要做转换)
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
private string ConvertFromCppStr(string str)
{
byte[] b = System.Text.Encoding.Default.GetBytes(str);
str = System.Text.Encoding.UTF8.GetString(b);
return str;
}
#endregion 私有方法
}
}