正确访问其他程序的 MSFlexGrid类

项目实战 同时被 2 个专栏收录
26 篇文章 3 订阅

1. 问题说明

在这里插入图片描述

获取右下方的表格数据,通过Spy++可知,该表格控件所属的类是:MSFlexGridWndClass
不幸的是,这个控件
在这里插入图片描述


2. 希望的曙光

感谢MSDN 在我搜索 MSFlexGridWndClass MSDN时 看到了一个

  • Forums—微软人员对这个问题MSFlexGrid UIAutomation C++ API的回答 MSFlexGridWndClass 是MSFlexGrid控件的一个实例,控件属于的类 其实叫 MSFlexGrid 好了 继续调研,我又觉得自己可以了~!!
    • 上面论坛消息提到了一个叫 Inspect tool的工具,搜一搜 找到个差不多的/更先进的,哈哈哈
    • Accessibility tools - Inspect : Inspect(Inspect.exe)是基于Windows的工具,使您可以选择任何UI元素并查看元素的可访问性数据。 您可以查看Microsoft UI自动化属性和控件模式,以及Microsoft Active Accessibility属性。 Inspect还使您能够测试UI自动化树中的自动化元素的导航结构以及Microsoft Active Accessibility层次结构中的可访问对象。
      Windows软件开发工具包(SDK)已安装Inspect。 (在Windows SDK的早期版本中也可用。)它位于SDK安装路径(Inspect.exe)的\ bin \ \ 文件夹中。
    • Accessibility Insights for Windows 推荐安装这个工具,下面就是使用这个工具的一个截图(反正可以查看控件的属性 )
      在这里插入图片描述
    • 从另一个微软人员对提问的回答-MSFlexgrid 可知: .MSFlexGrid 是VB5/6时代遗留的Active X控件, I suspect the DataGridView replaced the MSFlexGrid sometime ago with regard to VB.Net. It is not wise to mix old tech with new tech or the end result is what is occuring now.
    • 提出的解决方案就是hook dll注入,虽然我的c++忘得差不多了,但是还是要用起来啊,啊!
      在这里插入图片描述
    • 关键词: WH_GETMESSAGE hook(DLL) GetWindowLong GWL_USERDATA IMSFlexGrid
    • 测试环境 Windows 7 SP1 32-bit(和我唯一的区别就是我是64-bit)

3.代码

如果对这个hook很小白,可以去看我的另一个博客[Visual Studio关于hook项目的简单使用]: 先运行点简单的,大概心里有个底会好点,不至于那么生疏。

网上关于这方面的资料很少,能搜到稍微有用点的也都是2010年之前的,真的是个冷板凳啊。

[原创]萌新逆向学习笔记——消息钩子键盘记录
Github上一个中文注释的示例demo:也很友好
VC Windows API应用之GetDesktopWindow ——获得桌面所有窗口句柄的方法:方法很好,但是感觉比较耗时(从桌面窗口开始搜索的 不划算)

3.1 代码demo

DWORD* p= (DWORD*) ::GetWindowLong(flexGridHWND, GWL_USERDATA);
p++; //add 8;
p++; //add 8;
LPDISPATCH pDisp= (LPDISPATCH)*p;
long row,col;
COleDispatchDriver iDriver;
iDriver.AttachDispatch(pDisp);
// getrows()
iDriver.InvokeHelper(0x4, DISPATCH_PROPERTYGET, VT_I4, (void*)&row, NULL);
// getcols()
iDriver.InvokeHelper(0x5, DISPATCH_PROPERTYGET, VT_I4, (void*)&col, NULL);
ofs.open("c:\\textout.txt",ios::ate|ios::out);
ofs<<"rows:"<<row<<",cols:"<<col<<endl;
CString result;
static BYTE parms[] = VTS_I4;
for (int i=0;i<row;i++)
     for (int j=0;j<col;j++)
     {
          iDriver.InvokeHelper(0x37, DISPATCH_PROPERTYGET, VT_BSTR, (void*)&result, parms, col*i+j);
          ofs<<i<<","<<j<<"--"<<result<<endl;
     }
iDriver.DetachDispatch();
ofs.close();
//一开始报奇怪的错误就是因为忘了写最后这两行

3.1.1 DetachDispatch函数

根据:coledispatchdriver
在这里插入图片描述

3.1.1 ofs.close()函数

在这里插入图片描述

3.2 相关数据类型


3.2.1 LPDISPATCH (l p dispatch)

LPDISPATCH is a pointer to IDispatch interface and is defined inAfxWin.h

typedef IDispatch* LPDISPATCH;

MSDN Forum——what’s means LPDISPATCH

IDispatch is the COM interface for automation. It is a very general interface. You need to read the help document of the program which provides the automation for more information about specific methods and properties you can access from this interface.Some IDEs/compilers support generating wrapper classes for automation code.
For example, Visual C++'s class wizard supports generating COleDispatchDriver-based wrappers from type libraries. The Visual C++ compiler supports generating template-based COM wrapper from type libraries via the #import directive.

上面提到的COleDispatchDriver 刚好在那个实现了对MSFlexGrid访问的博客Get data from flexgrid in another process中也有


3.2.2 其他相关内容

Get data from flexgrid in another process中 DanRollins的回答:有一定的探索和启发意义
在这里插入图片描述
大意就是:
控件设计者将有用的信息存储在Window结构是很常见的。使用Spy++检查了MsFlexGrid控件,发现 用户数据部分的 DWORD双字节数据(DWORD是双字节数据类型。 DWORD全称Double Word,是指注册表的键值,每个word为2个字节的长度,DWORD 双字即为4个字节,每个字节是8位,共32位) 看起来很像是一个地址。 (用spy++可以看到其他控件的 用户数据部分的内容都是 全0 就只有这个是有不一样值的 所以这大概率就是地址)

检查那个地址及其附近的8个字节的内容(可以用cheat engine看一下),刚好发现与MFC测试程序使用的 IDispatch*的内容匹配,
在这里插入图片描述
打开 cheat engine ->先打开要查看的进程/程序->Add Adress manually->把上面那个疑似地址的 02A866A8填进去,然后右击选择 Browser this memory region 就能看到下面的内容了

在这里插入图片描述


3.3 错误问题解决


3.3.1 无法打开"AfxWin.h"

感谢MFC 之 afxwin.h无法打开,确实是在安装vs2019时候没有勾选

还是找到当时安装vs_2019时候的exe,然后等待 visual studio installer下载完,在弹出的界面中选择 ->修改 然后选中 使用C++的桌面开发 右侧勾选 Visual C++ MFC(不同vs版本字不一样,重点是vc++ MFC就OK了)
在这里插入图片描述


3.3.2 windows.h相关的error

其实是由3.3.1引入AfxWin.h之后引发的一个问题,AfxWin.h是MFC开发默认会引入的一个包,

错误1
#error : WINDOWS.H already included. MFC apps must not #include <windows.h>
错误2
大意就是 MFC要求dll必须是 shared dll(默认创建dll的时候,其实是使用标准的MFC)

错误解决参考MFC apps must not include windows.h:VS2019设置MFC的位置:
项目->右击 属性->配置属性 高级->高级属性 MFC的使用->选择 在共享DLL中使用MFC 然后就不会报错了(代码中删除 #include “windows.h” 这个库MFC默认包含)

删除之后依然报这个错误,可以去相关的文件,比如,新建dl项目后默认会在头文件里新建一个framework.h 里面默认也#include了"windows.h" 也要删除。。。


3.3.3 DllMain@12 已经在 dllmain.obj 中定义

和上面的错误一样,是连续引发的,因为

  1. VS2008在生成动态连接库时会默认生成一个DLLMain函数,
    这个函数没什么用。使用的MFC又会包含一个DLLMain函数,这样就造成了DLLMain的重定义。
  2. 在建立dll项目时,在“附加选项”里勾选了“预编译头”!也就是说,在这些预编译头里可能有dllmain这个函数的定义,导致出错,于是,我重新建立项目,这次勾选“空项目”

解决该链接错误方法:

项目属性->C/C+±>预处理器->预处理器定义 去掉_USRDLL

3.3.4 无法启动程序dll不是有效的win32应用程序

在这里插入图片描述
这不是因为代码错误,是你的运行方式不对,这是个dll项目,是不能运行的。。。dll肯定不是exe啊,它是给其他exe程序调用的,调用的时候才能执行。
谢谢兄弟VS2013 动态库编译无法启动***.dll文件:只顾着查错误,都不认真看错误,dll项目只能生成,不能运行,运行就报错。

此外,如果dll项目不在解决方案中,可以从菜单栏->生成->选择生成当前文件(这里面也有 重新生成 /清理 ) 或者自己可以直接删除 Debug文件夹,它就自动重新生成了


3.3.5 不认识标识符COleDispatchDriver

MSDN官方文档——COleDispatchDriver Class:当有些说明文档太长,看花眼的时候,直接搜索Header 头文件
在这里插入图片描述
可以看到,这个类需要的头文件是 afxdisp.h (也是MFC C++那个部分的,需要在安装时 安装这部分插件 不然也会提示没有)


3.3.6 不认识标识符 ofs及ios

ofs.open("c:\\textout.txt", ios::ate | ios::out);

直接在MSDN里没有搜到,这也是一个很老的库了。
根据 std::ofstream::open给出的一段示例程序:

// ofstream::open / ofstream::close
#include <fstream>      // std::ofstream
int main () {
  std::ofstream ofs;
  ofs.open ("test.txt", std::ofstream::out | std::ofstream::app);
  ofs << " more lorem ipsum";
  ofs.close();
  return 0;
}

另外,由于时间久远,即使include了,也报错,参考:vs++2010 编译说找不到 fstream.h 解决方法:使用如下方式

#include   <fstream> 
using namespace std;

3.3.7 此声明没有存储类或类型说明符

C++错误:此声明没有存储类或类型说明符
在这里插入图片描述
太久没有用过C++了 基本语法忘得一干二净。。。


3.3.8 UNICODE环境下输出TCHAR中文

参考
在UNICODE环境下TCHAR字符串如果是中文, 可以用_tprintf输出,需要加 setlocale(LC_ALL, “CHS”);

例如:

TCHAR s[20]={0};
setlocale(LC_ALL,"CHS");//不用引入额外的头文件,CHS注意双引号,不是单引号括住
_tprintf("XXXX%s",s);

3.4 相关函数

与GetWindowLong 同名的还有GetWindowLongA 这些前缀相同,后面加A和加W的就是随着Windows.h版本的改变而改变的。不加A和W的基本都是2006年左右的,也就是VB时代,而加了A和W的基本都是2018年,VC时代

so以后有后缀A和W的,在确保功能实现的情况下,尽量用新的吧。


3.4.1 GetWindowLongA

函数功能:检索关于特定窗口的信息。该函数还可以根据指定的偏移量将32位(DWORD)值检索到额外的窗口内存中。

注意: 当检索一个指针或句柄时,这个函数会被GetWindowLongPtr函数取代。 (指针和句柄在32位系统上时,就是32位,在64位系统上时,就是64位 .) 如果想要写兼容32位和64位的代码,就使用GetWindowLongPtr函数

函数原型

LONG GetWindowLongA(
  HWND hWnd,
  int  nIndex
);

hWnd Type: HWND窗口的句柄( indirectly, the class to which the window belongs.?这句不懂)

nIndex Type: int要检索的值的基于0的偏移值。有效值范围是0到额外的窗口内存的字节数,比如:如果您指定12个或更多字节的额外内存,则值为8将是第三个32位整数的索引。如果要检索其他数值,有以下明确的选择
在这里插入图片描述
从上表可以看到,其实和Spy++中窗口的 某窗口的样式用户数据进程id实例句柄那些是相对应的。

GWL_USERDATA -21检索与这个窗口相关的数据,数据是供创建该窗口的应用程序使用的,初始值为0.

返回值 类型:LONG。如果函数执行成功,返回的就是所求的数据;失败,则返回值为0,具体信息可以调用GetLastError查看。如果以前未调用SetWindowLong,则GetWindowLong对于额外的窗口或类内存中的值返回零。


3.4.2 COleDispatchDriver类的InvokeHelper

COleDispatchDriver::InvokeHelper
鉴于某个参考程序中 这个函数的第一个参数值 填写的是 0x4/0x5/0x37 要搞清楚这三个数字的含义,搜索关于MSFlexGrid方法的文档,时间太久,2002年左右,MSDN上相关的文档浏览跳转非常不方便,故

根据这个问题下的回答在VC中使用MSFlexgrid控件,为什么在MSDN中查不到CMSFlexGrid? 如何查到该控件的使用说明??,搜索下载 MSHFLX98.CHM这个文件。
感谢CHM-Download Free这个网站,

下载后chm提示 已取消到该网页的导航的解决方法,根据关于chm提示 已取消到该网页的导航的解决方法
在这里插入图片描述
成功打开,虽然是英语。

搜索发现别的关于这个函数第一个参数的说明:

dwDispIDIdentifies the method or property to be invoked. This value is usually supplied by Component Gallery. 可以发现,几乎关于COleDispatchDriver这个类的所有方法都使用dwDispID作为第一个参数,OMG!


3.4.2.1 成功前的错误尝试

其他相关参考资料

  1. 2001年的帖子——那位高手能告诉我属性的DISPID值如何获得,在那里找到?

  2. 百度问答2017年——求教InvokeHelper的用法,该如何解决:ActiveX控件的方法和属性操作与生成的C++类成员函数相关联都是通过InvokeHelper函数的调用来完成的,InvokeHelper函数的第一个参数是由Component Gallery(控件提供者)提供的。因为经过这样的处理,所以我们如果要调用ActiveX控件的方法或对其属性进行取和设置操作,只需调用生成的C++类对应的成员函数便可。

  3. Stack-overflow——How to find the function name, which is going to be invoked?
    :根据0x4这样的值来找到其对应函数的名字。。。

  4. 2002年的帖子——how to get dispids of dll? 在这里插入图片描述
    搜了一下,确实有 OLE/COM Object Viewer这个东西

  5. Windows10安装OLE/COM Object Viewer对象查看器:感谢兄弟 右击管理员身份运行的方法

  6. MSDN官方文档-Using the OLE/COM Object Viewer

PS:好像win10安装了VS2019之后,会默认带有Win10的SDK(这里会包含olecomviewer.exe),就是目录层次比较深
C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64
在这里插入图片描述
双击运行后,报错:
在这里插入图片描述
但是我在OLEViewer.exe这个软件的同级目录下看到了这个 iviewers.dll文件
在这里插入图片描述
右击 管理员身份运行,就不会再报错了。

运行后查看,显示的都不是我想要的内容。。。。。。。此路不通,继续搜索关于 dwDispID的相关内容


3.4.2.2 正确方向

我找到咧!!!

感谢VC++调用MSFlexGrid的SetRow方法,出现异常“Invalid Row Value”,让我改变搜索的关键词,应该是 MSFlexGrid InvokeHelper(0xa

不难发现很多网页其实给出了#include "msflexgrid.h"这样的关键词

  1. Caetest/msflexgrid.cpp__.htm
  2. myatltest/msflexgrid.cpp

怎么去找某个组件的方法/属性的 dwDispID呢,去这个组件的头文件 header .h对应的头文件里找哇,啊啊啊啊啊啊啊啊啊啊。只要愿意找,一定就可以找到!

在这里插入图片描述
在这里插入图片描述

关于MSFlexGrid的使用(自己使用这个控件方法的例子):
+ Using the Microsoft Flexgrid in Visual C++
+ MSflexgrid vc++2010

3.4.3 LoadLibray和FreeLibrary

主要参考这个:动态链接库–载入、卸载

LoadLibray

HMODULE LoadLibraryA(
  LPCSTR lpLibFileName
);

FreeLibrary:释放已加载的动态链接库(DLL)模块,并在必要时减少其引用计数。 当引用计数达到零时,将从调用进程的地址空间中卸载该模块,并且该句柄不再有效。

BOOL FreeLibrary(
  HMODULE hLibModule
);

说明: 通过 LoadLibrary 和 LoadLibraryEx 两个函数会在用户的系统中对DLL文件进行定位,并试图将该文件映射到调用进程的地址空间中。
pszDLLPathName
参数非全路径或网络路径,那么将以标准路径进行搜索;
参数传入全路径或网络路径,那么不在搜索其他地方而直接返回;

通过 FreeLibrary 和 FreeLibraryAndExitThread 来显示地将DLL从进程地址空间中卸载。FreeLibrary会立刻将DLL从进程地址空间中卸载,如果先调FreeLibrary再调ExitThread,则导致调用ExitThread的代码不复存在。


3.4.4 关于SetWindowsHookEx()中的消息类型

一开始使用的是
SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hInstance,threadID)

但是使用这个 WH_GETMESSAGE制作钩子通过dll来访问另一个不是自己写的进程的内存空间的时,触发条件是:
在这里插入图片描述
是可以使用这个类型的钩子去检测 由 GetMessagePeekMessage函数返回的消息。以及鼠标/键盘输入或者是其他加入到消息队列的消息,

我发送给目标线程一个WM_NULL类型的消息


3.4. 调试

单独把调试拿出来是因为,由于涉及到 访问另一个进程的内存,所以有些东西(比如调用dll后获取的钩子的hook数据,就无法读取器内容)即便使用变量进行了保存,在自己的程序中也无法读出示数。。。除非在dll里进行打印或者保存到某个文件中。

3.4.1 vs2019 添加断点和打开监视窗口

关于监视窗口

  • 你所不知道的Visual Studio监视窗口的使用方法
    :总之是一个很强大的工具,可以查看当前程序中已执行部分所有变量的内容
  • vs2019怎么打开监视窗口:必须在设置断点,调试过程中,才可以打开 监视窗口
    在这里插入图片描述
    PS:vs2019打断点的地方非常窄(用惯了python的Pycharm,一下载还有点不适应),是在行号左边这个蓝条/灰蓝条里面点击,打断点(之前在行号左侧的白色部分点半天没有反应,我以为自己都不会打断点了。。。)

打完断点后,点击 本地调试器 就是这个绿色按钮,只是 我这截取的是下一个状态(会执行到断点处,然后停下,要继续运行的话 就点 继续运行)
在这里插入图片描述
此时,正常显示程序输出信息的地方会有:
在这里插入图片描述
随便右击 自动窗口中的任意一个变量,在弹出的窗口中选择添加监视 ,就可以看到监视窗口了,同时可以手动输入某个变量来查看
在这里插入图片描述
此外,确实可以看到,这种dll返回的别人进程内存里的东西,我的程序是没有权限去读的

在这里插入图片描述

3.4.2 将dll中的printf信息输出到控制台中

参考

  1. 如何把DLL里的输出文本显示在控制台中
  2. Stack overflow——Printing messages to console from C++ DLL

上面两个网页都提到了:调用OutputDebugString,即可把信息输入到DebugView软件

3.4.2.1 DebugView软件

DebugView是一个软件。。。
MSDN官方——Docs ->Sysinternals ->Downloads->DebugView

参考DebugView 调试入门

反正最重要的一点就是 运行时记得右击 管理员身份运行 然后菜单栏 Capture里记得勾选择 Capture Global Win32 (如果不使用管理员身份运行,勾这个就会报错),要是不确定自己程序的信息属于哪类,全部勾选就行了。

3.4.2.2 OutputDebugString函数

这个东西默认只能输出字符串,不能直接输出字符串变量,根据

Simplest way to write output message to ‘output window’ in Visual Studio 2010?

可以考虑使用如下方式,将需要格式化的内容通过

char buffer[100];
sprintf_s(buffer, "check it out: %s\n", "I can inject things");
OutputDebugStringA(buffer);


int sprintf_s(
   char *buffer,
   size_t sizeOfBuffer,
   const char *format,
   ...
);

sprintf_s, _sprintf_s_l, swprintf_s, _swprintf_s_l:这个函数的作用是把数据写入格式化的字符串中去

注意:如果使用sprintf,则运行过程中报错:sprintf: This function or variable may be unsafe.Consider using sprintf_s instead.To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.。所以最好直接使用sprintf_s

4. CString的问题

获取到了数据,但是每个单元格里的数据更像是一种编号/序号,初步考虑可能是与数据库里的值相关联了。

花费了许多时间研究数据库(软件配套的),但是实际上是因为CString这种类型的字符串编码导致的问题,
感谢 CSDN博客——CString类型的字符串写入文件,所遇到的问题

我的CString字符串直接写入文件,得到的是一系列数字,根据上面的博客

FILE *pt = NULL;

CString str;
char buffer[30] = {'\0'};

str.Format(_T("http://www.baidu.com"));
WideCharToMultiByte(CP_ACP, 0, str.GetBuffer(0), str.GetLength(), buffer, 30, 0, 0);
// 关键是这个函数 对CString类型的字符串做了转换 使其可以正确输出
pt = fopen("D:\\pagedownload", "a");
fprintf(pt, "%s", buffer);
fprintf(pt, "\n---------------\n");
str.ReleaseBuffer();
fclose(pt);

4.1 WideCharToMultiByte function

WideCharToMultiByte function

函数功能:将一个UTF-16(宽字符)字符串映射成一个新的字符串,新的字符串也不一定必须来自多字节字符集。
注意:不正确地使用WideCharToMultiByte函数可能会损害应用程序的安全性。调用此函数很容易导致缓冲区溢出,因为lpWideCharStr表示的输入缓冲区的大小等于Unicode字符串中的字符数,而lpMultiByteStr表示的输出缓冲区的大小等于字节数。为避免缓冲区溢出,您的应用程序必须指定适合于缓冲区接收的数据类型的缓冲区大小。
从UTF-16转换为非Unicode编码的数据可能会导致数据丢失,因为代码页可能无法表示特定Unicode数据中使用的每个字符。

函数原型

int WideCharToMultiByte(
  UINT                               CodePage,
  DWORD                              dwFlags,
  _In_NLS_string_(cchWideChar)LPCWCH lpWideCharStr,
  int                                cchWideChar,
  LPSTR                              lpMultiByteStr,
  int                                cbMultiByte,
  LPCCH                              lpDefaultChar,
  LPBOOL                             lpUsedDefaultChar
);

函数参数

CodePage:用于执行转换的代码页。 可以将此参数设置为操作系统中已安装或可用的任何代码页的值。 有关代码页的列表,请参见代码页标识符。 您的应用程序还可以指定下表中显示的值之一。
还有 CP_MACCP,CP_OEMCP,CP_SYMBOL等等
在这里插入图片描述
dwFlags 指示转换类型的标志。 应用程序可以指定以下值的组合。 当这些标志均未设置时,该函数执行速度更快。 应用程序应使用特定值WC_DEFAULTCHAR指定WC_NO_BEST_FIT_CHARSWC_COMPOSITECHECK,以检索所有可能的转换结果。 如果未提供所有三个值,则将丢失某些结果。
在这里插入图片描述
源字符串的指针和长度

lpWideCharStr:指向要转换的Unicode字符串的指针
cchWideCharlpWideCharStr指示的字符串的大小(以字符为单位)。
如果字符串以空值结尾,则可以将此参数设置为-1。
如果将cchWideChar设置为0,该函数将失败。
如果此参数为-1,则函数将处理整个输入字符串,包括终止null字符。 因此,结果字符串具有终止的空字符,并且长度函数返回的值包含此字符。
如果此参数设置为正整数,则该函数将精确处理指定数量的字符。 如果提供的大小不包含终止的空字符,则结果字符字符串不是以空值结尾的,并且返回的长度不包含此字符。

目标字符串存储的位置指针和存储区域的长度

lpMultiByteStr:指向 接收转换后的字符串的缓冲区 的指针。
cbMultiByte:lpMultiByteStr指示的缓冲区大小(以字节为单位)。 如果此参数设置为0,则该函数返回lpMultiByteStr所需的缓冲区大小,并且不使用输出参数本身。

lpDefaultChar:如果无法在指定的代码页中表示字符,则指向要使用的字符的指针。 如果该功能使用系统默认值,则应用程序将此参数设置为NULL。 要获取系统默认字符,应用程序可以调用GetCPInfo或GetCPInfoEx函数。对于CodePage的CP_UTF7和CP_UTF8设置,此参数必须设置为NULL。 否则,该函数将失败,并显示ERROR_INVALID_PARAMETER。

lpUsedDefaultChar:指向标志的指针,该标志指示函数是否在转换中使用了默认字符。 如果源字符串中的一个或多个字符不能在指定的代码页中表示,则将标志设置为TRUE。 否则,该标志设置为FALSE。 此参数可以设置为NULL。对于CodePage的CP_UTF7和CP_UTF8设置,此参数必须设置为NULL。 否则,该函数将失败,并显示ERROR_INVALID_PARAMETER。

5.后续问题

5.1 判断是否存在某个文件 不存在则创建

参考:
C++判断文件或文件夹是否在,不存在则创建

5.2 Visual Studio项目导出为exe

参考:Visual Studio 将代码程序导出为exe

选择 生成解决方案 就好咧(主要是我的 生产dll的项目 和调用dll的项目 在同一个解决方案下 论项目管理的重要性,还是要多少懂点 Visual Studio的解决方案的)

5.3 dll获取程序的数据写到dll文件同级目录

5.3.1 GetModuleFileName函数

检索包含特定模块的文件的路径。要求模块必须被当前进程所加载。(注意,当 hModule参数为NULL时,GetModuleFileName函数检索当前进程的可执行文件地址)

GetModuleFileNameA和GetModuleFileNameW以及GetModuleFileName:这个文章说明了一下,MSDN中那么多win32函数 同名 但是跟着不同后缀的 A W都是什么意思,大意:

GetModuleFileName的定义是一个宏,在UNICODE版本下,GetModuleFileName等同于GetModuleFileNameW,在ANSI版本下等同于GetModuleFileNameA。GetModuleFileNameA和GetModuleFileNameW的区别在于它们的字符串参数的“字符宽度”

两个函数的函数原型和功能在MSDN网站里都是一模一样的,关键说明点在于
在这里插入图片描述
意思就是上面那段话了

DWORD GetModuleFileNameA(
  HMODULE hModule,
  LPSTR   lpFilename,
  DWORD   nSize   
);
// hModule 传入dll入口函数的 hModule 则就可以在dll的cpp文件中获取生成的dll文件位置 并据此进行一些相关操作
DWORD GetModuleFileNameW(
  HMODULE hModule,
  LPWSTR  lpFilename,
  DWORD   nSize
);

5.3.2 tchar字符类型拼接

MSDN官方论坛-How concatenate a TCHAR array with a string?
在这里插入图片描述

lstrcat(path, L"\\result.txt");

这个回答也可以看看:StackOverflow-C++ Combine 2 Tchar

5.3.3 ofstream的open

根据这个博客:(转载)C++ ofstream和ifstream详细用法,得到以下我会用到的信息:

5.3.3.1 ofstream类

ofstream是从内存到硬盘ifstream是从硬盘到内存,其实所谓的流缓冲就是内存空间

在C++中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包括我们要认识的文件I/O

stream这个类有两个重要的运算符:

1、插入器(<<):向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout<<“Write Stdout”<<’\n’;就表示把字符串"Write Stdout"和换行字符(’\n’)输出到标准输出流。

最常见的使用例子:

#include <fstream>
using namespace std;
int main(int argc, char **argv)
{
    ofstream ofs("ofstream.txt");
    if (!ofs.bad())
    {
        ofs << "Writing to a basic_ofstream object..." << endl;
        ofs.close();
    }
}

5.3.3.2 basic_ofstream::open

open函数原型

void open(
    const char* _Filename,
    ios_base::openmode _Mode = ios_base::out,
    int _Prot = (int)ios_base::_Openprot);

void open(
    const char* _Filename,
    ios_base::openmode _Mode);

void open(
    const wchar_t* _Filename,
    ios_base::openmode _Mode = ios_base::out,
    int _Prot = (int)ios_base::_Openprot);

void open(
    const wchar_t* _Filename,
    ios_base::openmode _Mode);

参数说明

第一个参数 也就是 要打开的文件名(文件路径)可以是 char*或者wchar_t*类型,但是一定要是const类型。

ios_base::openmode _Mode 参数 打开文件模式,有以下可选值:

  • ios::app:   以追加的方式打开文件
  • ios::ate:   文件打开后定位到文件尾,ios:app就包含有此属性
  • ios::binary: 以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
  • ios::in:    文件以输入方式打开(文件数据输入到内存)
  • ios::out:   文件以输出方式打开(内存数据输出到文件)
  • ios::nocreate: 不建立文件,所以文件不存在时打开失败
  • ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败
  • ios::trunc:  如果文件存在,把文件长度设为0

可以用“或”把以上属性连接起来,如ios::out|ios::binary

相关内容可以参考:

5.3.3.2.1 覆盖模式

使用

std::ofstream ofs("test.txt", std::ofstream::trunc|std::ofstream::out)

5.3.3.3 相关使用

How to get Current Directory?

//获取当前文件不带文件名的目录
#include <windows.h>
#include <string>
#include <iostream>

wstring ExePath() {
    TCHAR buffer[MAX_PATH] = { 0 };
    GetModuleFileName( NULL, buffer, MAX_PATH );
    std::wstring::size_type pos = std::wstring(buffer).find_last_of(L"\\/");
    return std::wstring(buffer).substr(0, pos);
}

int main() {
    std::cout << "my directory is " << ExePath() << "\n";
}

C/C++文件输入输出操作——FILE*、fstream、windowsAPI:这个博客转自于新浪博客,说明早年间的程序员写技术博客大部分都在新浪,可能因为当时技术博客网站有限吧。


5.4 我需要的正确内容

我使用的代码 直接使用一个 写死的路径作为打开的文件对象

const char* fname="d:\\result.txt";
std::ofstream ofs;
ofs.open(fname.ios::ate|ios::out); //打开一个文件对象   std::ios::ate
ofs<<"123,"<<endl; //写入数据
ofs.close; //关闭流对象

我的需求 需要使用GetModuleFileName获取当前dll所在路径(给出了dll的句柄 在cpp文件中使用dll入口函数的 HMODULE参数传进去就可以),然后在dll路径下新建一个文件,使用这个文件作为ofstream要写入内容的文件

主要参考:C++ 读取中文文本 ifstream
关键代码:

#include <Windows.h>  
#include <iostream>  
#include <fstream>  
#include <stdio.h>  
#include <string>  
using namespace std;    
int main()  
{  
	string str = "";  
	wchar_t wch = '中';  
	char ch = 'b';  
	cout<<"char : "<<ch<<wch<<endl;  
	 
	 // 主要是从这里开始的内容 字符串格式的转换 
	 //****************
	char filename[MAX_PATH];  
	GetModuleFileName(NULL,filename, MAX_PATH);  
	str = (string)filename;  
	int pos = str.find_last_of('\\',str.length());  
	str = str.substr(0,pos);  
	char * ch2 = const_cast<char *>(str.c_str());  //输出string类型查看路径 需要进行这个转换才可以使用sprintf_s来输出到OutputDebugString中,进一步通过DebugViewer查看
	cout<<"file path: "<< ch2<<endl;  	 // 这两步只是为了输出看看效果 其实没什么用 
	str += "\\read.txt";  	
	//*********************  
	ifstream ifs;  
	ifs.open(str);  
	if (!ifs.is_open()){  
	cout<<"open file "<<str<<"    failed"<<endl;  
	}   
	system("pause");  
	return 0;  
}  

修改后的代码(运行平台 vs2019)

char dllPath[MAX_PATH]={0};
GetModuleFileNameA(hInstance,dllPath,MAX_PATH);
// 主要纠结的地方就是这里
一开始使用的是
TCHAR dllPath[MAX_PATH]={0};
GetModuleFileName(hInstance,dllPath,MAX_PATH);

但是后续关于string的操作,要求dllPath最好是char,但是改为char就会报错:
char*类型的实参与类型的形参不兼容。

一开始在dllPath前加入强制类型转换前缀

GetModuleFileName(hInstance,(LPCWSTR)dllPath,MAX_PATH);
但是这样其实在后面的内容中,直接使用这个dllPath其实仍然还是TCHAR类型,治标不治本

后来想起来:
GetModuleFileName的定义是一个宏,
在UNICODE版本下,GetModuleFileName等同于GetModuleFileNameW,
在ANSI版本下等同于GetModuleFileNameA。
GetModuleFileNameA和GetModuleFileNameW的区别在于它们的字符串参数的“字符宽度”

由于vs2019默认字符集是 UNICODE ,所以使用GetModuleFileName时其实直接调用了GetModuleFileNameW。
但是由于我输入字符更希望是char类型,所以显示的调用GetModuleFileNameA 就可以解决问题了。

其余代码直接复用上面参考的代码即可


5.5 报错 char*类型的实参与LPCWSTR类型的形参不兼容

之前也见过很多次了,但是之前确实都是 字面常量,可以直接 加一个前缀L解决,也可以直接修改字符集为 多字节字符而不是默认的UNICODE字符集,但是不推荐这种方式,不好。

搜索后,根据
MSDN官方社区问题-incompatible types - from ‘char [4]’ to 'LPCWSTR’可知:
在这里插入图片描述
总结
上面这些方式总是治标不治本的,如果是函数参数类型导致报错,那就直接换函数是最好的。

Win32com的函数基本都提供了 xxAxxW两种类型的声明,只要根据需求去选择 ANSI/char体系下的xxA或者是UNICODE/tcha/wchar_t体系下的xxW就好了

这应该才算是从根本上解决问题,追根溯源。

5.6 String类型输出

使用DebugViewer软件可以看到使用 OutputDebugString。但是如果想要输出变量,而不是字面字符串的话,就需要使用sprintf_s。使用该函数输出string需要稍微做一些处理:

MSDN官网-sprintf_s, _sprintf_s_l, swprintf_s, _swprintf_s_l→→Format Specifications. 格式化输出字符串:MSDN官网-Format specification syntax: printf and wprintf functions
在这里插入图片描述
所以关键就是使用printf如何输出string类型,根据关于c++中printf语句输出string类型乱码
在这里插入图片描述
string类型可以直接使用cout输出,但是如果想使用printf就要做一个转换。

C++中关于string类型究竟能不能用cout输出的问题:其实还是有一些C→C++类型重载的遗留问题。

参考

  1. Forums—微软人员对这个问题MSFlexGrid UIAutomation C++ API的回答
  2. 成功访问到数据的例子-Get data from flexgrid in another process
  3. 失败的尝试者——Thread: Idispatch From HWND
  4. 如何把DLL里的输出文本显示在控制台中
  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页

打赏作者

吨吨不打野

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值