C++随笔第一期

第一期C++随笔

打算从现在开始记录工作中碰到的问题以及各种学习到的东西,先用CSDN的markdown来练练手,后续会不断地添加新的学习心得,仅作为记录学习过程笔记,可能会有不严谨甚至错误的地方望大家指正,感谢!

一、工作问题

1、高版本编译器搭配Qt4时碰到的问题:使用 Qt4 时,更高版本的编译器可能会报告“无法解析的外部符号(包含一堆右值引用的运算符重载)”错误。这是因为Qt4对不同编译器对右值引用的支持进行了限制,需要注释掉头文件qglobal.h中的Q_COMPILER_RVALUE_REFS宏。
2、IDispatch类型理解IDispatch 是 COM(Component Object Model)中的一个接口,它提供了一种机制,使得 COM 对象可以动态地响应客户端的请求,即使这些请求在编译时并不存在。IDispatch 接口允许客户端通过字符串名称来调用对象的方法,而不是通过预定义的接口。这种动态调用的能力使得 COM 对象更加灵活,可以支持运行时的脚本语言和自动化。

IDispatch 接口的主要功能包括:

  1. 动态方法调用:客户端可以通过字符串形式的方法名来调用对象的方法,而不需要知道方法的具体签名。

  2. 属性访问IDispatch 支持通过字符串名称来读取和设置对象的属性值。

  3. 事件处理IDispatch 可以用于实现事件通知机制,允许客户端订阅和接收来自 COM 对象的事件。

  4. 自动化支持IDispatch 是自动化(Automation)的基础,它使得 COM 对象可以被脚本语言(如 VBScript、JavaScript)和宏语言(如 VBA)调用。

  5. 类型信息IDispatch 可以提供类型信息,帮助客户端了解对象的方法和属性。

在 COM 中,IDispatch 接口通常与 ITypeInfo 接口一起使用,后者提供了关于 COM 对象类型(如方法、属性、事件等)的详细信息。通过 IDispatch,客户端可以在运行时动态地发现和使用 COM 对象的功能,而不需要事先知道对象的完整接口定义。

例如,如果你有一个 COM 对象,它有一个名为 DoSomething 的方法,客户端可以通过 IDispatch 接口动态调用这个方法,即使在客户端代码中没有直接声明这个方法。这种灵活性使得 COM 成为构建可扩展和可互操作应用程序的强大工具。
3、关于微软Imapi2sample刻录示例流程的初步分析:
在使用 IMAPI2(Image Mastering API 2)进行光盘刻录时,通常会涉及到以下几个核心 COM 接口,以及它们在刻录过程中的作用:

  1. IDiscRecorder2

    • 功能:代表一个物理光盘刻录机,提供了对刻录机的控制,如打开和关闭托盘、获取设备信息、设置刻录参数等。
    • 使用:在刻录之前,你需要创建 IDiscRecorder2 对象的实例,并初始化它以指定要使用的刻录机。
  2. IDiscFormat2Data

    • 功能:用于创建和管理数据光盘的映像文件,支持一次性刻录(DAO)、增量刻录(TAO)等模式。
    • 使用:在刻录数据到光盘之前,你需要创建 IDiscFormat2Data 对象,设置刻录参数(如写入速度、媒体类型等),并添加要刻录的文件。
  3. IDiscFormat2Erase

    • 功能:用于擦除可重写的光盘,如 CD-RW 或 DVD-RW。
    • 使用:在需要擦除光盘上的数据时,创建 IDiscFormat2Erase 对象,并调用其 Erase 方法。
  4. IFileSystemImage

    • 功能:用于创建和管理文件系统映像,这是准备刻录到光盘的数据结构。
    • 使用:在创建数据光盘之前,你需要使用 IFileSystemImage 接口来构建文件系统映像,包括添加文件和目录。
  5. IFileSystemImageResult

    • 功能:包含了最终的文件系统映像数据流,用于实际的刻录过程。
    • 使用:在 IFileSystemImage 中构建好映像后,你需要获取 IFileSystemImageResult 对象,它包含了准备刻录的数据。
  6. IDiscFormat2DataEvents

    • 功能:提供了刻录过程中的事件通知,如刻录进度、错误发生等。
    • 使用:在调用 IDiscFormat2DataWrite 方法开始刻录时,你会得到一个 IDiscFormat2DataEvents 对象,你可以监听这个对象的事件来获取刻录状态。

整体流程大致如下:

  1. 初始化刻录机

    • 使用 CoCreateInstance 创建 IDiscRecorder2 对象,并初始化它。
  2. 创建文件系统映像

    • 使用 IFileSystemImage 创建文件系统映像,添加要刻录的文件。
  3. 设置刻录参数

    • 创建 IDiscFormat2Data 对象,设置刻录参数,如写入速度、媒体类型等。
  4. 开始刻录

    • 使用 IDiscFormat2DataWrite 方法开始刻录过程,并获取 IDiscFormat2DataEvents 对象以监听事件。
  5. 处理事件

    • 在刻录过程中,处理 IDiscFormat2DataEvents 提供的事件,如进度更新、错误通知等。
  6. 完成刻录

    • 刻录完成后,释放所有 COM 对象,确保资源被正确释放。

请注意,这个流程是一个高层次的概述,实际的实现可能会涉及到更多的细节和错误处理。此外,IMAPI2 的使用可能受到操作系统版本的限制,确保你的应用程序在支持 IMAPI2 的 Windows 版本上运行。
4、QueryInterface方法与CoCreateInstance方法的区别与联系:
QueryInterface 方法和 CoCreateInstance 函数在 COM(Component Object Model)编程中扮演着不同的角色,它们的主要区别和联系如下:

CoCreateInstance

  • CoCreateInstance 是一个 COM API 函数,用于在运行时创建 COM 对象的实例。
  • 它接受一个类标识符(CLSID)和一个接口标识符(IID),然后根据这些标识符创建并返回一个 COM 对象的接口指针。
  • CoCreateInstance 是客户端用来动态创建 COM 对象的标准方法,它允许客户端在不知道对象具体实现的情况下创建对象。

应用场景:

  • 当你需要创建一个 COM 对象,并且知道它的 CLSID 和你想要使用的接口的 IID 时,你可以使用 CoCreateInstance
  • 例如,如果你有一个数据库连接字符串,并且想要创建一个 ADO(ActiveX Data Objects)数据库连接对象,你可以使用 CoCreateInstance 来创建 IADOConnection 接口的实例。

QueryInterface

  • QueryInterfaceIUnknown 接口中的一个方法,用于在已经创建的 COM 对象上查询和获取其他接口的指针。
  • 它允许客户端在已经有一个接口的指针的情况下,请求对象支持的其他接口的指针。
  • QueryInterface 是实现 COM 聚合(Aggregation)和多接口支持的关键方法。

应用场景:

  • 当你已经有一个 COM 对象的某个接口的指针,并且想要访问该对象的其他接口时,你可以调用 QueryInterface
  • 例如,如果你有一个 IDispatch 接口的指针,并且想要使用 IDispatchEx 接口,你可以调用 QueryInterface 方法来获取 IDispatchEx 的指针。

联系:

  • CoCreateInstanceQueryInterface 都是 COM 编程中用于对象创建和接口获取的方法,但它们在对象生命周期的不同阶段发挥作用。
  • CoCreateInstance 用于对象的创建阶段,而 QueryInterface 用于对象创建后,当需要访问对象的其他接口时。

总结:

  • CoCreateInstance 是创建 COM 对象的入口点,而 QueryInterface 是在对象创建后,用于获取对象支持的其他接口的方法。两者共同支持了 COM 对象的动态创建和多接口访问,使得 COM 对象更加灵活和可扩展。

5、在Windows下32位进程调用某些API执行某些操作时可能会开启一个64位的代理进程进行处理,情况大致如下:

  • Shell Execute API(ShellExecute、ShellExecuteEx):用于启动应用程序、打开文件等操作。

  • COM(Component Object Model)API:用于实现组件之间的通信和交互。

  • Windows Script Host API:用于执行脚本语言(如VBScript、JScript)。

  • Windows Management Instrumentation (WMI) API:用于管理和监控Windows操作系统和应用程序。

  • Windows Installer API:用于安装、卸载和管理Windows应用程序。

6、Windows下关于PE文件的一些感悟:

  • 启动一个可执行模块的时候,操作系统的加载程序会先为进程创建虚拟地址空间,接着把可执行模块映射到进程的地址空间中。之后加载程序会检查可执行模块的导入段,试图对所需的 DLL 进行定位并将它们映射到进程的地址空间中。
  • 加载程序要载入所有这些 DLL模块,并用所有导出符号的正确地址来修复每个模块的导入段,这自然需要相当多的时间。由于这项工作是在进程初始化的时候完成的,因此它不会对应用程序的性能产生影响。但是,对许多应用程序来说,初始化过程太慢也是不可接受的。为了减少应用程序的载入时间,我们应该对自己的可执行模块和 DLL模块进行基地址重定位和绑定。这两项技术极其重要,但不幸的是,很少有开发人员知道该如何应用它们。如果每家公司都应用这两项技术,那么整个系统会运行得更好。

7、关于LoadLibrary与LoadLibraryEx返回值的认识:

  • 这两个函数会(按照Windows下搜索动态库的先后路径)在用户的系统中对 DLL的文件映像进行定位并试图将该文件映像映射到调用进程的地址空间中。两个函数返回的HMODULE 表示文件映像被映射到的虚拟内存地址。注意,这两个函数返回的是HMODULE值。这个HMODULE类型等价于HINSTANCE,两者可以换用。

8、关于Qt4中QFileSystemModel有关快捷方式展示的一个坑记录:

  • 当直接使用或者继承QFileSystemModel自定义时如果没有进行特殊处理,那么使用QDir设置目录节点后,一旦对应快捷方式的源文件目录不存在了,也就是快捷方式找不到源文件后,那么在模型关联的视图中是不会显示该快捷方式的,因为模型会无视这个快捷方式,但是如果只是源文件目录变动了或者改名了那么源文件是可以正常展示的。这个情况是否在Qt5或者Qt6中存在目前未知,后续碰到会更新笔记。
  • 上述问题通过询问大神跟踪Qt源码后得知,QFileSystemModel将独立的快捷方式识别为系统文件了,在源码中直接过滤掉了,所以模型中相当于没有添加数据。通过QDir::System标志就可以让模型不再过滤,这个标志会让模型按照不同操作系统的默认行为来处理文件和目录的查找。如果还未生效可以再设置一下setNameFilterDisables与setResolveSymlinks这两个接口进行尝试。

9、关于DllMain的认识:

  • 函数名 DllMain 是区分大小写的。许多开发人员不小心将这个函数拼写为 DLLMain这是一个非常容易犯的错误,因为术语 DLL 经常会用全部大写字母表示。如果我们将入口点函数命名为 DIlMain 之外的其他名称,那么虽然代码仍然能够编译和链接,但我们的入口点函数将永远不会被调用,DIL也永远不会进行初始化。
  • 必须记住,DLL 使用 DIlMain 函数来对自己进行初始化。DIlMain 函数执行的时候同一个地址空间中的其他 DLL 可能还没有执行它们的 DllMain。这意味着它们尚未初始化,因此我们应该避免调用那些从其他 DLL中导入的函数。此外,我们应该避免在 DIlMain 中调用 LoadLibrary(Ex)和FreeLibrary,因为这些函数可能会产生循环依赖。
  • Platform SDK 文档说 DllMain 函数只应该执行简单的初始化,比如设置线程局部存储区(参见第 21 章),创建内核对象,打开文件,等等。我们必须避免调用 User,Shell,ODBC,COM,RPC 以及套接字函数(或其他调用了这些函数的函数),这是因为包含这些函数的 DLL 可能尚未初始化完毕,或者函数可能会在内部调用LoadLibrary(Ex),从而产生循环依赖。另外值得注意的是,如果要创建全局或静态C++对象,会存在同样的问题,因为在DllMain 函数被调用的同时,这些对象的构造函数和析构函数也会被调用。DIlMain 入口点函数在执行的时候存在一些限制,这些限制与获取进程范围内的加载程序锁(loader lock)有关。
  • 如果进程终止是因为系统中的某个线程调用了 TerminateProcess,系统便不会用DLL PROCESS DETACH 来调用 DLL 的 DIlMain 函数。这意味着在进程终止之前,已映射到进程的地址空间中的任何DLL将没有机会执行任何清理代码。这可能会导致数据丢失。因此,除非万不得已,我们应该避免使用 TerminateProcess 函数。
  • 当进程创建一个线程的时候,系统会检查当前映射到该进程的地址空间中的所有DLL文件映像,并用 DLL THREAD ATTACH 来调用每个 DLL的 DIMain 函数。这告诉 DLL 需要执行与线程相关的初始化。新创建的线程负责执行所有DLL的DIMain 函数中的代码。只有当所有 DLL都完成了对该通知的处理之后,系统才会让新线程开始执行它的线程函数。
    当系统将一个新的 DLL 映射到进程的地址空间中时,如果进程中已经有多个线程在运行那么系统不会让任何已有的线程用DLLTHREADATTACH来调用该DLL的DIlMain函数。如果在创建新线程的时候 DLL已经被映射到进程的地址空间中,那么只有在这种情况下系统才会用 DLL THREAD ATTACH 来调用 DLL的 DIlMain 函数。
    另外要注意的是,系统不会让进程的主线程用DLL THREAD ATTACH值来调用 DIIMain函数。在进程创建的时候被映射到进程地址空间中的任何DLL会收到DLL PROCESS ATTACH 通知,但不会收到 DLL THREAD ATTACH 通知。
  • DllMain函数是动态链接库的入口函数,只有在通过LoadLibrary等API显式加载动态库时才会调用DllMain函数。而在隐式加载动态库时,即在应用程序启动时通过链接器自动加载动态库时,并不会调用DllMain函数。
  • 前面提到的这些规则可能会导致下面的情况:进程中的一个线程调用LoadLibrary 来载入一个DLL,这使得系统用 DLL PROCESS ATTACH 来调用该DLL的DIlMain 函数。(注意该线程不会得到 DLL THREAD ATTACH通知。)接着,载入该DLL的线程退出,这使得系统再次调用 DIMain 函数–但这次传入的是DLL THREAD DETACH。注意,虽然当系统将该线程连接到该DLL的时候,不会向该DLL发送 DLL THREAD ATTACH通知,但是当系统将该线程与DLL解除连接的时候,却会向该DLL发送DLL THREAD DETACH 通知。由于这个原因,我们在进行与线程相关的清理时必须极其小心。幸运的是,在大多数程序中,调用LoadLibrary的线程与调用FreeLibrary 的线程是同一个线程。
  • DisableThreadLibraryCalls 告诉系统,我们不想让系统向某个指定DLL的 DIMain 函数发送 DLL THREAD ATTACH和DLL THREAD DETACH 通知。我觉得如果告诉系统不要向该 DLL 发送 DLL 通知,就不会发生死锁的情形。但是,问题在于: 当系统创建进程的时候,会同时创建一个锁(在 Windows Vista 中是一个关键段)每个进程都有自己的锁–多个进程不会共享同一个锁。当进程中的线程调用映射到进程地址空间中的 DLL 的 DllMain 函数时,会用这个锁来同步各个线程。注意,在今后版本的Windows中,这个锁可能会消失。(Windows核心编程557页仔细讲述了DllMain与C++运行库之间的关系)

10、cmake编译Qt5.6.3项目时碰到的问题汇总

  1. 解决vs+Qt运行时报错:This application failed to start because no Qt platform plugin could be initialized:使用Qt在Windows下带的windeployqt 工具命令:windeployqt XXX.exe然后运行,就会把Qt应用程序依赖的库自动检索并拷贝到应用程序所在目录。
  2. 利用xcb将当前qt窗口置为活动状态(xlib也可以实现):
#include<xcb/xcb.h>

// 获取 XCB 连接
xcb_connection_t *connection = xcb_connect(nullptr, nullptr);

// 检查连接是否成功
if (connection == nullptr) 
{
 // 处理错误
 return;
}

// 获取默认屏幕
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(connection));
xcb_screen_t *screen = iter.data;
xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, false, 17, "_NET_ACTIVE_WINDOW");
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, cookie, NULL);
xcb_atom_t atom;
if(reply)
{
  atom = reply->atom;
  free(reply);
}
// 创建客户端消息
xcb_client_message_event_t client_message;
memset(&client_message, 0, sizeof(client_message));
client_message.response_type = XCB_CLIENT_MESSAGE;
client_message.window = widget->winId();
client_message.type = atom;
client_message.format = 32;

// 设置客户端消息的数据
client_message.data.data32[0] = 1; // _NET_WM_STATE_ADD
client_message.data.data32[1] = widget->winId();; // 消息类型
client_message.data.data32[2] = 0; // 第二个参数通常为 0
client_message.data.data32[3] = 0; // 第三个参数通常为 0
client_message.data.data32[4] = 0; // 第四个参数通常为 0

// 发送客户端消息
xcb_send_event(connection, false, widget->winId(), XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char*)&client_message);

// 断开 XCB 连接
xcb_disconnect(connection);
  1. CMAKE_CXX_FLAGS用于设置C++编译器的编译选项含义:

    • Wall: 打开所有警告选项。这可以帮助发现潜在的问题。

    • Wextra: 打开一些额外的警告选项。比如未使用的变量等。

    • Wfatal-errors: 将警告视为错误。如果有任何警告产生,编译将失败。这可以确保代码没有任何警告。

    • s: 压缩和去除调试符号表。这可以减小可执行文件的体积。

    • g: 生成调试信息。这将包含代码行号和符号信息,用于调试。

    • ggdb: 生成更多调试信息,用于gdb调试。

11、当新加固态后发现无法操作新的磁盘终极解决办法:

  • 试了好多网上的办法基本上就是修改安全属性的权限,但是试了都没用,终极办法就是在计算机管理中,磁盘管理界面将整个盘压缩然后删除原来的磁盘卷,然后再新建卷就可以了,其实就是格式化,但是直接格式化会报错无权限,这样就绕过去格式化了。

12、关于tuple的一部分理解以及资料内容:

  • std::tuple 就是想和 struct/class 一样,能存储不同类型的对象,而不是像 std::vector 等同质容器只能存储同类型对象。但是问题是,struct/class 的使用者,知道自己要存储什么类型的变量,以及要存储几个变量。但是 std::tuple 的设计者不知调用者要存储什么类型的变量,也不知道要存储几个。但是也要实现和 struct/class 一样的功能,怎么办?自然就是递归了,通过递归将 std::tuple 构造函数中的参数按照入栈的顺序生成内存模型,注意与普通类型相比,tuple由于借助了递归模板所以其内存等效模型与普通类型是顺序相反的。
auto t = std::make_tuple(1,2,3);

// 等效内存模型
class Tuple_eq { 
public:
  Tuple_eq() = default;
  // ...
private:
  int val_3_{3};
  int val_2_{2};
  int val_1_{1};
};

13、PCI总线号码的参数意义:
在 Linux 中,PCI 设备的设备名称(Device Name)通常以 domain:bus:slot:function 的形式来表示,其中冒号分隔开的各个数字具有以下含义:
domain:表示 PCI 设备所在的 PCI 域(Domain),通常为一个 16 位的十六进制数,用于区分不同的 PCI 域。在大多数情况下,这个值为 0000。
bus:表示 PCI 设备所在的总线(Bus),通常为一个 8 位的十六进制数,用于区分不同的总线。一个系统可以具有多个总线。
slot:表示 PCI 设备所在的插槽(Slot),通常为一个 5 位的十六进制数,用于区分不同的插槽。一个总线上可以有多个插槽。
function:表示 PCI 设备的功能(Function),通常为一个 3 位的十六进制数,用于区分同一插槽上的不同功能。一个插槽上可以有多个功能。
通过这种编号方式,可以唯一标识一个 PCI 设备的位置信息。在上述示例中,“0000:03:00.0” 表示该设备位于 PCI 域 0000,总线 03,插槽 00,功能 0。

请注意,这些数字可能会因系统配置而有所不同,具体取决于你的系统和相应的 PCI 设备。
14、关于运行时库、链接的记录:
首先静态库自身并不存在链接这一步,除非手动处理目标文件,不然静态库没办法静态链接运行时库,而动态库是有链接过程的,所以可以静态链接运行时库。至于当静态链接三方库与静态链接运行时库发生符号冲突时,可以将三方库换为动态库则可解决问题(动态库检测到符号存在就不会反复往内存映射)。还有就是只有运行时库的内存分配器知道进程那块内存用完了那块内存没用,操作系统也是不知道的,只有当内存分配器取消了某一段内存的映射后,操作系统才会感知到进程内存占用变小了,这就意味着如果进程中发生了一个模块一个堆这种问题时,不同内存分配器取消映射的情况可能不同,这就会导致操作系统会认为进程的内存一直处于高占用状态,但是实际上进程已经释放掉内存了只是还没有取消映射而已(也有可能发生了真实的内存泄漏)。当物理内存不够用了,这个时候新加进来的进程的内存页在加入之前需要将目前内存中其他进程的内存页面交换出去,放到磁盘的页交换文件中,等用到的时候再交换到内存上,这个过程相比于直接读取内存就多了很多额外开销,系统操作运行起来的进程都是在内存上,交换出去的内存页用到时必须再交换回来才可以,并不是直接操作磁盘!
15、C++异常捕获:
对于C++程序来说,std::runtime_error、std::system_error等继承于std::exception的异常类应该都可以使用catch(…)来捕获到,但是实践中发现,在Win7跟XP下即便是程序内部正常捕获了,二进制也会给系统发一份异常,虽然程序正确处理了异常,但是系统报告程序异常还是无法避免,以后编写代码需要注意这点,上述问题纯粹是因为我没写test is null导致的弱智问题,说的全都是错的,以后一定要引以为戒,尤其是在释放指针、使用指针的时候必须养成test is null的习惯
16、NTFS系统部分理解:
在NTFS文件系统中,当文件被删除时,系统并不会立即擦除磁盘上的文件数据。系统一般会更新文件的元数据,将其标记为“已删除”。这意味着文件所占用的磁盘空间被标记为可用,但实际数据仍然存在于磁盘上,直到被新的数据覆盖。
17、Windows下OOM错误:
Windwos下使用VS编程如果不开启大地址选项那么32位执行程序只有2G内存可以使用,开了大地址应该可以到3G左右;64位执行程序理论上可以使用相当于无线内存,一旦执行程序存在无限执行某些任务的情形尤其是UI程序,拿Qt来说本身占用就不小,如果反复不停打开某些界面,那么最终一定会导致OOM(out of memory)错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值