首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
93 阅读
2
nvim番外之将配置的插件管理器更新为lazy
73 阅读
3
2018总结与2019规划
57 阅读
4
PDF标准详解(五)——图形状态
39 阅读
5
为 MariaDB 配置远程访问权限
33 阅读
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE 运动
菜谱
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
emacs
linux
文本编辑器
Java
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
313
篇文章
累计收到
31
条评论
首页
栏目
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE 运动
菜谱
页面
归档
友情链接
关于
搜索到
313
篇与
的结果
2018-01-06
hexo next主题为博客添加分享功能
今天心血来潮,决定给博客添加分享功能,百度上首先是找到了使用shareSDK的分享功能,最后在实践的过程中发现它添加时步骤比较多,添加完成后效果比较丑(就是一个长条的浅蓝色按钮),而且点击后想要退出分享比较麻烦(它的取消按钮实在太难找了,它在页面最下方的位置,呈现浅灰色,这个设计太反人类了,决定放弃它了)。在next主题的官方的文档中发现它自身集成了百度分享的功能,所以决定采用百度了。
2018年01月06日
5 阅读
0 评论
0 点赞
2018-01-01
2017总结与2018规划
时间如白驹过隙,回想起过去的2017年仿佛就在昨天,元旦做了几天咸鱼,没有能够及时更新自己的年终总结,在此补上回望2017回望2017,在4月份我换了一份工作,之前的工作实在太闲了,我感觉这样不利于自己的进步,于是在3月份的时候正式提出离职,在4月份进入新公司。工作感悟新公司主要是做Web安全的,而我主要负责一款扫描器插件的开发于扫描器本身的维护工作。这份工作经常给我带来惊喜,就比如说sql注入,正常人的思维是输入一个值,然后在数据库中查询,但是sql注入就不是,它会在输入的查询条件中带上sql语句,这与传统的方式不同,它给我一种耳目一新的感觉。我第一次学习到sql注入的原理时是那样的兴奋,感慨于它的不同寻常。同时也对安全从业者产生了一定的崇拜,他们一定是一些思维活跃的人,是一群打破常规的人,这些都是我决定从事安全行业,努力融入安全圈内。这一年收获很多,在这一年中学习了一些常见漏洞的原理,攻击与防御的相关知识,通过阅读公司扫描器的源代码,学习了一些阅读源代码的方法,虽说还没有总结出一整套的方法论,但是也有一些思路。并根据这些方法我找到对应的代码并进行了一些修改。另外,在这一年中学会了一些脚本语言,比如Python、JavaScript。我自己感觉虽然扫描器是使用VC++语言编写的,但是我在这一年中本身使用最多的反而是Python。在刚入职时使用Python开发了扫描器对于ajax部分的支持,使其能够正常爬取到使用JavaScript动态生成的脚本。后来又使用Python开发了大量的漏洞扫描插件。现在又在使用Python开发一款网站监控软件。可以说这一年如果不是自己一直在自学VC的内容,可能现在C的东西都忘干净了^_^由于需要与Web程序打交道,所以一些理论方面的内容必定会涉及到,这一年主要详细了解了http协议,但是主要是在数据包层面上的,比如http的协议头、协议体等等,对于网络底层的东西仍然不是太明白,这也是目前的一个短板。后来为了方便,使用Python发送网络包的时候主要使用的requests库。知道了使用requests库定制协议头、判断响应包等等操作。但是反而对原生的urllib库不太了解。总结这一年我感觉最有用处的项目就是当时我使用Python + webkit写的一个web2.0的爬虫以及现在的一个web监控系统。web监控目前没有完成,所以暂时不提它,这里主要说说web2.0爬虫项目web2.0爬虫参考书籍《白帽子将web扫描》一书,基本代码都是按照书中的思路来的。这个项目使我详细了解了http协议,正则表达式、xpath表达式等等。为了使用webkit,项目主体是一个qt的任务调度部分负责调度由扫描器传过来的需要解析的url,每当有一个url过来的时候,都会触发一个自定义的事件,然后由qt调用对应的槽函数。其实我原本打算使用Python的线程池,来进行调度,但是由于使用的是webkit的Python封装——ghost库,这个库有一个好处就是能够捕捉到所有的网络请求,这样我可以很方便的得到ajax异步加载时请求的URL,从而得到一些额外的url,当时ghost库使用的是全局的app类对象,qt又不允许跨线程访问对象,所以没办法取消了多线程。后来经过我的测试在性能上虽然有一些损失但是损失在能接受的范围内。在解析url时候主要解析下面几种:在一些常规标签中的url,对于这种采用的是常规的xpath表达式出现在文本内容中的链接,或者JavaScript脚本中的链接,这种采用的是正则表达式需要进行异步请求的链接,这种链接主要出现在一些JavaScript注册的事件中。这种链接主要使用ghost库,获取所有网络请求,然后解析其中的url还有就是一些需要进行渲染之后才能出现的链接,对于这种链接,采用的是ghost库中的webkit进行渲染然后解析生成的HTML其余事项我在国庆期间去了一趟深圳,见了一下之前的大学同学,虽然很遗憾,不少人都回去了,但是我见到了当年的室友,以及领我上安全这条路的大牛同学。在于大牛同学的一些交谈中,我发现自己进步的没有想象中那么大,这些都激励着我继续努力不足2017虽然有进步,但是也有许多的不足:生活过于懒散:最大的毛病在于生活过于懒散,没有规律。经常周五熬夜做自己感兴趣的事,一旦这件事做完了或者碰到瓶颈没法解决的时候,后面几天就成了一无是处的咸鱼。手机占据了大量的业余时间,在这一年中,公司很多时候没有什么大事,加班比较少,我虽然回来的早,但是大量的时间用在玩手机上,根据我自己的观察,差不多从7点到8点半的时间很多时候都是在玩手机。而且有时候在学习的时候集中不了精力,时不时会看看手机,这样极大的影响了注意力。缺少锻炼:要说这一年什么收获最大,那应该是我身上的30斤肥肉T_T,从年初的100多斤涨到现在快130斤,在加上自己经常久坐,导致现在有时候稍微活动一下就浑身不舒服,浑身酸痛。18年需要改变这一现状读书太少:这一条与玩手机太多有很大的关系,长时间玩手机,使读书的时间压缩了,有时候200多页的书需要看个把月,总体算下来,今年一年加上在kindle上的书共有6本,明年可能要花更多时间在读书上展望2018生活习惯针对上述出现的问题2018年决定做如下改变:多锻炼:目前计划在3月份房子到期找到新地方后就近找一个健身房办张卡,经常去锻炼身体,并且强迫自己每个星期必须出去走走按时休息:每天按时休息不再分周末和平时,到点了放下手机或者手中的事睡觉,周末白天尽量在8点前起来,保证每天早上出去吃个早餐尝试自己做饭,少点外卖学习计划详细学习网络原理系统学习渗透测试,一些常见漏洞的攻击与防御学习一些逆向的知识学习一些Windows驱动编程的东西看完之前的一套VC高级编程的视频,并针对每个知识点编写相应的代码多写博客,做到每周一篇技术博客,尝试写一些读书笔记、鸡汤、项目总结类的文章将CSDN博客中之前写的VC反汇编系列、VC高级编程系列的内容慢慢转到自己在GitHub的博客上我的博客合理利用周末,周末做到每天玩手机时间不超过2小时,平时不超过半小时2018书单2018年计划读如下书目:1.《白帽子讲Web安全》2.《TCP\IP协议》3.《数学之美》4.《人类简史》5.《未来简史》6.《腾讯传》7.《古龙全集》(利用坐地铁的时间)8.《球状闪电》 最后祝我与所有朋友2018越来越好
2018年01月01日
5 阅读
0 评论
0 点赞
2017-12-19
为 MariaDB 配置远程访问权限
最近在配置MySQL远程连接的时候发现我的MySQL数据库采用的是 MariaDB 引擎,与普通的数据库配置有点不同经过查找资料终于完成了,特此记录方便以后查询MariaDB 与普通的MySQL数据库的一个不同在于它的配置文件不止一个,它将不同的数据放入到不同的配置文件中,之前的/etc/mysql/my.cnf内容如下:从文件中的注释上来看,它主要有这么几个配置文件/etc/mysql/mariadb.cnf 默认配置文件,/etc/mysql/conf.d/*.cnf 设置全局项的文件"/etc/mysql/mariadb.conf.d/*.cnf" 设置与MariaDB相关的信息"~/.my.cnf" 设置该账户对应的信息这也就是为什么我们在my.cnf做相关设置有的时候不起作用(可能在其他配置文件中有相同的项,MySQL最终采用的是另外一个文件中的设置)。根据官方的说法, MariaDB为了提高安全性,默认只监听127.0.0.1中的3306端口并且禁止了远程的TCP链接,我们可以通过下面两步来开启MySQL的远程服务注释掉skip-networking选项来开启远程访问.注释bind-address项,该项表示运行哪些IP地址的机器连接,允许所有远程的机器连接但是配置文件这么多,这两选项究竟在哪呢?这个时候使用grep在/etc/mysql/目录中的所有文件中递归查找,看哪个文件中含有这个字符串我们输入:grep -rn "skip-networking" *,结果如下:十分幸运的是这两项都在同一文件中(我自己的是没有skip-networking项)我们打开文件/etc/mysql/mariadb.conf.d/50-server.cnf,注释掉bind-address项,如下:只有这些仍然不够,我们只是开启了MySQL监听远程连接的选项,接下来需要给对应的MySQL账户分配权限,允许使用该账户远程连接到MySQL输入select User, host from mysql.user;查看用户账号信息:root账户中的host项是localhost表示该账号只能进行本地登录,我们需要修改权限,输入命令:GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'password' WITH GRANT OPTION;修改权限。%表示针对所有IP,password表示将用这个密码登录root用户,如果想只让某个IP段的主机连接,可以修改为GRANT ALL PRIVILEGES ON *.* TO 'root'@'192.168.100.%' IDENTIFIED BY 'my-new-password' WITH GRANT OPTION;注意:此时远程连接的密码可能与你在本地登录时的密码不同了,主要看你在IDENTIFIED BY后面给了什么密码具体的请参考GRANT命令最后别忘了FLUSH PRIVILEGES;保存更改。再看看用户信息:这个时候发现相比之前多了一项,它的host项是%,这个时候说明配置成功了,我们可以用该账号进行远程访问了输入service mysql restart重启远程服务器,测试一下:如果这些都做完了,还是不能连接,可以看一下端口是不是被防火墙拦截了参考地址:官方文档
2017年12月19日
33 阅读
2 评论
0 点赞
2017-12-07
向上取整算法
在进行内存分配的时候一般都需要在实际使用内存大小的基础上进行内存对齐,比如一般32位平台进行4字节对齐,而64位平台使用8字节对齐等等。一般采用的算法是先利用公式$$int(\frac{a + b - 1} { b})$$(其中a是实际使用的内存, b是对齐值)然后根据这个值乘以b即可得到对应的对齐值公式推导$ 假设 A = NB +M (M \in \left[0,B-1\right])$$\because\frac{A}{B} = N + \frac{M}{B}$$\because M \in\left[0, B-1\right]$$\therefore \frac{A}{B} \leq \frac{NB +B -1}{B} \leq \frac{A + B -1}{B}$从上面可以得出$\frac{A}{B}$向上取整可能是int($\frac{A+B-1}{B}$)但是具体是否有比它小的整数,仍然不能确定.因此我们根据推导一下这个结果与$\frac{A}{B}$向上取整的结果是否相同$ 假设 A = NB +M (M \in \left[0,B-1\right])$$当M = 0时UP(\frac{A}{B}) = N$$当M \neq 0时,UP(\frac{A}{B}) = N$而针对int($\frac{A+B-1}{B}$)的结果$当M = 0时int(\frac{A+B-1}{B} )=int(\frac{NB+B -1}{B}) = int(N + 1 - \frac{1}{B}) $$\because \frac{1}{B} < 1$$\therefore int(\frac{A+B-1}{B} )$$当 M \neq 0 时int(\frac{A+B-1}{B} ) = int(\frac{NB +M + B -1}{B}) = int(N + 1 + \frac{M-1}{B})$$当M = 1时 int(\frac{A+B-1}{B} ) = int(N + 1 + \frac{M-1}{B}) = N + 1$$当1 < M \leq B-1时 \frac{M -1}{B} < 1$<br/>$\therefore int(\frac{A+B-1}{B} ) = int(N + 1 + \frac{M-1}{B}) = N$从上面的推导来看二者的值完全相同所以可以得出结论$UP(\frac{A}{B}) = int(\frac{A + B - 1}{B})$所以当我们对A字节的内存进行B字节的对齐时可以使用公式$int(\frac{A + B - 1}{B}) \times B $补充其实还有一个算法long(A + B - 1) &~ (B - 1)也可以计算,但是我没有弄清楚它的原理是什么,暂时不管先记住再说^_^
2017年12月07日
7 阅读
0 评论
0 点赞
2017-11-28
python检测404页面
某些网站为了实现友好的用户交互,提供了一种自定义的错误页面,而不是显示一个大大的404 ,比如CSDN上的404提示页面如下:这样虽然提高了用户体验,但是在编写对应POC进行检测的时候如果只根据返回的HTTP头部信息判断,则很可能造成误报,为了能准确检测到404页面,需要从状态码和页面内容两个方面来进行判断。从状态码来判断比较简单。可以直接使用requests库发送http请求,得到响应码即可。从页面内容上进行判断的话,采用的思路是访问web站点上明显不存在的页面,获取页面内容进行保存,然后访问目标页面,将二者进行比较,如果相似度达到某一阈值,则该页面为404页面,否则为正常页面。为了判断两个页面的相似度,采用Python的simhash库,这个库具体实现的算法我不太懂,但是Python的好处就是:不懂无所谓,直接拿来用就行。这里也只是简单的拿来用一下:#-*- encoding:utf-8 -*- # 404 页面识别 from hashes.simhash import simhash import requests class page_404: def __init__(self, domain): #检测站点 self._404_page = [] # 404页面 self._404_url = [] #404 url self._404_path = ["test_404.html", "404_test.html", "helloworld.html", "test.asp?action=modify&newsid=122%20and%201=2%20union%20select%201,2,admin%2bpassword,4,5,6,7%20from%20shopxp_admin"] #404页面路径,用于生成一部分404页面 self._404_code = [200, 301, 302] #当前可能是404页面的http请求的返回值 #自己构造404url,以便收集一些404页面的信息 for path in self._404_path: for path in self._404_path: if domain[-1] == "/": url = domain + path else: url = domain + "/" + path response = requests.get(url) if response.status_code in self._404_code: self.kb_appent(response.content, url) def kb_appent(self, _404_page, _404_url): if _404_page not in self._404_page: self._404_page.append(_404_page) if _404_url not in self._404_url: self._404_url.append(_404_url) def is_similar_page(self, page1, page2): hash1 = simhash(page1) hash2 = simhash(page2) similar = hash1.similarity(hash2) if similar > 0.85: #当前阈值定义为0.85 return True else: return False def is_404(self, url): if url in self._404_url: return True response = requests.get(url) if response.status_code == 404: return True if response.status_code in self._404_code: for page in self._404_page: if self.is_similar_page(response.content, page): self.kb_appent(url, response.content) #如果是404页面,则保存当前的url和页面信息 return True else: return False return False上面的代码中,检测类中主要保存了这样几个信息:_404_page:404页面,用于与其他请求的页面进行相似度判断,以便识别404页面,这里用列表主要为了防止一个站点有多种404页面,这段代码运行时间越长它的准确度越高_404_url:404 页面的url,保存之前判断出页面是404的url,已经判断出来的就不再判断,为了提升效率_404_path:构建不存在页面的url,最后一个是一个sql注入的代码,这里为了识别出那些被防火墙拦截而显示的错误页面_404_code:可能返回404页面的响应码,如果响应码为这些,则需要对页面进行判断类在初始化时需要传入一个域名,根据这个域名来拼接几个不存在的或者会被防火墙拦截的请求并提交这些请求,得到返回信息,将这些信息作为判断的信息进行保存。在判断时首先根据之前保存的404 url信息进行判断,如果当前url是404页面则直接返回,提高效率。然后提交正常的http请求并获取响应信息,如果响应码为404则返回True,否则再状态码是否在_404_code列表中,最后再与之前保存的404页面信息进行比较得到结果。这段代码的测试代码如下:from page_404 import page_404 if __name__ == '__main__': domain = "http://xzylrd.gov.cn" check_404 = page_404(domain) dest_url = "http://xzylrd.gov.cn/TEXTBOX2.ASP?action=modify&newsid=122%20and%201=2%20union%20select%201,2,admin%2bpassword,4,5,6,7%20from%20shopxp_admin" print (check_404.is_404(dest_url))
2017年11月28日
5 阅读
0 评论
0 点赞
2017-11-02
COM学习(四)——COM中的数据类型
上一次说到,COM为了跨语言,有一套完整的规则,只要COM组件按照规则编写,而不同的语言也按照对应的规则调用,那么就可以实现不同语言间相互调用。但是根据那套规则,只能识别接口,并调用没有参数和返回类型的接口,毕竟不同语言里面的基本数据类型不同,可能在VC++中char * 就表示字符串,而在Java或者c#中string是一个对象,二者的内存结构不同,不能简单的进行内存数据类型的强制转化。为了实现数据的正常交互,COM中又定义了一组公共的数据类型。HRESULT类型:在COM中接口的返回值强制定义为该类型,用于表示当前执行的状态是完成或者是出错,这个类型一般在VC中使用,别的语言在调用时根据接口的这个值来确定接下来该如何进行。HRESULT类型的定义如下:typedef _Return_type_success_(return >= 0) long HRESULT;其实它就是一个32位的整数,微软将这个整数分成几个部分,各个部分都有详细的含义,这个值的详细解释在对应的winerror.h中。// // Note: There is a slightly modified layout for HRESULT values below, // after the heading "COM Error Codes". // // Search for "**** Available SYSTEM error codes ****" to find where to // insert new error codes // // Values are 32 bit values laid out as follows: // // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 // +-+-+-+-+-+---------------------+-------------------------------+ // |S|R|C|N|r| Facility | Code | // +-+-+-+-+-+---------------------+-------------------------------+ // // where // // S - Severity - indicates success/fail // // 0 - Success // 1 - Fail (COERROR) // // R - reserved portion of the facility code, corresponds to NT's // second severity bit. // // C - reserved portion of the facility code, corresponds to NT's // C field. // // N - reserved portion of the facility code. Used to indicate a // mapped NT status value. // // r - reserved portion of the facility code. Reserved for internal // use. Used to indicate HRESULT values that are not status // values, but are instead message ids for display strings. // // Facility - is the facility code // // Code - is the facility's status code // // // Define the facility codes //根据上面的注释可以看到,以及我自己查阅相关资料,它里面总共有7个部分,各个部分代表的含义如下:S - 严重性 - 表示成功或失败0 - 成功,1 - 失败R - 设施代码的保留部分,对应于NT的第二严重性位。1 - 严重故障C - 第三方。 此位指定值是第三方定义还是Microsoft定义的。0 - Microsoft-定义,1 - 第三方定义N - 保留部分设施代码。 用于指示映射的NT状态值。X - 保留部分设施代码。 保留供内部使用。 用于指示不是状态值的HRESULT值,而是用于显示字符串的消息标识。Facility - 表示引发错误的系统服务. 示例Facility代码如下所示:2 - 调度(COM调度)3 - 存储 (OLE存储)4 - ITF (COM/OLE 接口管理)7 - (原始 Win32 错误代码)8 - Windows9 - SSPI10 - 控制11 - CERT (客户端或服务器认证)...Code - 设施的状态代码其实这些没有必要知道的很详细,只需要知道里面常用的几个即可:S_OK:成功S_FALSE:失败E_NOINTERFACE:没有接口,一般是由QueryInterface或者CoCreateInterface函数返回,当我们传入的ID不对它找不到对应的接口时返回该值E_OUTOFMEMORY:当内存不足时返回该值。一般在COM的调用者看来,有的时候只要最高位不为0就表示成功,这个时候可能会继续使用,所以在我们自己编写组件的时候要根据具体情况选择返回值,不要错误了就返回S_FALSE,其实我们看它的定义可以知道它是等于1的,最高位为0,仍然是成功的。如果返回S_FALSE可能会造成意想不到的错误,而且还难以调试。BSTRCOM中规定了一种通用的字符串类型BSTR,查看BSTR的定义如下:typedef /* [wire_marshal] */ OLECHAR *BSTR; typedef WCHAR OLECHAR;从上面的定义上不难看出BSTR其实就是一个WCHAR ,也就是一个指向宽字符的指针。COM中使用的是UNICODE字符串,在编写COM程序的时候经常涉及到CString、WCHAR、char等的相互转化,其实本质上就是多字节字符与宽字节字符之间的转化。我们平时在进行char 与WCHAR*之间转化的函数像WideCharToMultiByte和MultiByteToWideChar,以及W2A和A2W等。COM为了方便使用,另外也提供了一组转化函数_com_util::ConvertBSTRToString以及_com_util::ConvertStringToBSTR用在在char*与BSTR之间进行转化。需要注意的是,这组函数返回的字符串是在堆上分配出来的,使用完后需要自己释放。在BSTR类型中,定义了两个函数SysAllocString(),和SysFreeString()用来分配和释放一个BSTR的内存空间。在这总结一下他们之间的相互转化:char*----->BSTR: _com_util::ConvertStringToBSTRWCHAR*---->BSTR:可以直接用 = 进行赋值,也可以使用SysAllocStringBSTR---->WCHAR:一般是直接使用等号即可,但是在WCHAR使用完之前不能释放,所以一般都是赋值给一个CStringBSTR---->char*:_com_util::ConvertBSTRToStringConvert函数是定义在头文件atlutil.h中并且需要引用comsupp.lib文件另外COM封装了一个_bstr_t的类,使用这个类就更加方便了,它封装了与char*之间的相互转化,可以直接使用赋值符号进行相互转化,同时也不用考虑回收内存的问题,它自己会进行内存回收。VARIANT 万能类型现代编程语言一般有强类型的语言和弱类型的语言,强类型的像C/C++、Java这样的,必须在使用前定义变量类型,而弱类型像Python这样的可以直接定义变量而不用管它的类型,甚至可以写出像:i = 0 i = "hello world"这样的代码,而且不同语言中可能同一种类型的变量在内存布局上也可能不一样。解决不同语言之间变量类型的冲突,COM定义了一种万能类型——VARIANT。typedef struct tagVARIANT VARIANT; typedef struct tagVARIANT VARIANTARG; struct tagVARIANT { union { struct __tagVARIANT { VARTYPE vt; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { LONGLONG llVal; LONG lVal; BYTE bVal; SHORT iVal; FLOAT fltVal; DOUBLE dblVal; VARIANT_BOOL boolVal; _VARIANT_BOOL bool; SCODE scode; CY cyVal; DATE date; BSTR bstrVal; IUnknown *punkVal; IDispatch *pdispVal; SAFEARRAY *parray; BYTE *pbVal; SHORT *piVal; LONG *plVal; LONGLONG *pllVal; FLOAT *pfltVal; DOUBLE *pdblVal; VARIANT_BOOL *pboolVal; _VARIANT_BOOL *pbool; SCODE *pscode; CY *pcyVal; DATE *pdate; BSTR *pbstrVal; IUnknown **ppunkVal; IDispatch **ppdispVal; SAFEARRAY **pparray; VARIANT *pvarVal; PVOID byref; CHAR cVal; USHORT uiVal; ULONG ulVal; ULONGLONG ullVal; INT intVal; UINT uintVal; DECIMAL *pdecVal; CHAR *pcVal; USHORT *puiVal; ULONG *pulVal; ULONGLONG *pullVal; INT *pintVal; UINT *puintVal; struct __tagBRECORD { PVOID pvRecord; IRecordInfo *pRecInfo; } __VARIANT_NAME_4; } __VARIANT_NAME_3; } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; };从定义上看出,它其实是一个巨大的联合体,将所有C/C++的基本类型都包含进来,甚至包含了像BSTR, 这样的COM中使用的类型。它通过成员vt来表示它当前使用的是哪种类型的变量。vt的类型是一个枚举类型,详细的定义请参见MSDN。为了简化操作,COM中也对它进行了一个封装——_variant_t,该类型可以直接使用任何类型的数据对其进行初始化操作。但是在使用里面的值时还是得判断它的vt成员的值COM中的其他操作最后附上一张COM常用函数表以供参考:
2017年11月02日
5 阅读
0 评论
0 点赞
2017-10-30
COM学习(三)——COM的跨语言
COM是基于二进制的组件模块,从设计之初就以支持所有语言作为它的一个目标,这篇文章主要探讨COM的跨语言部分。idl文件一般COM接口的实现肯定是以某一具体语言来实现的,比如说使用VC++语言,这就造成了一个问题,不同的语言对于接口的定义,各个变量的定义各不相同,如何让使用vc++或者说Java等其他语言定义的接口能被别的语言识别?为了达到这个要求,定义了一种文件格式idl——(Interface Definition Language)接口定义语言,IDL提供一套通用的数据类型,并以这些数据类型来定义更为复杂的数 据类型。一般来说,一个文件有下面几个部分说明接口的定义组件库的定义实现类的定义而各个部分又包括他们的属性定义,以及函数成员的定义属性:属性是在接口定义的上方,使用“[]”符号包裹,一般在属性中使用下面几个关键字:object:标明该部分是一个对象(可以理解为c++中的对象,包括接口和具体的实现类)uuid:标明该部分的GUIDversion:该部分的版本接口定义接口定义采用关键字interface,接口函数定义在一对大括号中,它的定义与类的定义相似,其中函数定义需要修饰函数各个参数的作用,比如使用in 表示它作为输入参数,out表示作为输出参数,retval表示该参数作为返回值,一般在VC++定义的接口中,函数返回值为HRESULT,但是需要返回一个值供外界调用,此时就使用输出参数并加上retval表示它将在其他语言中作为函数的返回值。组件库定义库使用library关键字定义,在定义库的时候,它的属性一般定义GUID和版本信息,而在库中通常定义库中的实现类的相关信息,库中的信息也是写在一对大括号中实现类的定义接口实现类使用关键字coclass,接口类的属性一般定义一个object,一个GUID,然后一般定义实现类不需要向在C++中那样定义它的各个接口,各个数据成员,只需要告知它实现哪些接口即可,也就是说它继承自哪些接口。下面是一个具体的例子:import "unknwn.idl"; [ object, uuid(CF809C44-8306-4200-86A1-0BFD5056999E) ] interface IMyString : IUnknown { HRESULT Init([in] BSTR bstrInit); HRESULT GetLength([out, retval] ULONG *pretLength); HRESULT Find([in] BSTR bstrFind, [out, retval] BSTR* bstrSub); }; [ uuid(ADF50A71-A8DD-4A64-8CCA-FFAEE2EC7ED2), version(1.0) ] library ComDemoLib { importlib("stdole32.tlb"); [ uuid(EBD699BA-A73C-4851-B721-B384411C99F4) ] coclass CMyString { interface IMyString; }; };上面的例子中定义了一个IMyString接口继承自IUnknown接口,函数参数列表中in表示参数为输入参数,out表示它为输出参数,retval表示该参数是函数的返回值。import导入了一个库文件类似于include。而importlib导入一个tlb文件,我们可以将其看成VC++中的#pragma comment导入一个lib库从上面不难看出一个IDL文件至少有3个ID,一个是接口ID,一个是库ID,还有一个就是实现类的ID在VC环境中通过midl命令可以对该文件进行编译,编译会生成下面几个我们在编写实现时会用到的重要文件:一个.h文件:包含各个部分的声明,以及接口的定义一个_i.c文件:包含各个部分的定义,主要是各个GUI的定义需要实现的导出函数一般我们需要在dll文件中导出下面几个全局的导出函数:STDAPI DllRegisterServer(void); STDAPI DllUnregisterServer(void); STDAPI DllGetClassObject(const CLSID & rclsid, const IID & riid, void ** ppv); STDAPI DllCanUnloadNow(void);其中DllRegisterServer用来向注册表中注册模块的相关信息,主要注测在HKEY_CLASSES_ROOT中,主要定义下面几项内容:字符串名称项,该项中包含一个默认值,一般给组件的字符串名称;CLSID子健,一般给实现类的GUID;CurVer子健一般是子健的版本以版本字符串为键的注册表项,该项中主要保存:默认值,当前版本的项目名称;CLSID当前版本库的实现类的GUID在HKEY_CLASSES_ROOT/CLSID子健中注册以实现类GUID字符串为键的注册表项,里面主要包含:默认值,组件字符串名称;InprocServer32,组件所在模块的全路径;ProgID组件名称;TypeLib组件类型库的ID,也就是在定义IDL文件时,定义的实现库的GUID。下面是具体的定义:const TCHAR *g_RegTable[][3] = { { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}"), 0, _T("FirstComLib.MyString")}, //组件ID { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\InprocServer32"), 0, (const TCHAR*)-1 }, //组建路径 { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\ProgID"), 0, _T("FirstComLib.MyString")}, //组件名称 { _T("CLSID\\{EBD699BA-A73C-4851-B721-B384411C99F4}\\TypeLib"), 0, _T("{ADF50A71-A8DD-4A64-8CCA-FFAEE2EC7ED2}") }, //类型库ID { _T("FirstComLib.MyString"), 0, _T("FirstComLib.MyString") }, //组件的字符串名称 { _T("FirstComLib.MyString\\CLSID"), 0, _T("{EBD699BA-A73C-4851-B721-B384411C99F4}")}, //组件的CLSID { _T("FirstComLib.MyString\\CurVer"), 0, _T("FirstComLib.MyString.1.0") }, //组件版本 { _T("FirstComLib.MyString.1.0"), 0, _T("FirstComLib.MyString") }, //当前版本的项目名称 { _T("FirstComLib.MyString.1.0\\CLSID"), 0, _T("{EBD699BA-A73C-4851-B721-B384411C99F4}")} //当前版本的CLSID };使用上一篇博文的代码,来循环注册这些项即可DllGetClassObject:该函数用来生成对应的工厂类,而工厂类负责产生对应接口的实现类。DllCanUnloadNow:函数用来询问是否可以卸载对应的dll,一般在COM中有两个全局的引用计数,用来记录当前内存中有多少个模块中的类,以及当前有多少个线程在使用它,如果当前没有线程使用或者存在的对象数为0,则可以卸载实现类的定义实现部分的整体结构图如下:由于所有类都派生自IUnknown,所在在这里就不显示这个基类了。每个实现类都对应了一个它具体的类工厂,而项目中CMyString类的类厂的定义如下:class CMyClassFactory : public IClassFactory { public: CMyClassFactory(); ~CMyClassFactory(); STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppvObject); STDMETHOD(LockServer)(BOOL isLock); STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject); STDMETHOD_(ULONG, AddRef)(void); STDMETHOD_(ULONG, Release)(void); protected: ULONG m_refs; };STDMETHOD宏展开如下:#define STDMETHOD(method) virtual HRESULT __stdcall method所以上面的代码展开后就变成了:virtual HRESULT __stdcall CreateInstance((IUnknown *pUnkOuter, REFIID riid, void **ppvObject);另外3个派生自IUnknown接口就没什么好说的,主要说说另外两个:CreateInstance:主要用来生成对应的实现类,然后再调用实现类——CMyString的QueryInterface函数生成对应的接口LockServer:当前是否被锁住:如果传入的值为TRUE,则表示被锁住,对应的锁计数器+1, 否则 -1至于CMyString类的代码与之前的大同小异,也就没什么说的。其他语言想要调用,以该项目为例,一般会经历下面几个步骤:调用对应语言提供的产生接口的函数,该函数参数一般是传入一个组件的字符串名称。如果要引用该项目中的组件则会传入FirstComLib.MyString在注册表的HKEY_CLASSES_ROOT\组件字符串名\CLSID(比如HKEY_CLASSES_ROOT\FirstComLib.MyString\CLSID)中找到对应的CLSID值在HKEY_CLASSES_ROOT\CLSID\对应ID\InprocServer32(CLSID\{EBD699BA-A73C-4851-B721-B384411C99F4}\InprocServer32)位置处找到对应模块的路径加载该模块根据IDL文件告知其他语言里面存在的接口,由语言调用对应的创建接口的函数创建接口调用模块的导出函数DllGetClassObject将查询到的CLSID作为第一个参数,并将接口ID作为第二个参数传入,得到一个接口6.后面根据idl文件中的定义,直接调用接口中提供的函数真实ATLCOM项目的解析最后来看看一个正式的ATLCOM项目里面的内容,来复习前面的内容,首先通过VC创建一个ATLCOM的dll项目在项目上右键-->New Atl Object,输入接口名称,IDE会根据名称生成一个对应的接口,还是以MyString接口为例,完成这一步后,整个项目的类结构如下:这些全局函数的作用与之前的相同,它里面多了一个_Module的全局对象,该对象类似于MFC中的CWinApp类,它用来表示整个项目的实例,里面封装了对于引用计数的管理,以及对项目中各个接口注册信息的管理,所以看DllRegisterServer等函数就会发现它们里面其实很简单,大部分的工作都由_Module对象完成。整个IDL文件的定义如下:import "oaidl.idl"; import "ocidl.idl"; [ object, uuid(E3BD0C14-4D0C-48F2-8702-9F8DBC96E154), dual, helpstring("IMyString Interface"), pointer_default(unique) ] interface IMyString : IDispatch { }; [ uuid(A61AC54A-1B3D-4D8E-A679-00A89E2CBE93), version(1.0), helpstring("FirstAtlCom 1.0 Type Library") ] library FIRSTATLCOMLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(11CBC0BE-B2B7-4B5C-A186-3C30C08A7736), helpstring("MyString Class") ] coclass MyString { [default] interface IMyString; }; };里面的内容与上一次的内容相差无几,多了一个helpstring属性,该属性用于产生帮助信息,当使用者在调用接口函数时IDE会将此提示信息显示给调用者。由于有系统框架给我们做的大量的工作,我们再也不用关心像引用计数的问题,只需要将精力集中在编写接口的实现上,减少了不必要的工作量。至此从结构上说明了为了实现跨语言COM组件内部做了哪些工作,当然只有这些工作是肯定不够的,后面会继续说明它所做的另一块工作——提供的一堆通用的变量类型。
2017年10月30日
4 阅读
0 评论
0 点赞
2017-10-17
COM学习(二)——COM的注册和卸载
COM组件是跨语言的,组件被注册到注册表中,在加载时由加载函数在注册表中查找到对应模块的路径并进行相关加载。它的存储规则如下:在注册表的HKEY_CLASSES_ROOT中以模块名的方式保存着COM模块的GUID,比如HKEY_CLASSES_ROOT\ADODB.Error\CLSID键中保存着模块ADODB.Error的GUID为{00000541-0000-0010-8000-00AA006D2EA4}在HKEY_CLASSES_ROOT\CLSID中以GUID为项名保存着对应组件的详细信息,比如之前的{00000541-0000-0010-8000-00AA006D2EA4}这个GUID在注册表中的位置为HKEY_CLASSES_ROOT\CLSID\{00000541-0000-0010-8000-00AA006D2EA4}\InprocServer32\项的默认键中保存着模块所在路径为%CommonProgramFiles%\System\ado\msado15.dll一般的COM模块都是使用regsvr32程序注册到注册表中,该程序在注册时会在模块中查找DllRegisterServer函数,卸载时调用模块中提供的DllUnregisterServer,所以要实现注册的功能主要需要实现这两个函数这两个函数的原型如下:STDAPI DllRegisterServer(); STDAPI DllUnregisterServer();通过VS的F12功能查找STDAPI 的定义如下:#define STDAPI EXTERN_C HRESULT STDAPICALLTYPE在查看STDAPICALLTYPE得到如下结果:#define STDAPICALLTYPE __stdcall所以这个宏展开也就是extern "C" HRESULT __stdcall DllRegisterServer();为了实现注册功能,首先定义一个全局的变量,用来表示需要写入到注册表中的项const TCHAR *g_regTable[][3] = { {_T("SOFTWARE\\ComDemo"), 0, _T("ComDemo")}, {_T("SOFTWARE\\ComDemo\\InporcServer32"), 0, (const TCHAR*)-1}这三项分别为注册表项,注册表项中的键名和键值,当键名为0时会创建一个默认的注册表键,最后一个-1我们会在程序中判断,如果键值为-1,那么值取为模块的路径下面是注册的函数STDAPI DllRegisterServer() { HKEY hKey = NULL; TCHAR szFileName[MAX_PATH] = _T(""); GetModuleFileName(g_hDllIns, szFileName, MAX_PATH); int nCount = sizeof(g_regTable) / sizeof(*g_regTable); for (int i = 0; i < nCount; i++) { LPCTSTR pszKeyName = g_regTable[i][0]; LPCTSTR pszValueName = g_regTable[i][1]; LPCTSTR pszValue = g_regTable[i][2]; if (pszValue == (const TCHAR*)-1) { pszValue = szFileName; } long err = RegCreateKey(HKEY_LOCAL_MACHINE, pszKeyName, &hKey); if (err != ERROR_SUCCESS) { return SELFREG_E_LAST; } err = RegSetValueEx(hKey, pszValueName, 0, REG_SZ, (const BYTE*)pszValue, _tcslen(pszValue) * sizeof(TCHAR)); if (err != ERROR) { return SELFREG_E_LAST; } RegCloseKey(hKey); } return S_OK; }在程序中会循环读取上述全局变量中的值,将值保存到注册表中,在上面的代码中有一句sizeof(g_regTable) / sizeof(*g_regTable);这个是算需要循环多少次,第一个sizeof得到的是这个二维数组的总大小。在C语言中我们说二维数组可以看做是由一维数组组成的,这个二维数组可以看成是由两个一维数组——一个由3个const TCHAR 成员组成的一维数组组成。所以g_regTab自然就是这个一维数组的首地址,第二个sizeof就是这个一维数组的大小,两个相除得到的就是一维数组的个数。卸载函数如下:STDAPI DllUnregisterServer() { int nCount = sizeof(g_regTable) / sizeof(*g_regTable); for (int i = nCount - 1; i >= 0; i--) { LPCTSTR pszKeyName = g_regTable[i][0]; long err = RegDeleteKey(HKEY_LOCAL_MACHINE, pszKeyName); if (err != ERROR_SUCCESS) { return SELFREG_E_LAST; } } return S_OK; }至此已经实现注册和卸载函数。后面就可以直接使用regsvr32这个程序进行注册和卸载了.
2017年10月17日
7 阅读
0 评论
0 点赞
2017-10-12
COM学习(一)——COM基础思想
概述学习微软技术COM是绕不开的一道坎,最近做项目的时候发现有许多功能需要用到COM中的内容,虽然只是简单的使用COM中封装好的内容,但是许多代码仍然只知其然,不知其所以然,所以我决定从头开始好好学习一下COM基础的内容,因此在这记录下自己学习的内容,以便日后参考,也给其他朋友提供一点学习思路。COM的全称是Component Object Module,组件对象模型。组件就我自己的理解就是将各个功能部分编写成可重用的模块,程序就好像搭积木一样由这些可重用模块构成,这样将各个模块的耦合降到最低,以后升级修改功能只需要修改某一个模块,这样就大大降低了维护程序的难度和成本,提高程序的可扩展性。COM是微软公司提出的组件标准,同时微软也定义了组件程序之间进行交互的标准,提供了组件程序运行所需的环境。COM是基于组件化编程的思想,在COM中每一个组件成为一个模块,它可以是动态链接库或者可执行文件,一个组件程序可以包含一个或者多个组件对象,COM对象不同于OOP(面向对象)中的对象,COM对象是定义在二进制机器代码基础之上,是跨语言的。而OOP中的对象是建立在语言之上的。脱离了语言对象也就不复存在.COM是独立在编程语言之上的,是语言无关的。COM的这一特性使得不同语言开发的组件之间的互相交互成为可能。COM对象和接口COM中的对象类似于C++中的对象,对象是某个类中的实例。而类则是一组相关的数据和功能组合在一起的一个定义。使用对象的应用(或另一个对象)称为客户,有时也称为对象的用户。 接口是一组逻辑相关的函数的集合,比如一组处理URL的接口,处理HTTP请求的接口等等。在习惯上接口通常是以"I"开头。对象通过接口成员函数为客户提供各种形式的服务。一个对象可以拥有多个不同的接口,以表现不同的功能集合。 在C++语言中,一个接口就是一个虚基类,而对象就是该接口的实现类,派生自该接口并实现接口的功能。class IBook { public: virtual void NextPage() = 0; virtual void ForwardPage() = 0; } class IAppliances { public: virtual void charge() = 0; virtual void shutdown() = 0; } class CKindle: public IBook, IAppliances { public: virtual void NextPage(); virtual void ForwardPage(); virtual void charge(); virtual void shutdown(); }就像上面的例子,上面的例子中提供了一个书本的接口,书本可以翻到上一页,下一页,而电器有充电和关机的接口,最后我们利用kindle这个类来实现这两个接口。所以在使用上我们可以利用下面的伪代码来使用pInterface = CreateInterface(ID_IBOOK, ID_KINDLE); pInterface->NextPage(); if(Late()) { pInter2 = pInterface->QueryInterface(ID_APPLIANCES); pInter2->shutdown(); }在平时我们使用kindle的翻页功能来看书,因为翻页功能在接口IBook,所以首先调用一个创建接口的函数,传入对应接口以及接口实现类的标识,用来生成相应的接口,其实在内部也就是根据类ID来创建一个对应的实现类的实例。然后根据需要转化为对应基类的指针。在看书看累的时候,将接口转化为电子产品的接口,调用对应的关机功能,关闭电子书。在之后比如说kindle进行了升级,也就是重写了实现这些接口的代码,但是接口原型不变,这样使用接口的代码不用改变,也就是说即使kindle对内部进行了升级,优化某些功能,用户在使用上仍然是那样在用,不必改变使用习惯。再比如kindle出了一个新款,提供了背光功能,这个时候可能提供一个新接口:class IAppliances2 : public IAppliances { public: virtual void Light() = 0; }然后只需要稍微更新一下CKindle这个实现类,新增一个Light接口的实现,在使用上如果不用背光功能原来的代码就够用了,如果要使用背光功能,只需要将原来的接口类型改为IAppliances2 ,并且添加调用背光功能的函数,而其余的功能也不变,这与实际生活相似,某个产品提供新功能时,一般保持原始功能的使用方法不变,新功能会有新的按钮或者其他方法进行打开。再比如说我不想用kindle了改用其他的电子阅读器,只要接口不变,我的使用方法基本不变,唯一改变的可能是我以前拿着kindle,现在拿着其他品牌的阅读器,也就是说可能要改变传入CreateInterface函数中的类标识。COM基本接口COM中所有接口都派生自该接口:struct IUnknown { virtual HRESULT QueryInterface(REFIID riid,void **ppvObject) = 0; virtual ULONG AddRef( void) = 0; virtual ULONG Release( void) = 0; };所有类都应该实现上述三个方法,AddRef主要将接口的引用计数+1, 而Release则是将引用计数 -1,当对象的引用计数为0,则会调用析构函数,释放对象的存储空间。每一次接口的创建和转化都会增加引用计数,而每次不再使用调用Release,都会把引用计数 -1,当引用计数为0时会释放对象的空间。QueryInterface主要用来进行接口转化,将对象的指针转化为另外一个接口的指针,就好像上面例子中pInter2 = pInterface->QueryInterface(ID_APPLIANCES);这句代码将之前的Ibook接口转化为电子产品的接口。在C++中也就是做了一次强制类型转化。对象和接口的唯一标识在COM中,对象本身对于客户来说是不可见的,客户请求服务时,只能通过接口进行。每一个接口都由一个128位的全局唯一标识符(GUID,Global Unique Identifier)来标识。客户通过GUID来获得接口的指针,再通过接口指针,客户就可以调用其相应的成员函数。与接口类似,每个组件也用一个 128 位 GUID 来标识,称为 CLSID(class identifer,类标识符或类 ID),用 CLSID 标识对象可以保证(概率意义上)在全球范围内的唯一性。实际上,客户成功地创建对象后,它得到的是一个指向对象某个接口的指针,因为 COM 对象至少实现一个接口(没有接口的 COM 对象是没有意义的),所以客户就可以调用该接口提供的所有服务。根据 COM 规范,一个 COM 对象如果实现了多个接口,则可以从某个接口得到该对象的任意其他接口。由此可看出,客户与 COM 对象只通过接口打交道,对象对于客户来说只是一组接口。在COM中GUID的定义如下:typedef struct _GUID { unsigned long Data1; unsigned short Data2; unsigned short Data3; unsigned char Data4[ 8 ]; } GUID;一般我们在程序中只是作为一个标志来使用,并不对它进行特别的操作。生成它一般是使用VS自带的GUID生成工具。而CLSID的定义如下:typedef GUID CLSID;其实在COM中一般涉及到ID的都是GUID,只是利用typedef另外定义了一个名称而已另外COM也提供了一组函数用来对GUID进行操作:函数功能IsEqualGUID判断GUID是否相等IsEqualCLSID判断CLSID是否相等IsEqualIID判断IID是否相等CLSIDFromProgID把字符串形式的CLSID转化为CLSID结构形式(类似于将字符串的234转化为数字,也是把字面上的CLSID转化为计算机能识别的CLSID)StringFromCLSID把CLSID转化为字符串形式IIDFromString把字符串形式的IID转化为IID接口形式StringFromIID把IID结构转化为字符串StringFromGUID2把GUID形式转化为字符串形式COM接口的一般使用步骤一般使用COM中的时候首先使用CoInitialize初始化COM环境,不用的时候使用CoUninitialize卸载COM环境,在使用接口中一般需要进行下面的步骤调用CoCreateInstance函数传入对应的CLSID和对应的IID,生成对应对象并传入相应的接口指针。使用该指针进行相关操作调用接口的QueryInterface函数,转化为其他形式的接口在最后分别调用各个接口的Release函数,释放接口下面提供一个小例子,以供参考,也方便更好的理解COM//组件部分 extern "C" __declspec(dllexport) void __stdcall ComCreateObject(GUID clsID, GUID interfaceID, void** pObj); void __stdcall ComCreateObject(GUID clsID, GUID interfaceID, void** pObj) { if (clsID == CLSID_COMSTRING) { CComString *pComObject = new CComString; *pObj = pComObject->QueryInterface(interfaceID); } } class IComBase { public: virtual void* QueryInterface(GUID gInterfaceId) = 0; virtual void AddRef() = 0; virtual void Release() = 0; }; static const GUID IID_ICOMSTRING = { 0xb2fcd22c, 0x63fa, 0x4f61, { 0xbf, 0x12, 0xd3, 0xd2, 0x5a, 0x99, 0x59, 0x24 } }; class IComString : public IComBase { public: virtual void Init(LPCTSTR pStr) = 0; virtual int Find(LPCTSTR lpSubStr) = 0; virtual int GetLength() = 0; }; static const GUID CLSID_COMSTRING = { 0xf57f3489, 0xff2d, 0x4c97, { 0xb1, 0xf6, 0xc, 0x60, 0x7e, 0xf7, 0xae, 0xfc } }; class CComString : public IComString { public: virtual void* QueryInterface(GUID gInterfaceId); virtual void AddRef(); virtual void Release(); virtual void Init(LPCTSTR pStr); virtual int Find(LPCTSTR lpSubStr); virtual int GetLength(); protected: int m_nCnt = 0; CString m_csString; }; //cpp void* CComString::QueryInterface(GUID gInterfaceId) { if (gInterfaceId == IID_ICOMSTRING) { //该接口的引用计数+1 AddRef(); return dynamic_cast<IComString*>(this); } //如果它还实现了其他接口,可以再写判断,生成其他类型的接口 return NULL; } void CComString::AddRef() { m_nCnt++; } void CComString::Release() { m_nCnt--; //引用计数为0,此时没有该类的接口被使用,应该释放该类 if (m_nCnt == 0) { delete this; } } void CComString::Init(LPCTSTR pStr) { m_csString = pStr; } int CComString::Find(LPCTSTR lpSubStr) { return m_csString.Find(lpSubStr); } int CComString::GetLength() { return m_csString.GetLength(); }这些代码被封装在一个dll中,dll中导出一个函数ComCreateObject,外部在使用时调用该函数传入对应的ID,以便生成对应的接口。在这个dll里面提供一个接口的基类IComBase,这个是仿照了COM种的IUnknow基类,另外定义了一个IComString字符串的接口,同时定义了它的实现类CComString,为了简单,它的功能方法我直接使用了一个CString类实现。在函数ComCreateObject,会根据传入对应的类ID,来生成对应的类实例,然后调用实例的QueryInterface,转化成对应的接口,在实现类中实现了这个方法,实现类中的QueryInterface方法主要完成了类型转化并将引用计数+1。而Release函数在每次-1的时候会进行判断,当引用计数为0时销毁该类的实例由于类是new出来创建在堆上的,所以每次用完一定要记得调用Release释放,否则会造成内存泄露注意:在使用这里使用的是dynamic_cast进行类型转化,在进行类的强制类型转化时,特别是在有多重继承的情况下,最好使用dynamic_cast方式进行转化,当一个类拥有多个基类时,类中有多个虚函数表,为了能正常找到对应的虚函数表,就需要进行对应的偏移量的计算,C中的强制类型转化是直接将对象的首地址进行转化,这样在寻址虚函数表时可能会出错。而dynamic_cast会进行对应的计算。详细情形请参考这里在使用上void ComInitialize(); void ComUninitialize(); typedef void(__stdcall *pfnCreateInstance)(GUID, GUID, void**); pfnCreateInstance CreateInstance; HMODULE hComDll = NULL; int _tmain(int argc, _TCHAR* argv[]) { ComInitialize(); IComString *pIString = NULL; CreateInstance(CLSID_COMSTRING, IID_ICOMSTRING, (void**)&pIString); pIString->Init(_T("Hello World")); IComString* pIString2 = (IComString*)(pIString->QueryInterface(IID_ICOMSTRING)); int nLength = pIString2->GetLength(); int iPos = pIString2->Find(_T("World")); printf("%d, %d\n", nLength, iPos); pIString->Release(); pIString2->Release(); return 0; } void ComInitialize() { hComDll = LoadLibrary(_T("ComInterface.dll")); if (NULL != hComDll) { CreateInstance = (pfnCreateInstance)GetProcAddress(hComDll, "ComCreateObject"); } } void ComUninitialize() { FreeLibrary(hComDll); }给使用者使用时只需要提供对应类和接口的GUID,然后将函数ComCreateObject原型提供给调用者,以便生成对应的接口。这里为了模仿COM的使用定义了ComInitialize和ComUninitialize这两个函数,真实的初始化函数怎么写的,我也不知道,在这里只是为了模仿COM的使用。至此相信各位小伙伴应该对COM有了一个初步的了解
2017年10月12日
3 阅读
0 评论
0 点赞
2017-10-09
使用FormatMessage函数编写一个内核错误码查看器
在编写驱动程序的时候,常用的一个结构是NTSTATUS,它来表示操作是否成功,但是对于失败的情况它的返回码过多,不可能记住所有的情况,应用层有一个GetLastError函数,根据这个函数的返回值可以通过错误查看器来查看具体的错误原因,但是内核中就没有这么方便了,我之前在网上找资料的时候发现很多人都是把错误码和它的具体原因都列举出来,然后人工进行对照查找,这样很不方便,有没有类似于应用层上错误码查看工具的东西呢?终于皇天不负有心人,我在微软官网上找到了FormatMessage的说明,自己实现了这个功能,现在讲这个部分记录下来,以供大家参考void CNTLookErrorDlg::OnBnClickedBtnLookup() { // TODO: 查找错NTSTATUS值对应的错误 LPVOID lpMessageBuffer; HMODULE Hand = LoadLibrary(_T("NTDLL.DLL")); DWORD dwErrCode = 0; dwErrCode = GetDlgItemInt(IDC_EDIT_ERRCODE); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE, Hand, dwErrCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMessageBuffer, 0, NULL ); // Now display the string. GetDlgItem(IDC_EDIT_ERRMSG)->SetWindowText((LPTSTR)lpMessageBuffer); // Free the buffer allocated by the system. LocalFree( lpMessageBuffer ); FreeLibrary(Hand); }这是用mfc写的一段代码,首先加载NTDLL.dll文件,然后调用FormatMessage,第一个参数需要新加入FORMAT_MESSAGE_FROM_HMODULE表示需要从某个模块中取出错误码和具体字符串之间的对应关系,然后将第二个参数传入dll的句柄,这个dll中记录了内核中错误码和对应字符串的信息。如果不加这个标志,那么默认从系统中获取,也就是获取应用层的GetLastError中返回的信息与错误字符串的对应关系。有了这个信息,剩下的就交给FormatMessage来进行格式化啦。这样一个简单的工具就完成了,再也不用满世界的找对应关系然后手工对比了,程序的运行结果如下:
2017年10月09日
5 阅读
0 评论
0 点赞
1
...
21
22
23
...
32