首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
80 阅读
2
nvim番外之将配置的插件管理器更新为lazy
58 阅读
3
2018总结与2019规划
54 阅读
4
PDF标准详解(五)——图形状态
33 阅读
5
为 MariaDB 配置远程访问权限
30 阅读
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
Java
emacs
linux
文本编辑器
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
308
篇文章
累计收到
27
条评论
首页
栏目
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
页面
归档
友情链接
关于
搜索到
5
篇与
的结果
2021-12-10
MFC程序中使用QT开发界面
如果你有一个现成的MFC项目在做维护,但是你厌倦了使用MFC繁琐的操作来做界面美化,或者你需要在这个项目中用到QT里面好用的某些功能;亦或者是你需要使用某些只能在MFC中使用的组件,但是界面这部分已经用QT做好了。那么这篇文章可能可以帮助到你演示环境使用Visual Studio 2019 + QT5.12.8 版本添加QT依赖首先创建一个基于对话框的MFC工程,当然其他的像是多文档、单文档工程也是可以的,只是为了简单起见我这里用的是对话框然后通过鼠标右键点击项目,然后依次点击属性 --> C/C++ -->常规在工程的附加头文件中添加上QCore、QGui、QWidget和QT的头文件路径这里记得按照对应编译选项来选择包含64位或者32。接着在连接器-->常规 中的附加库目录中添加qt的lib库最后再在连接器-->输入中添加依赖的lib文件,需要注意的是,debug版本需要链接上带d的lib文件,release则链接上不带d的。先编译一下,如果没有问题,qt相关的配置已经完成了添加信号槽机制MFC是基于Windows 消息队列来处理和响应ui事件的,而qt是采用信号槽机制来响应的,我们虽然添加了qt的依赖,但是现在只能使用其他的qt库,无法使用qt中的信号槽,需要额外添加一些组件来使mfc支持信号槽。好在这部分需求qt相关的研发人员已经考虑到了,可以在github中找到 QMfcApp我们可以将这两个文件给拷贝下来,添加到项目中。并且在cpp文件相应位置添加上 #include "pch.h"包含预处理头中间会有报错,这是因为在Unicode 字符集下 CString 中的字符串类型是 wchar_t* QString::fromLocal8bit 无法 从 wchar_t* 转化为 char* 所以这里可以修改一下,使用 QString::fromStdWString(),然后进行编译在QMfcApp.cpp的注释里面可以看到,如何使用它/*! Creates an instance of QApplication, passing the command line of \a mfcApp to the QApplication constructor, and returns the new object. The returned object must be destroyed by the caller. Use this static function if you want to perform additional initializations after creating the application object, or if you want to create Qt GUI elements in the InitInstance() reimplementation of CWinApp: \code BOOL MyMfcApp::InitInstance() { // standard MFC initialization // ... // This sets the global qApp pointer QMfcApp::instance(this); // Qt GUI initialization } BOOL MyMfcApp::Run() { int result = QMfcApp::run(this); delete qApp; return result; } \endcode \sa run() */首先在app类的InitInstance 函数中初始化QApplication类BOOL CMFCWithQtApp::InitInstance() { CWinApp::InitInstance(); QMfcApp::instance(this); return true; }然后需要重写 app类的run 方法,在该方法中调用QMFC 的run方法int CMFCWithQtApp::Run() { int result = QMfcApp::run(this); delete qApp; return result; }再次编译一下,完成了往mfc中添加信号槽机制的功能添加qt界面在项目中新建一个界面类,让他继承自QWidget,如下// MainUI.h #pragma once #include <QWidget> class MainUI: public QWidget { Q_OBJECT public: MainUI(QWidget* parent = nullptr); ~MainUI(); }; //MainUI.cpp #include "pch.h" #include "MainUI.h" #include <QPushButton> MainUI::MainUI(QWidget* parent) : QWidget(parent) { setWindowTitle("Qt Windows"); setFixedSize(800, 720); QPushButton* pBtn = new QPushButton(QString::fromLocal8Bit("这是一个Qt按钮"), this); } MainUI::~MainUI() { }然后在App 类的 InitInstance 中启动该界面BOOL CMFCWithQtApp::InitInstance() { CWinApp::InitInstance(); QMfcApp::instance(this); MainUI ui; ui.show(); QMfcApp::exec(); return FALSE; }然后编译,这个时候发现会在链接的时候包一些错误,找不到一些 meta 的函数的定义配置元编译过程传统的c/c++ 从源代码到生成可执行文件的过程需要经过预编译、编译、链接。而qt在预编译前会进行元编译,生成一个moc_开头的源码文件,后续编译的真正的文件其实是这个元编译生成的文件。MFC项目不会经历这一步,所以会报错。在MainUI.h 上点击右键,选择属性, 将项目类型选择为自定义生成工具然后应用,这个时候会出现新的选项在命令行和输入这两栏中分别填入 "moc.exe" "%(FullPath)" -o ".\GeneratedFiles\moc_%(Filename).cpp" "-fpch.h" "-f../MainUI.h" 和 .\GeneratedFiles\moc_%(Filename).cpp命令行的含义是会使用moc元编译器编译当前文件,并将生成的文件放入到当前目录下的GeneratedFiles子目录中,并且以moc_开头作为文件名,后面 -f 表示生成的新文件中会包含 #include "pch.h" 和 #include "../MainUI.h" 然后在文件中选择右键,编译。这样将会生成moc文件(两边的双引号也好包含进去)如果编译的时候报找不到moc.exe 这样的错误,请配置QT中的bin路径到环境变量中我们将新生成的文件添加到项目中再次编译,成功过后点击运行就可以看到qt界面已经展示出来了一些问题的处理窗口出来了,但是在我的环境下出现两个问题,关闭窗口后进程无法退出;程序退出后出现内存泄露的问题针对这两个问题可以在QMfcApp.cpp 文件中修改// 表示在最后一个qt窗口退出时,关闭QApplication setQuitOnLastWindowClosed(true); //将之前的false改为true// ~QMfcApp() 中添加这两句,当析构完成后关闭进程 HANDLE hself = GetCurrentProcess(); TerminateProcess(hself, 0);测试信号槽我们可以在MainUI中添加信号槽MainUI::MainUI(QWidget* parent) : QWidget(parent) { setWindowTitle("Qt Windows"); setFixedSize(800, 720); QPushButton* pBtn = new QPushButton(QString::fromLocal8Bit("这是一个Qt按钮"), this); connect(pBtn, &QPushButton::clicked, [=]() { QMessageBox::information(this, QString::fromLocal8Bit("信号槽"), QString::fromLocal8Bit("这是由信号槽弹出来的")); }); }点击按钮之后,消息框也正常弹出来了使用qt designer 设计界面使用 qtdesigner 设计这样一个界面qtdesigner 会生成一个.ui文件,在qt的开发环境中,会自动使用uic.exe 将这个文件生成一个对应的.h文件。我们先将ui文件导入到项目,并且按照之前的步骤设置自定义生成工具,填入如下命令行"uic.exe" "%(FullPath)" -o ".\ui_%(Filename).h"并且填写上输出路径.\ui_%(Filename).h编译之后会生成一个对应的ui_MainUi.h 文件,修改对应的MainUI.h 头文件,加上关于它的引用,并且添加一个ui的对象指针//MainUI.h #pragma once #include <QWidget> #include "ui_Main.h" class MainUI: public QWidget { Q_OBJECT public: MainUI(QWidget* parent = nullptr); ~MainUI(); private: Ui::mainUI* m_pUI; };在类的构造中,使用ui对象来产生界面元素//MainUI.cpp #include "pch.h" #include "MainUI.h" #include <QPushButton> #include <QMessageBox> MainUI::MainUI(QWidget* parent) : QWidget(parent), m_pUI(new Ui::mainUI()) { m_pUI->setupUi(this); connect(m_pUI->pushButton, &QPushButton::clicked, [=]() { QMessageBox::information(this, QString::fromLocal8Bit("qt 信号槽测试"), m_pUI->lineEdit->text()); }); } MainUI::~MainUI() { delete m_pUI; }执行效果如下
2021年12月10日
4 阅读
0 评论
0 点赞
2021-01-10
记一次内存泄露调试
首先介绍一下相关背景。最近在测试一个程序时发现,在任务执行完成之后,从任务管理器上来看,内存并没有下降到理论值上。程序在启动完成之后会占用一定的内存,在执行任务的时候,会动态创建一些内存,用于存储任务的执行状态,比如扫描了哪些页面,在扫描过程中一些收发包的记录等等信息。这些中间信息在任务结束之后会被清理掉。任务结束之后,程序只会保存执行过的任务列表,从理论上讲,任务结束之后,程序此时所占内存应该与程序刚启动时占用内存接近,但是实际观察的结果就是任务结束之后,与刚启动之时内存占用差距在100M以上,这很明显不正常,当时我的第一反应是有内存泄露内存泄露排查既然有内存泄露,那么下一步就是开始排查,由于程序是采用MFC编写的,那么自然就得找MFC的内存泄露排查手段。根据网上找到的资料,MFC在DEBUG模式中可以很方便的集成内存泄露检查机制的。首先在 stdafx.h 的头文件中加入#define _CRTDBG_MAP_ALLO #include <crtdbg.h>再在程序退出的地方加入代码_CrtDumpMemoryLeaks();如果发生内存泄露的话,在调试运行结束之后,观察VS的输出情况可以看到如下内容Detected memory leaks! Dumping objects -> .\MatriXayTest.cpp(38) : {1301} normal block at 0x0000000005584D30, 40 bytes long. Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete.在输出中会提示有内存泄露,下面则是泄露的具体内容,MatriXayTest.cpp 是发生泄露的代码文件,括号中的38代表代码所在行数,大括号中1301代表这是程序的第1301次分配内存,或者说第1301次执行malloc操作,再往后就是内存泄露的地址,以及泄露的大小,这个地址是进程启动之后随机分配的地址,参考意义不大。下面一行表示,当前内存中的具体值,从值上来看应该是分配了内存但是没有初始化。根据这个线索,我们来排查,找到第38行所在位置int *p = new int[10]; _CrtDumpMemoryLeaks(); return nRetCode;内存泄露正是出现在new了10个int类型的数据,但是后面没有进行delete操作,正是这个操作导致了内存泄露。到此为止,检测工具也找到了,下面就是加上这段代码,运行发生泄露的程序,查看结果再漫长的等待任务执行完成并自动停止之后,我发现居然没有发现内存泄露!!!我又重复运行任务多次,发现结果是一样的,这个时候我开始怀疑是不是这个库不准,于是我在数据节点的类中添加构造函数,统计任务执行过程中创建了多少个节点,再在析构中统计析构了多少个节点,最终发现这两个数据完全相同。也就是说真的没有发生内存泄露。在这里我也疑惑了,难道是任务管理器有问题?带着这个疑问,我自己写了一段代码,在程序中不定时打印内存占用情况,结果发现虽然与任务管理器有差异,但是结果是类似的,释放之后内存并没有大幅度的下降。我带着疑问查找资料的过程的漫长过程中,发现任务管理器的显示内存占用居然降下去了,我统计了一下时间,应该是在任务结束之后的30分钟到40分钟之间。带着这个疑问,我又重新发起任务,在任务结束,并等待一定时间之后,内存占用果然降下去了。这里我得出结论 程序中执行delete操作之后,系统并不会真的立即回收操作,而是保留这个内存一定时间,以便后续该进程在需要分配时直接使用结论验证科学一般来说需要大胆假设,小心求证,既然上面根据现象做了一些猜想,下面就需要对这个猜想进行验证。首先来验证操作系统在程序调用delete之后不会真的执行delete操作。我使用下面的代码进行验证//定义一个占1M内存的结构 struct data{ char c[1024 * 1024]; } data* pa[1024] = {0}; for (int i = 0; i < 1024; i++) { pa[i] = new data; //这里执行一下数据清零操作,以便操作系统真正为程序分配内存 //有时候调用new或者malloc操作,操作系统只是保留对应地址,但是并未真正分配物理内存 //操作会等到进程真正开始操作这块内存或者进程需要分配的内存总量达到一个标准时才真正进行分配 memset(pa[i], 0x00, sizeof(data)); } printf("内存分配完毕,按任意键开始释放内存...\n"); getchar(); for (int i = 0; i < 1024; i++) { delete pa[i]; } printf("内存释放完毕,按任意键退出\n"); _CrtDumpMemoryLeaks(); char c = getchar();通过调试这段代码,在刚开始运行,没有执行到new操作的时候,进程占用内存在2M左右,运行到第一个循环结束,分配内存后,占用内存大概为1G,在执行完delete之后,内存并没有立马下降到初始的2M,而是稳定在150M左右,过一段时间之后,程序所占用内存会将到2M左右。接着对上面的代码做进一步修改,来测试内存使用时间长度与回收所需时间的长短的关系。这里仍然使用上面定义的结构体来做尝试data* pa[1024] = {0}; for (int i = 0; i < 1024; i++) { pa[i] = new data; memset(pa[i], 0x00, sizeof(data)); } printf("内存分配完毕,按任意键开始写数据到内存\n"); getchar(); //写入随机字符串 srand((unsigned) time(NULL)); DWORD dwStart = GetTickCount(); DWORD dwEnd = dwStart; printf("开始往目标内存中写入数据\n"); while ((dwEnd - dwStart) < 1 * 60 * 1000) //执行时间为1分钟 { for (int i = 0; i < 1024; i++) { for (int j = 0; j < 1024; j++) { int flag = rand() % 3; switch (flag) { case 1: { //生成大写字母 pa[i]->c[j] = (char)(rand() % 26) + 'A'; } break; case 2: { //生成小写字母 pa[i]->c[j] = (char)(rand() % 26) + 'a'; } break; case 3: { //生成数字 pa[i]->c[j] = (char)(rand() % 10) + '0'; } break; default: break; } } } dwEnd = GetTickCount(); } printf("数据写入完毕,按任意键开始释放内存...\n"); getchar(); for (int i = 0; i < 1024; i++) { delete pa[i]; } printf("内存释放完毕,按任意键退出\n"); _CrtDumpMemoryLeaks(); char c = getchar();后面就不放测试的结果了,我直接说结论,同一块内存使用时间越长,操作系统真正保留它的时间也会越长。短时间内差别可能不太明显,长时间运行,这个差别可以达到秒级甚至分。我记得当初上操作系统这门课程的时候,老师说过一句话:一个在过去使用时间越长的资源,在未来的时间内会再次使用到的概率也会越高,基于这一原理,操作会保留这块内存一段时间,如果程序在后面再次申请对应结构时,操作系统会直接将之前释放的内存拿来使用。为了验证这一现象,我来一个小的测试int *p1 = new int[1024]; memset(p, 0x00, sizeof(int) * 1024); delete[] p; int* p2= new int[1024];通过调试发现两次返回的地址相同,也就验证了之前说的内容总结最后来总结一下结论,有时候遇到delete内存后任务管理器或者其他工具显示内存占用未减少或者减少的量不对时,不一定是发生了内存泄露,也可能是操作系统的内存管理策略:程序调用delete后操作系统并没有真的立即回收对应内存,它只是暂时做一个标记,后续真的需要使用相应大小的内存时会直接将对应内存拿出来以供使用。而具体什么时候真正释放,应该是由操作系统进行宏观调控。我觉得这次暴露出来的问题还是自己基础知识掌握不扎实,如果当时我能早点回想起来当初上课时所讲的内容,可能也就不会有这次针对一个错误结论,花费这么大的精力来测试。当然这个世界上没有如果,我希望看到这篇博文的朋友,能少跟风学习新框架或者新语言,少被营销号带节奏,沉下心了,补充计算机基础知识,必将受益匪浅。
2021年01月10日
6 阅读
0 评论
0 点赞
2019-04-14
VC+++ 操作word
最近完成了一个使用VC++ 操作word生成扫描报告的功能,在这里将过程记录下来,开发环境为visual studio 2008导入接口首先在创建的MFC项目中引入word相关组件右键点击 项目 --> 添加 --> 新类,在弹出的对话框中选择Typelib中的MFC类。然后在弹出的对话框中选择文件,从文件中导入MSWORD.OLB组件。这个文件的路径一般在C:\Program Files (x86)\Microsoft Office\Office14 中,注意:最后一层可能不一定是Office14,这个看机器中安装的office 版本。选择之后会要求我们决定导入那些接口,为了方便我们导入所有接口。导入之后可以看到项目中省成本了很多代码文件,这些就是系统生成的操作Word的相关类。这里编译可能会报错,error C2786: “BOOL (HDC,int,int,int,int)”: __uuidof 的操作数无效解决方法:修改对应头文件#import "C:\\Program Files\\Microsoft Office\\Office14\\MSWORD.OLB" no_namespace为:#import "C:\\Program Files\\Microsoft Office\\Office14\\MSWORD.OLB" no_namespace raw_interfaces_only \ rename("FindText","_FindText") \ rename("Rectangle","_Rectangle") \ rename("ExitWindows","_ExitWindows")再次编译,错误消失常见接口介绍要了解一些常见的类,我们首先需要明白这些接口的层次结构:Application(WORD 为例,只列出一部分) Documents(所有的文档) Document(一个文档) ...... Templates(所有模板) Template(一个模板) ...... Windows(所有窗口) Window Selection View Selection(编辑对象) Font Style Range这些组件其实是采用远程调用的方式调用word进程来完成相关操作。Application:相当于一个word进程,每次操作之前都需要一个application对象,这个对象用于创建一个word进程。Documents:相当于word中打开的所有文档,如果用过word编辑多个文件,那么这个概念应该很好理解Templates:是一个模板对象,至于word模板,不了解的请自行百度Windows:word进程中的窗口Selection:编辑对象。也就是我们要写入word文档中的内容。一般包括文本、样式、图形等等对象。回忆一下我们手动编写word的情景,其实使用这些接口是很简单的。我们在使用word编辑的时候首先会打开word程序,这里对应在代码里面就是创建一个Application对象。然后我们会用word程序打开一个文档或者新建一个文档。这里对应着创建Documents对象并从中引用一个Document对象表示一个具体的文档。当然这个Document对象可以是新建的也可以是打开一个现有的。接着就是进行相关操作了,比如插入图片、插入表格、编写段落文本等等了。这些都对应着创建类似于Font、Style、TypeText对象,然后将这些对象进行添加的操作了。说了这么多。这些接口这么多,我怎么知道哪个接口对应哪个对象呢,而且这些参数怎么传递呢?其实这个问题很好解决。我们可以手工进行相关操作,然后用宏记录下来,最后我们再将宏中的VB代码转化为VC代码即可。相关操作为了方便在项目中使用,这里创建一个类用于封装Word的相关操作class CCreateWordReport { private: CApplication m_wdApp; CDocuments m_wdDocs; CDocument0 m_wdDoc; CSelection m_wdSel; CRange m_wdRange; CnlineShapes m_wdInlineShapes; CnlineShape m_wdInlineShape; public: CCreateWordReport(); virtual ~CCreateWordReport(); public: //操作 //**********************创建新文档******************************************* BOOL CreateApp(); //创建一个新的WORD应用程序 BOOL CreateDocuments(); //创建一个新的Word文档集合 BOOL CreateDocument(); //创建一个新的Word文档 BOOL Create(); //创建新的WORD应用程序并创建一个新的文档 void ShowApp(); //显示WORD文档 void HideApp(); //隐藏word文档 //**********************打开文档********************************************* BOOL OpenDocument(CString fileName);//打开已经存在的文档。 BOOL Open(CString fileName); //创建新的WORD应用程序并打开一个已经存在的文档。 BOOL SetActiveDocument(short i); //设置当前激活的文档。 //**********************保存文档********************************************* BOOL SaveDocument(); //文档是以打开形式,保存。 BOOL SaveDocumentAs(CString fileName);//文档以创建形式,保存。 BOOL CloseDocument(); void CloseApp(); //**********************文本书写操作***************************************** void WriteText(CString szText); //当前光标处写文本 void WriteNewLineText(CString szText, int nLineCount = 1); //换N行写字 void WriteEndLine(CString szText); //文档结尾处写文本 void WholeStory(); //全选文档内容 void Copy(); //复制文本内容到剪贴板 void InsertFile(CString fileName); //将本地的文件全部内容写入到当前文档的光标处。 void InsertTable(int nRow, int nColumn, CTable0& wdTable); //**********************图片插入操作***************************************** void InsertShapes(CString fileName);//在当前光标的位置插入图片 //**********************超链接插入操作***************************************** void InsertHyperlink(CString fileLink);//超级链接地址,可以是相对路径。 //***********************表格操作表格操作********************************** BOOL InsertTableToMarkBook(const CString csMarkName, int nRow, int nColumn, CTable0& wdTable); //表格行数与列数 BOOL WriteDataToTable(CTable0& wdTable, int nRow, int nColumn, const CString &csData); //往表格中写入输入 };BOOL CCreateWordReport::CreateApp() { if (FALSE == m_wdApp.CreateDispatch("word.application")) { AfxMessageBox("Application创建失败,请确保安装了word 2000或以上版本!", MB_OK|MB_ICONWARNING); return FALSE; } return TRUE; } BOOL CCreateWordReport::CreateDocuments() { if (FALSE == CreateApp()) { return FALSE; } m_wdDocs = m_wdApp.get_Documents(); if (!m_wdDocs.m_lpDispatch) { AfxMessageBox("Documents创建失败!", MB_OK|MB_ICONWARNING); return FALSE; } return TRUE; } BOOL CCreateWordReport::CreateDocument() { if (!m_wdDocs.m_lpDispatch) { AfxMessageBox("Documents为空!", MB_OK|MB_ICONWARNING); return FALSE; } COleVariant varTrue(short(1),VT_BOOL),vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR); CComVariant Template(_T("")); //没有使用WORD的文档模板 CComVariant NewTemplate(false),DocumentType(0),Visible; m_wdDocs.Add(&Template,&NewTemplate,&DocumentType,&Visible); //得到document变量 m_wdDoc = m_wdApp.get_ActiveDocument(); if (!m_wdDoc.m_lpDispatch) { AfxMessageBox("Document获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } //得到selection变量 m_wdSel = m_wdApp.get_Selection(); if (!m_wdSel.m_lpDispatch) { AfxMessageBox("Select获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } //得到Range变量 m_wdRange = m_wdDoc.Range(vOptional,vOptional); if(!m_wdRange.m_lpDispatch) { AfxMessageBox("Range获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } return TRUE; } BOOL CCreateWordReport::Create() { if (FALSE == CreateDocuments()) { return FALSE; } return CreateDocument(); } BOOL CCreateWordReport::OpenDocument(CString fileName) { if (!m_wdDocs.m_lpDispatch) { AfxMessageBox("Documents为空!", MB_OK|MB_ICONWARNING); return FALSE; } COleVariant vTrue((short)TRUE), vFalse((short)FALSE), vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR), vZ((short)0); COleVariant vFileName(_T(fileName)); //得到document变量 m_wdDoc = m_wdDocs.Open( vFileName, // FileName vTrue, // Confirm Conversion. vFalse, // ReadOnly. vFalse, // AddToRecentFiles. vOptional, // PasswordDocument. vOptional, // PasswordTemplate. vOptional, // Revert. vOptional, // WritePasswordDocument. vOptional, // WritePasswordTemplate. vOptional, // Format. // Last argument for Word 97 vOptional, // Encoding // New for Word 2000/2002 vOptional, // Visible /*如下4个是word2003需要的参数。本版本是word2000。*/ vOptional, // OpenAndRepair vZ, // DocumentDirection wdDocumentDirection LeftToRight vOptional, // NoEncodingDialog vOptional ); if (!m_wdDoc.m_lpDispatch) { AfxMessageBox("Document获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } //得到selection变量 m_wdSel = m_wdApp.get_Selection(); if (!m_wdSel.m_lpDispatch) { AfxMessageBox("Select获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } //得到全部DOC的Range变量 m_wdRange = m_wdDoc.Range(vOptional,vOptional); if(!m_wdRange.m_lpDispatch) { AfxMessageBox("Range获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } return TRUE; } BOOL CCreateWordReport::Open(CString fileName) { if (FALSE == CreateDocuments()) { return FALSE; } return OpenDocument(fileName); } BOOL CCreateWordReport::SetActiveDocument(short i) { COleVariant vIndex(_T(i)),vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR); m_wdDoc.AttachDispatch(m_wdDocs.Item(vIndex)); m_wdDoc.Activate(); if (!m_wdDoc.m_lpDispatch) { AfxMessageBox("Document获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } //得到selection变量 m_wdSel = m_wdApp.get_Selection(); if (!m_wdSel.m_lpDispatch) { AfxMessageBox("Select获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } //得到全部DOC的Range变量 m_wdRange = m_wdDoc.Range(vOptional,vOptional); if(!m_wdRange.m_lpDispatch) { AfxMessageBox("Range获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } // HideApp(); return TRUE; } BOOL CCreateWordReport::SaveDocument() { if (!m_wdDoc.m_lpDispatch) { AfxMessageBox("Document获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } m_wdDoc.Save(); return TRUE; } BOOL CCreateWordReport::SaveDocumentAs(CString fileName) { if (!m_wdDoc.m_lpDispatch) { AfxMessageBox("Document获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } COleVariant covOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR); COleVariant varZero((short)0); COleVariant varTrue(short(1),VT_BOOL); COleVariant varFalse(short(0),VT_BOOL); COleVariant vFileName(_T(fileName)); m_wdDoc.SaveAs( vFileName, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional ); return TRUE; } BOOL CCreateWordReport::CloseDocument() { COleVariant vTrue((short)TRUE), vFalse((short)FALSE), vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR); m_wdDoc.Close(vFalse, // SaveChanges. vTrue, // OriginalFormat. vFalse // RouteDocument. ); m_wdDoc.AttachDispatch(m_wdApp.get_ActiveDocument()); if (!m_wdDoc.m_lpDispatch) { AfxMessageBox("Document获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } //得到selection变量 m_wdSel = m_wdApp.get_Selection(); if (!m_wdSel.m_lpDispatch) { AfxMessageBox("Select获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } //得到全部DOC的Range变量 m_wdRange = m_wdDoc.Range(vOptional,vOptional); if(!m_wdRange.m_lpDispatch) { AfxMessageBox("Range获取失败!", MB_OK|MB_ICONWARNING); return FALSE; } return TRUE; } void CCreateWordReport::CloseApp() { COleVariant vTrue((short)TRUE), vFalse((short)FALSE), vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR); m_wdDoc.Save(); m_wdApp.Quit(vFalse, // SaveChanges. vTrue, // OriginalFormat. vFalse // RouteDocument. ); //释放内存申请资源 m_wdInlineShape.ReleaseDispatch(); m_wdInlineShapes.ReleaseDispatch(); //m_wdTb.ReleaseDispatch(); m_wdRange.ReleaseDispatch(); m_wdSel.ReleaseDispatch(); //m_wdFt.ReleaseDispatch(); m_wdDoc.ReleaseDispatch(); m_wdDocs.ReleaseDispatch(); m_wdApp.ReleaseDispatch(); } void CCreateWordReport::WriteText(CString szText) { m_wdSel.TypeText(szText); } void CCreateWordReport::WriteNewLineText(CString szText, int nLineCount /* = 1 */) { int i; if (nLineCount <= 0) { nLineCount = 0; } for (i = 0; i < nLineCount; i++) { m_wdSel.TypeParagraph(); } WriteText(szText); } void CCreateWordReport::WriteEndLine(CString szText) { m_wdRange.InsertAfter(szText); } void CCreateWordReport::WholeStory() { m_wdRange.WholeStory(); } void CCreateWordReport::Copy() { m_wdRange.CopyAsPicture(); } void CCreateWordReport::InsertFile(CString fileName) { COleVariant vFileName(fileName), vTrue((short)TRUE), vFalse((short)FALSE), vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR), vNull(_T("")); /* void InsertFile(LPCTSTR FileName, VARIANT* Range, VARIANT* ConfirmConversions, VARIANT* Link, VARIANT* Attachment); */ m_wdSel.InsertFile( fileName, vNull, vFalse, vFalse, vFalse ); } void CCreateWordReport::InsertShapes(CString fileName) { COleVariant vTrue((short)TRUE), vFalse((short)FALSE), vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR); m_wdInlineShapes=m_wdSel.get_InlineShapes(); m_wdInlineShape=m_wdInlineShapes.AddPicture(fileName,vFalse,vTrue,vOptional); } void CCreateWordReport::InsertHyperlink(CString fileLink) { COleVariant vAddress(_T(fileLink)),vSubAddress(_T("")); CRange aRange = m_wdSel.get_Range(); CHyperlinks vHyperlinks(aRange.get_Hyperlinks()); vHyperlinks.Add( aRange, //Object,必需。转换为超链接的文本或图形。 vAddress, //Variant 类型,可选。指定的链接的地址。此地址可以是电子邮件地址、Internet 地址或文件名。请注意,Microsoft Word 不检查该地址的正确性。 vSubAddress, //Variant 类型,可选。目标文件内的位置名,如书签、已命名的区域或幻灯片编号。 vAddress, //Variant 类型,可选。当鼠标指针放在指定的超链接上时显示的可用作“屏幕提示”的文本。默认值为 Address。 vAddress, //Variant 类型,可选。指定的超链接的显示文本。此参数的值将取代由 Anchor 指定的文本或图形。 vSubAddress //Variant 类型,可选。要在其中打开指定的超链接的框架或窗口的名字。 ); aRange.ReleaseDispatch(); vHyperlinks.ReleaseDispatch(); }这样我们就封装好了一些基本的操作,其实这些操作都是我自己根据网上的资料以及VB宏转化而来得到的代码。特殊操作在这里主要介绍一些比较骚的操作,这也是这篇文章主要有用的内容,前面基本操作网上都有源代码直接拿来用就OK了,这里的骚操作是我在项目中使用的主要操作,应该有应用价值。先请各位仔细想想,如果我们要根据前面的代码,从0开始完全用代码生成一个完整的报表是不是很累,而且一般报表都会包含一些通用的废话,这些话基本不会变化。如果将这些写到代码里面,如果后面这些话变了,我们就要修改并重新编译,是不是很麻烦。所以这里介绍的第一个操作就是利用模板和书签在合适的位置插入内容。书签的使用首先我们在Word中的适当位置创建一个标签,至于如何创建标签,请自行百度。然后在代码中的思路就是在文档中查找我们的标签,再获取光标的位置,最后就是在该位置处添加相应的内容了,这里我们举一个在光标位置插入文本的例子:void CCreateWordReport::WriteTextToBookMark(const CString& csMarkName, const CString& szText) { CBookmarks bks = m_wdDoc.get_Bookmarks(); //获取文档中的所有书签 CBookmark0 bk; COleVariant bk_name(csMarkName); bk = bks.Item(&bk_name); //查询对应名称的书签 CRange hRange = bk.get_Range(); //获取书签位置 if (hRange != NULL) { hRange.put_Text(szText); //在该位置处插入文本 } //最后不要忘记清理相关资源 hRange.ReleaseDispatch(); bk.ReleaseDispatch(); bks.ReleaseDispatch(); }表格的使用在word报表中表格应该是一个重头戏,表格中常用的接口如下:CTables0: 表格集合CTable0: 某个具体的表格,一般通过CTables来创建CTableCColumn: 表格列对象CRow:表格行对象CCel:表格单元格对象创建表格一般的操作如下:void CCreateWordReport::InsertTable(int nRow, int nColumn, CTable0& wdTable) { VARIANT vtDefault; COleVariant vtAuto; vtDefault.vt = VT_INT; vtDefault.intVal = 1; vtAuto.vt = VT_INT; vtAuto.intVal = 0; CTables0 wordtables = m_wdDoc.get_Tables(); wdTable = wordtables.Add(m_wdSel.get_Range(), nRow, nColumn, &vtDefault, &vtAuto); wordtables.ReleaseDispatch(); }往表格中写入内容的操作如下:BOOL CCreateWordReport::WriteDataToTable(CTable0& wdTable, int nRow, int nColumn, const CString &csData) { CCell cell = wdTable.Cell(nRow, nColumn); cell.Select(); //将光标移动到单元格 m_wdSel.TypeText(csData); cell.ReleaseDispatch(); return TRUE; }合并单元格的操作如下:CTable0 wdTable; InsertTable(5, 3, wdTable); //创建一个5行3列的表格 CCell cell = wdTable.Cell(1, 1); //获得第一行第一列的单元格 //设置第二列列宽 CColumns0 columns = wdTable.get_Columns(); CColumn col; col.AttachDispatch(columns.Item(2)); col.SetWidth(40, 1); cell.Merge(wdTable.Cell(5, 1)); //合并单元格,一直合并到第5行的第1列。 cell.SetWidth(30, 1); cell.ReleaseDispatch();合并单元格用的是Merge函数,该函数的参数是一个单元格对象,表示合并结束的单元格。这里合并类似于我们画矩形时提供的左上角坐标和右下角坐标移动光标跳出表格当时由于需要连续的生成多个表格,当时我将前一个表格的数据填完,光标位于最后一个单元格里面,这个时候如果再插入的时候会在这个单元格里面插入表格,这个时候需要我手动向下移动光标,让光标移除到表格外。移动光标的代码如下:m_wdSel.MoveDown(&COleVariant((short)wdLine), &COleVariant((short)1), &COleVariant((short)wdNULL)); 这里wdLine 是word相关接口定义的,表示希望以何种单位来移动,这里我是以行为单位。后面的1表示移动1行。但是我发现在面临换页的时候一次移动根本移动不出来,这个时候我又添加了一行这样的代码移动两行。但是问题又出现了,这一系列表格后面跟着另一个大标题,多移动几次之后可能会造成它移动到大标题的位置,而破坏我原来定义的模板,这个时候该怎么办呢?我采取的办法是,判断当前光标是否在表格中,如果是则移动一行,知道出了表格。这里的代码如下://移动光标,直到跳出表格外 while (TRUE) { m_wdSel.MoveDown(&COleVariant((short)wdLine), &COleVariant((short)1), &COleVariant((short)wdNULL)); m_wdSel.Collapse(&COleVariant((short)wdCollapseStart)); if (!m_wdSel.get_Information((long)wdWithInTable).boolVal) { break; } }样式的使用在使用样式的时候当然也可以用代码来定义,但是我们可以采取另一种方式,我们可以事先在模板文件中创建一系列样式,然后在需要的时候直接定义段落或者文本的样式即可m_wdSel.put_Style(COleVariant("二级标题")); //在当前光标处的样式定义为二级标题样式,这里的二级标题样式是我们在word中事先定义好的 m_wdSel.TypeText(csTitle); //在当前位置输出文本 m_wdSel.TypeParagraph(); //插入段落,这里主要为了换行,这个时候光标也会跟着动 m_wdSel.put_Style(COleVariant("正文")); //定义此处样式为正文样式 m_wdSel.TypeText(csText;插入图表我自己尝试用word生成的图表样式还可以,但是用代码插入的时候,样式就特别丑,这里没有办法,我采用GDI+绘制了一个饼图,然后将图片插入word中。BOOL CCreateWordReport::DrawVulInforPic(const CString& csMarkName, int nVulCnt, int nVulCris, int nHigh, int nMid, int nLow, int nPossible) { CBookmarks bks = m_wdDoc.get_Bookmarks(); COleVariant bk_name(csMarkName); CBookmark0 bk = bks.Item(&bk_name); bk.Select(); CnlineShapes isps = m_wdSel.get_InlineShapes(); COleVariant vFalse((short)FALSE); COleVariant vNull(""); COleVariant vOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR); //创建一个与桌面环境兼容的内存DC HWND hWnd = GetDesktopWindow(); HDC hDc = GetDC(hWnd); HDC hMemDc = CreateCompatibleDC(hDc); HBITMAP hMemBmp = CreateCompatibleBitmap(hDc, PICTURE_WIDTH + GLOBAL_MARGIN, PICTURE_LENGTH + 2 * GLOBAL_MARGIN + LENGED_BORDER_LENGTH); SelectObject(hMemDc, hMemBmp); //绘制并保存图表 DrawPie(hMemDc, nVulCnt, nVulCris, nHigh, nMid, nLow, nPossible); COleVariant vTrue((short)TRUE); CnlineShape isp=isps.AddPicture("D:\\Program\\WordReport\\WordReport\\test.png",vFalse,vTrue,vOptional); //以图片的方式插入图表 //设置图片的大小 isp.put_Height(141); isp.put_Width(423); bks.ReleaseDispatch(); bk.ReleaseDispatch(); isps.ReleaseDispatch(); isp.ReleaseDispatch(); DeleteObject(hMemDc); DeleteDC(hMemDc); ReleaseDC(hWnd, hDc); return TRUE; }最后,各个接口的参数可以参考下面的链接:.net Word office组件接口文档
2019年04月14日
2 阅读
0 评论
0 点赞
2018-11-24
VC++ IPv6的支持
最近根据项目需要,要在产品中添加对IpV6的支持,因此研究了一下IPV6的相关内容,Ipv6 与原来最直观的改变就是地址结构的改变,IP地址由原来的32位扩展为128,这样原来的地址结构肯定就不够用了,根据微软的官方文档,只需要对原来的代码做稍许改变就可以适应ipv6。修改地址结构Windows Socket2 针对Ipv6的官方描述根据微软官方的说法,要做到支持Ipv6首先要做的就是将原来的SOCKADDR_IN等地址结构替换为SOCKADDR_STORAGE 该结构的定义如下:typedef struct sockaddr_storage { short ss_family; char __ss_pad1[_SS_PAD1SIZE]; __int64 __ss_align; char __ss_pad2[_SS_PAD2SIZE]; } SOCKADDR_STORAGE, *PSOCKADDR_STORAGE;ss_family:代表的是地址家族,IP协议一般是AF_INET, 但是如果是IPV6的地址这个参数需要设置为 AF_INET6。后面的成员都是作为保留字段,或者说作为填充结构大小的字段,这个结构兼容了IPV6与IPV4的地址结构,跟以前的SOCKADDR_IN结构不同,我们现在不能直接从SOCKADDR_STORAGE结构中获取IP地址了。也没有办法直接往结构中填写IP地址。使用兼容函数除了地址结构的改变,还需要改变某些函数,有的函数是只支持Ipv4的,我们需要将这些函数改为即兼容的函数,根据官方的介绍,这些兼容函数主要是下面几个:WSAConnectByName : 可以直接通过主机名建立一个连接WSAConnectByList: 从一组主机名中建立一个连接getaddrinfo: 类似于gethostbyname, 但是gethostbyname只支持IPV4所以一般用这个函数来代替GetAdaptersAddresses: 这个函数用来代替原来的GetAdaptersInfoWSAConnectByName函数:函数原型如下:BOOL PASCAL WSAConnectByName( __in SOCKET s, __in LPSTR nodename, __in LPSTR servicename, __inout LPDWORD LocalAddressLength, __out LPSOCKADDR LocalAddress, __inout LPDWORD RemoteAddressLength, __out LPSOCKADDR RemoteAddress, __in const struct timeval* timeout, LPWSAOVERLAPPED Reserved ); s: 该参数为一个新创建的未绑定,未与其他主机建立连接的SOCKET,后续会采用这个socket来进行收发包的操作nodename: 主机名,或者主机的IP地址的字符串servicename: 服务名称,也可以是对应的端口号的字符串,传入服务名时需要传入那些知名的服务,比如HTTP、FTP等等, 其实这个字段本身就是需要传入端口的,传入服务名,最后函数会根据服务名称转化为这些服务的默认端口LocalAddressLength, LocalAddress, 返回当前地址结构,与长度RemoteAddressLength, RemoteAddress,返回远程主机的地址结构,与长度timeout: 超时值Reserved: 重叠IO结构为了使函数能够支持Ipv6,需要在调用前使用setsockopt函数对socket做相关设置,设置的代码如下:iResult = setsockopt(ConnSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only) );调用函数的例子如下(该实例为微软官方的例子):SOCKET OpenAndConnect(LPWSTR NodeName, LPWSTR PortName) { SOCKET ConnSocket; DWORD ipv6only = 0; int iResult; BOOL bSuccess; SOCKADDR_STORAGE LocalAddr = {0}; SOCKADDR_STORAGE RemoteAddr = {0}; DWORD dwLocalAddr = sizeof(LocalAddr); DWORD dwRemoteAddr = sizeof(RemoteAddr); ConnSocket = socket(AF_INET6, SOCK_STREAM, 0); if (ConnSocket == INVALID_SOCKET){ return INVALID_SOCKET; } iResult = setsockopt(ConnSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, sizeof(ipv6only) ); if (iResult == SOCKET_ERROR){ closesocket(ConnSocket); return INVALID_SOCKET; } bSuccess = WSAConnectByName(ConnSocket, NodeName, PortName, &dwLocalAddr, (SOCKADDR*)&LocalAddr, &dwRemoteAddr, (SOCKADDR*)&RemoteAddr, NULL, NULL); if (bSuccess){ return ConnSocket; } else { return INVALID_SOCKET; } }WSAConnectByList该函数从传入的一组hostname中选取一个建立连接,函数内部会调用WSAConnectByName,它的原型,使用方式与WSAConnectByName类似,这里就不再给出具体的原型以及调用方法了。getaddrinfo该函数的作用与gethostbyname类似,但是它可以同时支持获取V4、V6的地址结构,函数原型如下:int getaddrinfo( const char FAR* nodename, const char FAR* servname, const struct addrinfo FAR* hints, struct addrinfo FAR* FAR* res );nodename: 主机名或者IP地址的字符串servname: 知名服务的名称或者端口的字符串hints:一个地址结构,该结构规定了应该如何进行地址转化。res:与gethostbyname类似,它也是返回一个地址结构的链表。后续只需要遍历这个链表即可。使用的实例如下:char szServer[] = "www.baidu.com"; char szPort[] = "80"; addrinfo hints = {0}; struct addrinfo* ai = NULL; getaddrinfo(szServer, szPort, NULL, &ai); while (NULL != ai) { SOCKET sConnect = socket(ai->ai_family, SOCK_STREAM, ai->ai_protocol); connect(sConnect, ai->ai_addr, ai->ai_addrlen); shutdown(sConnect, SD_BOTH); closesocket(sConnect); ai = ai->ai_next; } freeaddrinfo(ai); //最后别忘了释放链表针对硬编码的情况针对这种情况一般是修改硬编码,如果希望你的应用程序即支持IPV6也支持IPV4,那么就需要去掉这些硬编码的部分。微软提供了一个工具叫"Checkv4.exe" 这个工具一般是放到VS的安装目录中,作为工具一起安装到本机了,如果没有可以去官网下载。工具的使用也非常简单checkv4.exe 对应的.h或者.cpp 文件这样它会给出哪些代码需要进行修改,甚至会给出修改意见,我们只要根据它的提示修改代码即可。几个例子因为IPV6 不能再像V4那样直接往地址结构中填写IP了,因此在IPV6的场合需要大量使用getaddrinfo函数,来根据具体的IP字符串或者根据主机名来自动获取地址信息,然后根据地址信息直接调用connect即可,下面是微软的例子int ResolveName(char *Server, char *PortName, int Family, int SocketType) { int iResult = 0; ADDRINFO *AddrInfo = NULL; ADDRINFO *AI = NULL; ADDRINFO Hints; memset(&Hints, 0, sizeof(Hints)); Hints.ai_family = Family; Hints.ai_socktype = SocketType; iResult = getaddrinfo(Server, PortName, &Hints, &AddrInfo); if (iResult != 0) { printf("Cannot resolve address [%s] and port [%s], error %d: %s\n", Server, PortName, WSAGetLastError(), gai_strerror(iResult)); return SOCKET_ERROR; } if(NULL != AddrInfo) { SOCKET sConnect = socket(AddrInfo->ai_family, SOCK_STREAM, AddrInfo->ai_protocol); connect(sConnect, AddrInfo->ai_addr, AddrInfo->ai_addrlen); shutdown(sConnect, SD_BOTH); closesocket(sConnect); } freeaddrinfo(AddrInfo); return 0; }这个例子需要传入额外的family参数来规定它使用何种地址结构,但是如果我只有一个主机名,而且事先并不知道需要使用何种IP协议来进行通信,这种情况下又该如何呢?针对服务端,不存在这个问题,服务端是我们自己的代码,具体使用IPV6还是IPV4这个实现是可以确定的,因此可以采用跟上面类似的写法:BOOL Create(int af_family) { //这里不建议使用IPPROTO_IP 或者IPPROTO_IPV6,使用TCP或者UDP可能会好点,因为它们是建立在IP协议之上的 //当然,具体情况具体分析 s = socket(af_family, SOCK_STREAM, IPPROTO_TCP); } BOOL Bind(int family, UINT nPort) { addrinfo hins = {0}; hins.ai_family = family; hins.ai_flags = AI_PASSIVE; /* For wildcard IP address */ hins.ai_protocol = IPPROTO_TCP; hins.ai_socktype = SOCK_STREAM; addrinfo *lpAddr = NULL; CString csPort = ""; csPort.Format("%u", nPort); if (0 != getaddrinfo(NULL, csPort, &hins, &lpAddr)) { closesocket(s); return FALSE; } int nRes = bind(s, lpAddr->ai_addr, lpAddr->ai_addrlen); freeaddrinfo(lpAddr); if(nRes == 0) return TRUE; return FALSE; } //监听,以及后面的收发包并没有区分V4和V6,因此这里不再给出跟他们相关的代码针对服务端,我们自然没办法事先知道它使用的IP协议的版本,因此传入af_family参数在这里不再适用,我们可以利用getaddrinfo函数根据服务端的主机名或者端口号来提前获取它的地址信息,这里我们可以封装一个函数int GetAF_FamilyByHost(LPCTSTR lpHost, int nPort, int SocketType) { addrinfo hins = {0}; addrinfo *lpAddr = NULL; hins.ai_family = AF_UNSPEC; hins.ai_socktype = SOCK_STREAM; hins.ai_protocol = IPPROTO_TCP; CString csPort = ""; csPort.Format("%u", nPort); int af = AF_UNSPEC; char host[MAX_HOSTNAME_LEN] = ""; if (lpHost == NULL) { gethostname(host, MAX_HOSTNAME_LEN);// 如果为NULL 则获取本机的IP地址信息 }else { strcpy_s(host, MAX_HOSTNAME_LEN, lpHost); } if(0 != getaddrinfo(host, csPort, &hins, &lpAddr)) { return af; } af = lpAddr->ai_family; freeaddrinfo(lpAddr); return af; }有了地址家族信息,后面的代码即可以根据地址家族信息来分别处理IP协议的不同版本,也可以使用上述服务端的思路,直接使用getaddrinfo函数得到的addrinfo结构中地址信息,下面给出第二种思路的部分代码:if(0 != getaddrinfo(host, csPort, &hins, &lpAddr)) { connect(s, lpAddr->ai_addr, lpAddr->ai_addrlen); }当然,也可以使用前面提到的 WSAConnectByName 函数,不过它需要针对IPV6来进行特殊的处理,需要事先知道服务端的IP协议的版本。VC中各种地址结构在学习网络编程中,一个重要的概念就是IP地址,而巴克利套接字中提供了好几种结构体来表示地址结构,微软针对WinSock2 又提供了一些新的结构体,有的时候众多的结构体让人眼花缭乱,在这我根据自己的理解简单的回顾一下这些常见的结构SOCKADD_IN 与sockaddr_in结构在Winsock2 中这二者是等价的, 它们的定义如下:struct sockaddr_in{ short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8]; };sin_family: 地址协议家族sin_port:端口号sin_addr: 表示ip地址的结构sin_zero: 用于与sockaddr 结构体的大小对齐,这个数组里面为全0in_addr 结构如下:struct in_addr { union { struct{ unsigned char s_b1, s_b2, s_b3, s_b4; } S_un_b; struct { unsigned short s_w1, s_w2; } S_un_w; unsigned long S_addr; } S_un; }; 这个结构是一个公用体,占4个字节,从本质上将IP地址仅仅是一个占4个字节的无符号整型数据,为了方便读写才会采用点分十进制的方式。仔细观察这个结构会发现,它其实定义了IP地址的几种表现形式,我们可以将IP地址以一个字节一个字节的方式拆开来看,也可以以两个字型数据的形式拆开,也可以简单的看做一个无符号长整型。当然在写入的时候按照这几种方式写入,为了方便写入IP地址,微软定义了一个宏:#define s_addr S_un.S_addr因此在填入IP地址的时候可以简单的使用这个宏来给S_addr这个共用体成员赋值一般像bind、connect等函数需要传入地址结构,它们需要的结构为sockaddr,但是为了方便都会传入SOCKADDR_IN结构sockaddr SOCKADDR结构这两个结构也是等价的,它们的定义如下struct sockaddr { unsigned short sa_family; char sa_data[14];};从结构上看它占16个字节与 SOCKADDR_IN大小相同,而且第一个成员都是地址家族的相关信息,后面都是存储的具体的IPV4的地址,因此它们是可以转化的,为了方便一般是使用SOCKADDR_IN来保存IP地址,然后在需要填入SOCKADDR的时候强制转化即可。sockaddr_in6该结构类似于sockaddr_in,只不过它表示的是IPV6的地址信息,在使用上,由于IPV6是128的地址占16个字节,而sockaddr_in 中表示地址的部分只有4个字节,所以它与之前的两个是不能转化的,在使用IPV6的时候需要特殊的处理,一般不直接填写IP而是直接根据IP的字符串或者主机名来连接。sockaddr_storage这是一个通用的地址结构,既可以用来存储IPV4地址也可以存储IPV6的地址,这个地址结构在前面已经说过了,这里就不再详细解释了。各种地址之间的转化一般我们只使用从SOCKADDR_IN到sockaddr结构的转化,而且仔细观察socket函数族发现只需要从其他结构中得到sockaddr结构,而并不需要从sockaddr转化为其他结构,因此这里重点放在如何转化为sockaddr结构从SOCKADDR_IN到sockaddr只需要强制类型转化即可从addrinfo结构中只需要调用其成员即可从SOCKADDR_STORAGE结构到sockaddr只需要强制转化即可。其实在使用上更常用的是将字符串的IP转化为对应的数值,针对IPV4有我们常见的inet_addr、inet_ntoa 函数,它们都是在ipv4中使用的,针对v6一般使用inet_pton,inet_ntop来转化,它们两个分别对应于inet_addr、inet_ntoa。但是在WinSock中更常用的是WSAAddressToString 与 WSAStringToAddressINT WSAAddressToString( LPSOCKADDR lpsaAddress, DWORD dwAddressLength, LPWSAPROTOCOL_INFO lpProtocolInfo, OUT LPTSTR lpszAddressString, IN OUT LPDWORD lpdwAddressStringLength );lpsaAddress: ip地址dwAddressLength: 地址结构的长度lpProtocolInfo: 协议信息的结构体,这个结构一般给NULLlpszAddressString:目标字符串的缓冲lpdwAddressStringLength:字符串的长度而WSAStringToAddress定义与使用与它类似,这里就不再说明了。
2018年11月24日
3 阅读
0 评论
0 点赞
2015-09-11
VC++平台上的内存对齐操作
我们知道当内存的边界正好对齐在相应机器字长边界上时,CPU的执行效率最高,为了保证效率,在VC++平台上内存对齐都是默认打开的,在32位机器上内存对齐的边界为4字节;比如看如下的代码:struct MyStruct { int i; char c; }; int _tmain(int argc, _TCHAR* argv[]) { cout<<sizeof(MyStruct)<<endl; return 0; } 此时输出的结果并不是sizeof(int) + sizeof(char) = 5而是8,因为内存对齐的原因,将char分配为4个字节效率更高;在VC平台上我们可以通过预处理指令:#pragma pack(show)来查看当前内存对齐的方式,我们在代码前加上一句#pragma pack(show),再次编译,在编译器的“生成”窗口中看到一个警告:“warning C4810: 杂注 pack(show) 的值 == 8”说明这时编译器采用的是8字节的对齐方式,另外可以通过这个预处理指令更改对齐方式,比如将代码改写一下:#pragma pack(show) #pragma pack(1) struct MyStruct { int i; char c; }; int _tmain(int argc, _TCHAR* argv[]) { cout<<sizeof(MyStruct)<<endl; system("PAUSE"); return 0; }这个时候得到结果为5,也就是说我们已经将对齐方式改为了1;除了这个预处理指令我们也可以通过VC++扩展关键字align来改变内存的对齐方式:#pragma pack(show) #pragma pack(1) struct MyStruct { int i; char c; }; struct __declspec(align(1)) MyStruct1 { int i; char c; }; int _tmain(int argc, _TCHAR* argv[]) { cout<<sizeof(MyStruct)<<endl; cout<<sizeof(MyStruct1)<<endl; system("PAUSE"); return 0; }这个时候输出的结果为两个5;
2015年09月11日
3 阅读
0 评论
0 点赞