??xml version="1.0" encoding="utf-8" standalone="yes"?> 字符~码的问题看似很,l常被技术h员忽视,但是很容易导致一些莫名其妙的问题。这里ȝ了一下字W编码的一些普及性的知识Q希望对大家有所帮助?/p>
说到字符~码Q不得不说ASCII码的双Ӏ计机一开始发明的时候是用来解决数字计算的问题,后来Z发现Q计机q可以做更多的事Q例如文本处理。但׃计算机只?#8220;?#8221;Q因此h们必d诉计机哪个数字来代表哪个特定字W,例如65代表字母‘A’Q?6代表字母‘B’Q以此类推。但?strong>计算Z间字W?数字的对应关pdd一_否则׃造成同一D|字在不同计算Z昄出来的字W不一?/span>。因此美国国家标准协会ANSI制定了一个标准,规定了常用字W的集合以及每个字符对应的编Pq就是ASCII字符集(Character SetQ,也称ASCII码?/p>
当时的计机普遍使用8比特字节作ؓ最的存储和处理单元,加之当时用到的字W也很少Q?6个大写英文字母q有数字再加上其他常用符P也不?00个,因此使用7个比特位可以高效的存储和处理ASCII码,剩下最高位1比特被用作一些通讯pȝ的奇偶校验?/p>
注意Q字节代表系l能够处理的最单位,不一定是8比特。只是现代计机的事实标准就是用8比特来代表一个字节。在很多技术规格文献中Qؓ了避免生歧义,更們于?位组QOctetQ而不是字节(ByteQ这个术语来8个比特的二进制流。下文中Z便于理解Q我会g用大家熟悉的“字节”q个概念?/p> ASCII字符集由95个可打印字符Q?x20-0x7EQ和33个控制字W(0x00-0x19Q?x7FQ组成。可打印字符用于昄在输备上Q例如荧屏或者打印纸上,控制字符用于向计机发出一些特D指令,例如0x07会让计算机发出哔的一壎ͼ0x00通常用于指示字符串的l束Q?x0D?x0A用于指示打印机的打印针头退到行首(回RQƈUd下一行(换行Q?/p>
那时候的字符~解码系l非常简单,是单的查表q程。例如将字符序列~码Zq制写入存储设备,只需要在ASCII字符集中依次扑ֈ字符对应的字节,然后直接该字节写入存储讑֤卛_。解码二q制的q程也是cM?/p>
当计机开始发展v来的时候,Z逐渐发现QASCII字符集里那可怜的128个字W已l不能再满他们的需求了。h们就在想Q一个字节能够表C的数字Q编P?56个,而ASCII字符只用C0x00~0x7FQ也是占用了前128个,后面128个数字不用白不用Q因此很多h打v了后面这128个数字的L。可是问题在于,很多人同时有q样的想法,但是大家对于0x80-0xFFq后面的128个数字分别对应什么样的字W,却有各自的想法。这导致了当时销往世界各地的机器上出现了大量各式各LOEM字符集?/p>
下面q张表是IBM-PC机推出的其中一个OEM字符集,字符集的?28个字W和ASCII字符集的基本一_Z么说基本一致呢Q是因ؓ?2个控制字W在某些情况下会被IBM-PC机当作可打印字符解释Q,后面128个字W空间加入了一些欧z国家用到的重音字符Q以及一些用于画U条ȝ字符?/p>
事实上,大部分OEM字符集是兼容ASCII字符集的Q也是_大家对于0x00~0x7Fq个范围的解释基本是相同的,而对于后半部?x80~0xFF的解释却不一定相同。甚x时候同L字符在不同OEM字符集中对应的字节也是不同的?/p>
不同的OEM字符集导致h们无法跨机器交流各种文档。例如职员甲发了一简历résumésl职员乙Q结果职员乙看到的却是r 上面我们提到的字W集都是Z单字节编码,也就是说Q一个字节翻译成一个字W。这对于拉丁语系国家来说可能没有什么问题,因ؓ他们通过扩展W?个比特,可以得?56个字W了Q够用了。但是对于亚z国家来_256个字W是q远不够用的。因此这些国家的Zؓ了用上电脑,又要保持和ASCII字符集的兼容Q就发明了多字节~码方式Q相应的字符集就UCؓ多字节字W集。例如中国用的是双字节字W集~码QDBCSQDouble Byte Character SetQ?/p>
对于单字节字W集来说Q代码页中只需要有一张码表即可,上面记录着256个数字代表的字符。程序只需要做单的查表操作可以完成编解码的过E?/p>
代码|字符集编码的具体实现Q你可以把他理解Z?#8220;字符-字节”映射表,通过查表实现“字符-字节”的翻译。下面会有更详细的描q?/p> 而对于多字节字符集,代码中通常会有很多码表。那么程序怎么知道该用哪张码表去解码二进制流呢?{案是,ҎW一个字节来选择不同的码表进行解?/span>?/p>
例如目前最常用的中文字W集GB2312Q涵盖了所有简体字W以及一部分其他字符QGBKQK代表扩展的意思)则在GB2312的基上加入了对繁体字W等其他非简体字W(GB18030字符集不是双字节字符集,我们在讲Unicode的时候会提到Q。这两个字符集的字符都是使用1-2个字节来表示。Windowspȝ采用936代码|实现对GBK字符集的~解码。在解析字节的时候,如果遇到字节的最高位?的话Q那么就使用936代码中的第1张码表进行解码,q就和单字节字符集的~解码方式一致了?/p>
当字节的高位?的时候,切的说Q当W一个字节位?x Q关?36代码中完整的码表信息,参见MSDNQ?a >http://msdn.microsoft.com/en-us/library/cc194913%28v=MSDN.10%29.aspx.Q?/p>
按照936代码늚码表Q当E序遇到q箋字节?x81 0x40的时候,׃解码?#8220;?#8221;字符? 不同ASCII衍生字符集的出现Q让文档交流变得非常困难Q因此各U组l都陆箋q行了标准化程。例如美国ANSIl织制定了ANSI标准字符~码Q注意,我们现在通常说到ANSI~码Q通常指的是^台的默认~码Q例如英文操作系l中是ISO-8859-1Q中文系l是GBKQ,ISOl织制定的各UISO标准字符~码Q还有各国也会制定一些国家标准字W集Q例如中国的GBKQGB2312和GB18030?/p>
操作pȝ在发布的时候,通常会往机器里预装这些标准的字符集还有^C用的字符集,q样只要你的文档是用标准字W集~写的,通用性就比较高了。例如你用GB2312字符集编写的文档Q在中国大陆内的M机器上都能正显C。同Ӟ我们也可以在一台机器上阅读多个国家不同语言的文档了Q前提是本机必须安装该文档用的字符集?/p>
虽然通过使用不同字符集,我们可以在一台机器上查阅不同语言的文档,但是我们仍然无法解决一个问题:在一份文档中昄所有字W?/span>。ؓ了解册个问题,我们需要一个全人类达成p的巨大的字符集,q就是Unicode字符集?/p>
Unicode字符集涵盖了目前人类使用的所有字W,qؓ每个字符q行l一~号Q分配唯一的字W码QCode PointQ。Unicode字符集将所有字W按照用上的频J度划分?7个层面(PlaneQ,每个层面上有216=65536个字W码I间?/p>
其中W?个层面BMPQ基本涵盖了当今世界用到的所有字W。其他的层面要么是用来表CZ些远古时期的文字Q要么是留作扩展。我们^常用到的Unicode字符Q一般都是位于BMP层面上的。目前Unicode字符集中有大量字符I间未用? 在Unicode出现之前Q所有的字符集都是和具体~码Ҏl定在一LQ都是直接将字符和最l字节流l定MQ例如ASCII~码pȝ规定使用7比特来编码ASCII字符集;GB2312以及GBK字符集,限定了用最?个字节来~码所有字W,q且规定了字节序。这L~码pȝ通常用简单的查表Q也是通过代码就可以直接字W映ؓ存储讑֤上的字节了。例如下面这个例子: q种方式的缺点在于,字符和字节流之间耦合得太紧密了,从而限定了字符集的扩展能力。假设以后火星h入住地球了,要往现有字符集中加入火星文就变得很难甚至不可能了Q而且很容易破坏现有的~码规则?/p>
因此Unicode在设计上考虑Cq一点,字W集和字W编码方案分d?/p>
也就是说Q?strong>虽然每个字符在Unicode字符集中都能扑ֈ唯一定的编P字符码,又称Unicode码)Q但是决定最l字节流的却是具体的字符~码。例如同h对Unicode字符“A”q行~码QUTF-8字符~码得到的字节流?x41Q而UTF-16Q大端模式)得到的是0x00 0x41?/p>
UCS-2/UTF-16 如果要我们来实现Unicode字符集中BMP字符的编码方案,我们会怎么实现Q由于BMP层面上有216=65536个字W码Q因此我们只需要两个字节就可以完全表示q所有的字符了?/p>
举个例子Q?#8220;?#8221;的Unicode字符码是0x4E2D(01001110 00101101)Q那么我们可以编码ؓ01001110 00101101Q大端)或?0101101 01001110 Q小端)?/p>
UCS-2和UTF-16对于BMP层面的字W均是?个字节来表示Qƈ且编码得到的l果完全一致。不同之处在于,UCS-2最初设计的时候只考虑到BMP字符Q因此用固?个字节长度,也就是说Q他无法表示Unicode其他层面上的字符Q而UTF-16Z解除q个限制Q支持Unicode全字W集的编解码Q采用了变长~码Q最?个字节,如果要编码BMP以外的字W,则需?个字节结?/span>Q这里就不讨论那么远Q有兴趣可以参考维基百U:UTF-16/UCS-2?/p>
Windows从NT时代开始就采用了UTF-16~码Q很多流行的~程q_Q例?NetQJavaQQtq有Mac下的Cocoa{都是用UTF-16作ؓ基础的字W编码。例如代码中的字W串Q在内存中相应的字节就是用UTF-16~码q的?/p>
UTF-8 UTF-8应该是目前应用最q泛的一UUnicode~码Ҏ。由于UCS-2/UTF-16对于ASCII字符使用两个字节q行~码Q存储和处理效率相对低下Qƈ且由于ASCII字符l过UTF-16~码后得到的两个字节Q高字节始终?x00Q很多C语言的函数都此字节视ؓ字符串末从而导致无法正解析文本。因此一开始推出的时候遭到很多西方国家的抵触Q大大媄响了Unicode的推行。后来聪明的Z发明了UTF-8~码Q解决了q个问题?/p>
UTF-8~码Ҏ采用1-4个字节来~码字符Q方法其实也非常单?/p>
Q上图中的x代表Unicode码的?位,y代表?位) 对于ASCII字符的编码用单字节Q和ASCII~码一怸Pq样所有原先用ASCII~解码的文档可以直接{到UTF-8~码了。对于其他字W,则?-4个字节来表示Q其中,首字节前|?的数目代表正解析所需要的字节敎ͼ剩余字节的高2位始l是10。例如首字节?110yyyyQ前|有3?Q说明正解析d需?个字节,需要和后面2个以10开头的字节l合才能正确解析得到字符?/p>
关于UTF-8的更多信息,参考维基百U:UTF-8?/p>
GB18030 M能够Unicode字符映射为字节流的编码都属于Unicode~码。中国的GB18030~码Q覆盖了Unicode所有的字符Q因此也是一UUnicode~码。只不过他的~码方式q不像UTF-8或者UTF-16一PUnicode字符的编号通过一定的规则q行转换Q而只能通过查表的手D进行编码?/p>
关于GB18030的更多信息,参考:GB18030?/p>
Unicode是两个字节吗Q?/strong> Unicode只是定义了一个庞大的、全球通用的字W集Qƈ为每个字W规定了唯一定的编P具体存储Z么样的字节流Q取决于字符~码Ҏ。推荐的Unicode~码是UTF-16和UTF-8?/p>
带签名的UTF-8指的是什么意思? 带签名指的是字节以BOM标记开始。很多Y件会“”的探当前字节流使用的字W编码,q种探测q程Z效率考虑Q通常会提取字节流前面若干个字节,看看是否W合某些常见字符~码的编码规则。由于UTF-8和ASCII~码对于U英文的~码是一LQ无法区分开来,因此通过在字节流最前面dBOM标记可以告诉软gQ当前用的是Unicode~码Q判别成功率十分准了。但是需要注意,不是所有Y件或者程序都能正处理BOM标记Q例如PHP׃会检BOM标记Q直接把它当普通字节流解析了。因此如果你的PHP文g是采用带BOM标记的UTF-8q行~码的,那么有可能会出现问题?/p>
Unicode~码和以前的字符集编码有什么区别? 早期字符~码、字W集和代码页{概念都是表辑一个意思。例如GB2312字符集、GB2312~码Q?36代码,实际上说的是同个东西。但是对于Unicode则不同,Unicode字符集只是定义了字符的集合和唯一~号QUnicode~码Q则是对UTF-8、UCS-2/UTF-16{具体编码方案的l称而已Qƈ不是具体的编码方案。所以当需要用到字W编码的时候,你可以写gb2312Qcodepage936Qutf-8Qutf-16Q但请不要写unicodeQ看q别人在|页的meta标签里头写charset=unicodeQ有感而发Q?/p>
q指的是程序显C出来的字符文本无法用Q何语a去解诅R一般情况下会包含大?或者�。ؕ码问题是所有计机用户或多或少会遇到的问题?span style="color: #008000">造成q的原因就是因Z用了错误的字W编码去解码字节?/strong> 例如最常见的网ؕ码问题。如果你是网站技术h员,遇到q样的问题,需?span style="color: #000000">查以下原因: 注意Q网解析的q程如果使用的字W编码不正确Q还可能会导致脚本或者样式表出错。具体细节可以参考我以前写过的文章:文档字符集导致的脚本错误?a >Asp.Net面的编码问?/font>?/p>
不久前看到某技术论坛有人反馈,WinFormE序使用ClipboardcȝGetDataҎ去访问剪切板中的HTML内容时会出现q的问题,我估计也是由于WinForm在获取HTML文本的时候没有用Ҏ的字符~码D的。Windows剪脓板只支持UTF-8~码Q也是说你传入的文本都会被UTF-8~解码。这样一来,只要两个E序都是调用Windows剪切板API~程的话Q那么复制粘贴的q程中不会出Cؕ码。除非一方在获取到剪贴板数据之后使用了错误的字符~码q行解码Q才会得Cؕ码(我做了简单的WinForm剪切板编E实验,发现GetData使用的是pȝ默认~码Q而不是UTF-8~码Q?/p>
关于q中出?或者�Q这里需要额外提一下,当程序用特定字W编码解析字节流的时候,一旦遇到无法解析的字节时Q就会用?或者�来替代。因此,一旦你最l解析得到的文本包含q样的字W,而你又无法得到原始字节流的时候,说明正确的信息已l彻底丢׃Q尝试Q何字W编码都无法从这L字符文本中还原出正确的信息来?/p>
字符集(Character SetQ?/strong>Q字面上的理解就是字W的集合Q例如ASCII字符集,定义?28个字W;GB2312定义?445个字W。?strong>计算机系l中提到的字W集准确来说Q指的是已编L字符的有序集合(不一定是q箋Q?/span>?/p>
字符码(Code PointQ?/strong>指的是字符集中每个字符的数字编受例如ASCII字符集用0-127q连l的128个数字分别表C?28个字W;GBK字符集用区位码的方式ؓ每个字符~号Q首先定义一?4X94的矩阵,行称?#8220;?#8221;Q列UCؓ“?#8221;Q然后将所有国标汉字放入矩阵当中,q样每个汉字可以用唯一?#8220;Z”码来标识了。例?#8220;?#8221;字被攑ֈ54区第48位,因此字符码就?448。而Unicode中将字符集按照一定的cd划分?~16q?7个层面(PlanesQ中Q每个层面中拥有216=65536个字W码Q因此Unicoded拥有的字W码Q也xUnicode的字W空间d?7*65536=1114112?/p>
~码的过E是字W{换成字节?/p>
解码的过E是字节流解析为字W?/p>
字符~码QCharacter EncodingQ?/strong>是将字符集中的字W码映射为字节流的一U具体实现方案。例如ASCII字符~码规定使用单字节中低位?个比特去~码所有的字符。例?#8216;A’的编h65Q用单字节表C就?x41Q因此写入存储设备的时候就是b’01000001’。GBK~码则是区位码QGBK的字W码Q中的区码和位码的分别加?xA0Q?60Q的偏移Q之所以要加上q样的偏U,主要是ؓ了和ASCII码兼容)Q例如刚刚提到的“?#8221;字,Z码是5448Q十六进制是0x3630Q区码和位码分别加上0xA0的偏UM后就得到0xD6D0Q这是“?#8221;字的GBK~码l果?/p>
代码(Code PageQ?/strong>一U字W编码具体Ş式。早期字W相对少Q因此通常会用类D格的形式字W直接映ؓ字节,然后通过查表的方式来实现字符的编解码。现代操作系l沿用了q种方式。例如Windows使用936代码cMacpȝ使用EUC-CN代码实现GBK字符集的~码Q名字虽然不一P但对于同一汉字的编码肯定是一L?/p>
大小?/strong>的说法源自《格列佛游记》。我们知道,鸡蛋通常一端大一端小Q小人国的h们对于剥蛋壳时应从哪一端开始剥h着不一L看法。同P计算机界对于传输多字节字Q由多个字节来共同表CZ个数据类型)Ӟ是先传高位字节(大端Q还是先传低位字节(端Q也有着不一L看法Q这是计算机里头大端模式的由来了。无论是写文件还是网l传输,实际上都是往设备进行写操作的过E,而且q个写操作是从流的低地址向高地址开始写Q这很符合h的习惯)Q对于多字节字来_如果先写入高位字节,则称作大端模式。反之则UC端模式。也是_大端模式下,字节序和设备的地址序是相反的Q而小端模式则是相同的。一般网l协议都采用大端模式q行传输?/p>
参考链接: 其实def的功能相当于extern “C” __declspec(dllexport)Q所以它也仅能处?/span>C函数Q而不能处理重载函数。?/span>__declspec(dllexport)?/span>__declspec(dllimport)配合使用能够适应M情况Q因?/span>__declspec(dllexport)是更为先q的Ҏ。所以,目前普遍的看法是不?/span>def文gQ我也同意这个看法?br /> M来说有两U方法, 2)名字修饰U定 q种声明方式的话Q导入会出错Q因为找不到buildq个函数Q这里的build已经Ҏ名字是_build@4。当Ӟ如果我们直接?#8220;external 'test.dll' name '_build@4';”应该是没有问题的。不q不爽。ؓ什么win api的函数就没有q些׃八糟的东西? 本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/gobitan/archive/2007/03/18/1532769.aspx// convert_from_char.cpp
// compile with: /clr /link comsuppw.lib
#include <iostream>
#include <stdlib.h>
#include <string>
#include "atlbase.h"
#include "atlstr.h"
#include "comutil.h"
using namespace std;
using namespace System;
int main()
{
// Create and display a C style string, and then use it
// to create different kinds of strings.
char *orig = "Hello, World!";
cout << orig << " (char *)" << endl;
// newsize describes the length of the
// wchar_t string called wcstring in terms of the number
// of wide characters, not the number of bytes.
size_t newsize = strlen(orig) + 1;
// The following creates a buffer large enough to contain
// the exact number of characters in the original string
// in the new format. If you want to add more characters
// to the end of the string, increase the value of newsize
// to increase the size of the buffer.
wchar_t * wcstring = new wchar_t[newsize];
// Convert char* string to a wchar_t* string.
size_t convertedChars = 0;
mbstowcs_s(&convertedChars, wcstring, newsize, orig, _TRUNCATE);
// Display the result and indicate the type of string that it is.
wcout << wcstring << _T(" (wchar_t *)") << endl;
// Convert the C style string to a _bstr_t string.
_bstr_t bstrt(orig);
// Append the type of string to the new string
// and then display the result.
bstrt += " (_bstr_t)";
cout << bstrt << endl;
// Convert the C style string to a CComBSTR string.
CComBSTR ccombstr(orig);
if (ccombstr.Append(_T(" (CComBSTR)")) == S_OK)
{
CW2A printstr(ccombstr);
cout << printstr << endl;
}
// Convert the C style string to a CstringA and display it.
CStringA cstringa(orig);
cstringa += " (CStringA)";
cout << cstringa << endl;
// Convert the C style string to a CStringW and display it.
CStringW cstring(orig);
cstring += " (CStringW)";
// To display a CStringW correctly, use wcout and cast cstring
// to (LPCTSTR).
wcout << (LPCTSTR)cstring << endl;
// Convert the C style string to a basic_string and display it.
string basicstring(orig);
basicstring += " (basic_string)";
cout << basicstring << endl;
// Convert the C style string to a System::String and display it.
String ^systemstring = gcnew String(orig);
systemstring += " (System::String)";
Console::WriteLine("{0}", systemstring);
delete systemstring;
}
Output
]]>q是得从ASCII码说?/h2>
OEM字符集的衍生
sum
sQ因?#233;字符在职员甲机器上的OEM字符集中对应的字节是0x82Q而在职员乙的机器上,׃使用的OEM字符集不同,?x82字节解码后得到的字符却是
?/p>
多字节字W集QMBCSQ和中文字符?/h2>
81
–0xFE之间ӞҎW一个字节不同找C码页中的相应的码表,例如当第一个字节是0x81Q那么对?36中的下面q张码表Q?/code>
ANSI标准、国家标准、ISO标准
Unicode的出?/h2>
Unicode字符集概q?/h3>
~码pȝ的变?/h3>
常见的Unicode~码
Unicode相关的常见问?/h3>
q问题
必要的术语解?/h2>
]]>Def?/span>__declspec(dllexport)
如何在dll中定义输出函?/h1>
一U是d一个def定义文gQ在此文件中定义dll中要输出的函敎ͼ
W二U是在源代码中待输出的函数前加上__declspec(dllexport)关键字?/font>
1、修饰名(Decoration name)
“C”或?#8220;C++”函数在内部(~译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字W串。有些情况下使用函数的修饰名是必要的Q如在模块定义文仉头指定输?#8220;C++”重蝲函数、构造函数、析构函敎ͼ又如在汇~代码里调用“C””?#8220;C++”函数{?
修饰名由函数名、类名、调用约定、返回类型、参数等共同军_?
2、名字修饰约定随调用U定和编译种c?C或C++)的不同而变化。函数名修饰U定随编译种cd调用U定的不同而不同,下面分别说明?
a、C~译时函数名修饰U定规则Q?
__stdcall调用U定在输出函数名前加上一个下划线前缀Q后面加上一?#8220;@”W号和其参数的字节数Q格式ؓ_functionname@number?
__cdecl调用U定仅在输出函数名前加上一个下划线前缀Q格式ؓ_functionname?
__fastcall调用U定在输出函数名前加上一?#8220;@”W号Q后面也是一?#8220;@”W号和其参数的字节数Q格式ؓ@functionname@number?
它们均不改变输出函数名中的字W大写Q这和PASCAL调用U定不同QPASCALU定输出的函数名无Q何修C全部大写?
b、C++~译时函数名修饰U定规则Q?
__stdcall调用U定Q?
1、以“?”标识函数名的开始,后跟函数名;
2、函数名后面?#8220;@@YG”标识参数表的开始,后跟参数表;
3、参数表以代可C:
X--void Q?
D--charQ?
E--unsigned charQ?
F--shortQ?
H--intQ?
I--unsigned intQ?
J--longQ?
K--unsigned longQ?
M--floatQ?
N--doubleQ?
_N--boolQ?
....
PA--表示指针Q后面的代号表明指针cdQ如果相同类型的指针q箋出现Q以“0”代替Q一?#8220;0”代表一ơ重复;
4、参数表的第一ؓ该函数的q回值类型,其后依次为参数的数据cd,指针标识在其所指数据类型前Q?
5、参数表后以“@Z”标识整个名字的结束,如果该函数无参数Q则?#8220;Z”标识l束?
其格式ؓ“?functionname@@YG*****@Z”?#8220;?functionname@@YG*XZ”Q例?
int Test1Qchar *var1,unsigned longQ?----“?Test1@@YGHPADK@Z”
void Test2Q) -----“?Test2@@YGXXZ”
__cdecl调用U定Q?
规则同上面的_stdcall调用U定Q只是参数表的开始标识由上面?#8220;@@YG”变ؓ“@@YA”?
__fastcall调用U定Q?
规则同上面的_stdcall调用U定Q只是参数表的开始标识由上面?#8220;@@YG”变ؓ“@@YI”?
VC++对函数的省缺声明?"__cedcl ",只能被C/C++调用.
大家都应该知道,函数调用都有修饰W,有__cdecl,__stdcall,__fastcall{等Q不同修饰符意味着不同的参数出栈方式以及不同的函数名称变化?
对于C语言Q默认是__cdeclQ但如果我们要做DLL需要其它语a例如Delphi、VB{调用,通常都徏议声明ؓ__stdcall。win32的api基本都是__stdcall方式Q就是那个WINAPI宏)?
问题出在这个stdcall了。对于VC来说Q__cdecl修饰的函敎ͼ在DLL的导出名字一般是没有变化的,但对于__stdcall׃做一些改变,在前面加一个下划线Q后面再补上一?#8220;@”Q再加上参数的字节长度d?
例如Q对于函?int add(int a, int b)Q如果int __cdecl add(int a, int b)Q在DLL的导出名字还?#8220;add”Q但如果是int __stdcall add(int a, int b)Q在DLL的导出名字就?#8220;_add@8”Q查看导出名字可以用VC自带的dumpbin命oQ方法是“dumpbin /exports aaa.dll”Q?
好了Q说了一大堆废话Q开始进主题啦?
开始的时候,我做了一个test.dllQ例如包含一个导出的函数 int __stdcall build(int n)?
在delphi的时候,如果?
后来在网上找了一遍,写了一个def文g?
q样生成的dll的导出名字就没有那些׃八糟的修饰符了。不q这个时候又轮到Cq边有问题了。原因是Cq边在链接的时候找不到_build@4q个函数。真是晕倒,两边不讨好?
我就觉得奇怪,怎么win api的函敎ͼx有那些修饰符Q但又可以让delphi、C、VB{正调用呢Q奇怪啊?br />
今天写线E函数时Q发现msdn中对ThreadProc的定义有要求QDWORD WINAPI ThreadProc(LPVOID lpParameter);
不解Z么要用WINAPI宏定义,查了后发C面的定义。于是乎需要区别__stdcall和__cdecl两者的区别Q?#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
#define cdecl _cdecl
#ifndef CDECL
#define CDECL _cdecl
#endif
几乎我们写的每一个WINDOWS API函数都是__stdcallcd的,首先Q需要了解两者之间的区别Q?WINDOWS的函数调用时需要用到栈QSTACKQ一U先入后出的存储l构Q。当函数调用完成后,栈需要清楚,q里是问题的关键,如何清除Q?如果我们的函C用了_cdeclQ那么栈的清除工作是p用者,用COM的术语来讲就是客h完成的。这样带来了一个棘手的问题Q不同的~译器生栈的方式不相同,那么调用者能否正常的完成清除工作呢?{案是不能。如果用__stdcallQ上面的问题p决了Q函数自px除工作。所以,在跨Q开发)q_的调用中Q我们都使用__stdcallQ虽然有时是?WINAPI的样子出玎ͼ。那么ؓ什么还需要_cdecl呢?当我们遇到这L函数如fprintf()它的参数是可变的Q不定长的,被调用者事先无法知道参数的长度Q事后的清除工作也无法正常的q行Q因此,q种情况我们只能使用_cdecl。到q里我们有一个结论,如果你的E序中没有涉及可变参敎ͼ最好用__stdcall关键字?
2.
__cdecl,__stdcall是声明的函数调用协议.主要是传参和Ҏ斚w的不?一般c++用的是__cdecl,windows里大都用的是__stdcall(API)
__cdecl 是C/C++和MFCE序默认使用的调用约定,也可以在函数声明时加上__cdecl关键字来手工指定。采用__cdeclU定Ӟ函数参数按照从右到左的顺序入栈,q且p用函数者把参数弹出栈以清理堆栈。因此,实现可变参数的函数只能用该调用U定。由于每一个用__cdeclU定的函数都要包含清理堆栈的代码Q所以生的可执行文件大会比较大。__cdecl可以写成_cdecl?
__stdcall调用U定用于调用Win32 API函数。采用__stdcallU定Ӟ函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定。由于函C本n知道传进来的参数个数Q因此被调用的函数可以在q回前用一条ret n指o直接清理传递参数的堆栈。__stdcall可以写成_stdcall?
__fastcall U定用于Ҏ能要求非常高的场合。__fastcallU定函数的从左边开始的两个大小不大?个字节(DWORDQ的参数分别攑֜ECX和EDX寄存器,其余的参C旧自叛_左压栈传送,被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall
3.
__stdcall:
_stdcall 调用U定相当?6位动态库中经怋用的PASCAL调用U定?
?2位的VC++5.0中PASCAL调用U定不再被支持(实际上它已被定义?__stdcall。除了__pascal外,__fortran和__syscall也不被支持)Q取而代之的是__stdcall调用U定。两者实质上是一致的Q即函数的参数自叛_左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明Q?
_stdcall是PascalE序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC函数编译后会在函数名前面加上下划线前缀Q在函数名后加上"@"和参数的字节数?
_cdecl:
_cdecl c调用U定, 按从双左的序压参数入栈,p用者把参数弹出栈。对于传送参数的内存栈是p用者来l护的(正因为如此,实现可变参数的函数只能用该调用U定Q。另外,在函数名修饰U定斚w也有所不同?
_cdecl是C和CQ+E序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以生的可执行文件大会比调用_stdcall函数的大。函数采用从叛_左的压栈方式。VC函数编译后会在函数名前面加上下划线前缀。是MFC~省调用U定?
__fastcall:
__fastcall调用U定??如其名,它的主要特点是快,因ؓ它是通过寄存器来传送参数的Q实际上Q它用ECX和EDX传送前两个双字QDWORDQ或更小的参敎ͼ剩下的参C旧自叛_左压栈传送,被调用的函数在返回前清理传送参数的内存栈)Q在函数名修饰约定方面,它和前两者均不同?
_fastcall方式的函数采用寄存器传递参敎ͼVC函数编译后会在函数名前面加?@"前缀Q在函数名后加上"@"和参数的字节数?
thiscall:
thiscall仅仅应用?C++"成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定?
naked call:
采用1-4的调用约定时Q如果必要的话,q入函数时编译器会生代码来保存ESIQEDIQEBXQEBP寄存器,退出函数时则生代码恢复这些寄存器的内宏V?
naked call不生这L代码。naked call不是cd修饰W,故必d_declspec共同使用?
另附:
关键?__stdcall、__cdecl和__fastcall可以直接加在要输出的函数前,也可以在~译环境的Setting...\C/C++ \Code Generationw择。当加在输出函数前的关键字与~译环境中的选择不同Ӟ直接加在输出函数前的关键字有效。它们对应的命o行参数分别ؓ/Gz?/Gd?Gr。缺省状态ؓ/GdQ即__cdecl?
要完全模仿PASCAL调用U定首先必须使用__stdcall调用U定Q至于函数名修饰U定Q可以通过其它Ҏ模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏Q它可以出函数译成适当的调用约定,在WIN32中,它被定义为__stdcall。用WINAPI宏可以创qAPIs?
名字修饰U定
1、修饰名(Decoration name)
“C” 或?#8220;C++”函数在内部(~译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字W串。有些情况下使用函数的修饰名是必要的Q如在模块定义文仉头指定输?#8220;C++”重蝲函数、构造函数、析构函敎ͼ又如在汇~代码里调用“C””?#8220;C++”函数{?
修饰名由函数名、类名、调用约定、返回类型、参数等共同军_?
2、名字修饰约定随调用U定和编译种c?C或C++)的不同而变化。函数名修饰U定随编译种cd调用U定的不同而不同,下面分别说明?
a、C~译时函数名修饰U定规则Q?
__stdcall调用U定在输出函数名前加上一个下划线前缀Q后面加上一?#8220;@”W号和其参数的字节数Q格式ؓ_functionname@number?
__cdecl调用U定仅在输出函数名前加上一个下划线前缀Q格式ؓ_functionname?
__fastcall调用U定在输出函数名前加上一?#8220;@”W号Q后面也是一?#8220;@”W号和其参数的字节数Q格式ؓ@functionname@number?
它们均不改变输出函数名中的字W大写Q这和PASCAL调用U定不同QPASCALU定输出的函数名无Q何修C全部大写?
b、C++~译时函数名修饰U定规则Q?
__stdcall调用U定Q?
1、以“?”标识函数名的开始,后跟函数名;
2、函数名后面?#8220;@@YG”标识参数表的开始,后跟参数表;
3、参数表以代可C:
X--void Q?
D--charQ?
E--unsigned charQ?
F--shortQ?
H--intQ?
I--unsigned intQ?
J--longQ?
K--unsigned longQ?
M--floatQ?
N--doubleQ?
_N--boolQ?
....
PA--表示指针Q后面的代号表明指针cdQ如果相同类型的指针q箋出现Q以“0”代替Q一?#8220;0”代表一ơ重复;
4、参数表的第一ؓ该函数的q回值类型,其后依次为参数的数据cd,指针标识在其所指数据类型前Q?
5、参数表后以“@Z”标识整个名字的结束,如果该函数无参数Q则?#8220;Z”标识l束?
其格式ؓ“?functionname@@YG*****@Z”?#8220;?functionname@@YG*XZ”Q例?
int Test1Qchar *var1,unsigned longQ?----“?Test1@@YGHPADK@Z”
void Test2Q) -----“?Test2@@YGXXZ”
__cdecl调用U定Q?
规则同上面的_stdcall调用U定Q只是参数表的开始标识由上面?#8220;@@YG”变ؓ“@@YA”?
__fastcall调用U定Q?
规则同上面的_stdcall调用U定Q只是参数表的开始标识由上面?#8220;@@YG”变ؓ“@@YI”?
VC++对函数的省缺声明?#8220;__cedcl“,只能被C/C++调用.
CB在输出函数声明时使用4U修饰符?
//__cdecl
cb的默认|它会在输出函数名前加_Qƈ保留此函数名不变Q参数按照从叛_左的序依次传递给栈,也可以写成_cdecl和cdecl形式?
//__fastcall
她修饰的函数的参数将肯呢感C用寄存器来处理,其函数名前加@Q参数按照从左到右的序压栈Q?
//__pascal
它说明的函数名用Pascal格式的命名约定。这时函数名全部大写。参数按照从左到右的序压栈Q?
//__stdcall
使用标准U定的函数名。函数名不会改变。用__stdcall修饰时。参数按照由叛_左的序压栈Q也可以是_stdcallQ?
VC++对函数的省缺声明?__cedcl",只能被C/C++调用.
注意Q?
1、_beginthread需要__cdecl的线E函数地址Q_beginthreadex和CreateThread需要__stdcall的线E函数地址?
2、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定义:
#define CALLBACK __stdcall
#define WINAPI __stdcall
3、extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);
typedef int (__cdecl*FunPointer)(int a, int b);
修饰W的书写序如上?
4、extern "C"的作用:如果Add(int a, int b)是在c语言~译器编译,而在c++文g使用Q则需要在c++文g中声明:extern "C" Add(int a, int b)Q因为c~译器和c++~译器对函数名的解释不一Pc++~译器解释函数名的时候要考虑函数参数Q这h了方便函数重载,而在c语言中不存在函数重蝲的问题)Q用extern "C"Q实质就是告诉c++~译器,该函数是c库里面的函数。如果不使用extern "C"则会出现链接错误?
一般象如下使用Q?
#ifdef _cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C extern
#endif
#ifdef _cplusplus
extern "C"{
#endif
EXTERN_C int func(int a, int b);
#ifdef _cplusplus
}
#endif
5、MFC提供了一些宏Q可以用AFX_EXT_CLASS来代替__declspec(DLLexport)Qƈ修饰cdQ从而导出类QAFX_API_EXPORT来修饰函敎ͼAFX_DATA_EXPORT来修饰变?
AFX_CLASS_IMPORTQ__declspec(DLLexport)
AFX_API_IMPORTQ__declspec(DLLexport)
AFX_DATA_IMPORTQ__declspec(DLLexport)
AFX_CLASS_EXPORTQ__declspec(DLLexport)
AFX_API_EXPORTQ__declspec(DLLexport)
AFX_DATA_EXPORTQ__declspec(DLLexport)
AFX_EXT_CLASSQ?ifdef _AFXEXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
6、DLLMain负责初始?Initialization)和结?(Termination)工作Q每当一个新的进E或者该q程的新的线E访问DLLӞ或者访问DLL的每一个进E或者线E不再用DLL或者结束时Q都会调用DLLMain。但是,使用TerminateProcess或TerminateThreadl束q程或者线E,不会调用DLLMain?
7、一个DLL在内存中只有一个实?
DLLE序和调用其输出函数的程序的关系Q?
1)、DLL与进E、线E之间的关系
DLL模块被映到调用它的q程的虚拟地址I间?
DLL使用的内存从调用q程的虚拟地址I间分配Q只能被该进E的U程所讉K?
DLL的句柄可以被调用q程使用Q调用进E的句柄可以被DLL使用?
DLLDLL可以有自q数据D,但没有自q堆栈Q用调用进E的栈,与调用它的应用程序相同的堆栈模式?
2)、关于共享数据段
DLL定义的全局变量可以被调用进E访问;DLL可以讉K调用q程的全局数据。用同一 DLL的每一个进E都有自qDLL全局变量实例。如果多个线Eƈ发访问同一变量Q则需要用同步机Ӟ对一个DLL的变量,如果希望每个使用DLL的线E都有自q|则应该用线E局部存?TLSQThread Local Strorage)?
]]>
/* file TestC.h */
#ifndef TESTC_H
#define TESTC_H
#ifdef __cplusplus
extern "C" {
#endif
int add(int a, int b);
#ifdef __cplusplus
}
#endif
#endif /* TESTC_H */
/* file TestC.c */
#include "TestC.h"
int add(int a, int b)
{
return (a + b);
}
/* file TestCpp.cpp */
#include "stdio.h"
#include "TestC.h"
int main()
{
printf("add = %d\n", add(2, 5));
return 0;
}
说明Q?br />file TestC.h是C的头文gQfile TestC.c是其实现文gQfile TestCpp.cpp是调用C函数的C++文g?br />文gTestC.h中的TESTC_H定义是ؓ了头文g保护Q?#8221; #ifdef __cplusplus”q个不能~少Q你可以L看C的标准库头文件中都有q个Q如”stdio.h”。有了这个宏~译器就知道现在是Cq是C++在调用它?br />Z么要区分C与C++调用呢?其深层次原因是因为C和C++~译器在~译和链接时对于函数的处理不一栗C++Z支持函数重蝲在编译时会加入函数参数及cd信息。如上面的addҎQC~译器编译后在符号库中的名字为_addQ而C++~译器则会生像_add_int_int之类的名字。C++正是依靠q种机制实现了函数的重蝲?br />extern关键字表C将函数或变量声明ؓ全局cdQ与之相对应的是static。static限定函数或变量的作用域ؓ本文件。externq有一个作用就是与”C”q在一起用,即extern “C”通知~译器将extern “C”所包含的代码按照C的方式编译和链接?br />
下面我们来看看如何在C语言中用C++的代码(包括C++cȝҎQ。ؓ了简单v见,我将cȝ定义和实现放在一个文件中(通常应该是将分别攑֜.h?cpp文g?。自定义cL?q里省略了头文g保护{其它细?如下Q?br />//* file TestClass.h */
class HJH
{
public:
int add(int a, int b)
{
return (a + b);
}
};
C++cd装ؓC函数的文ӞZ略也声明和实现攑֜了同一个文件中Q如下:
/* file TestCpp.cpp */
#include "TestClass.h"
extern "C" int add_cpp(int a, int b);
int add_cpp(int a, int b)
{
HJH hjh;
return hjh.add(a, b);
}
实际调用C++代码的C文g如下Q?br />/*file TestC.c */
#include "stdio.h"
extern int add_cpp(int a, int b);
int main()
{
printf("add_cpp = %d\n", add_cpp(2, 5));
return 0;
}
上面的过E很清晰Q就是用一个函数将C++cȝ使用装hQ然后将它外部声明ؓC函数可以了?br />文gTestClass.h定义q实C一个类Q该cd有一个addҎ。文件TestCpp.cpp定义q实C一个函数add_cppQ函C定义了一个HJHcd象ƈ调用了该对象的addҎ。然后将add_cpp函数q行外部声明为C?br />TestC.c文g中ؓ了用add_cpp函数Q也需要进行外部声明。这是ؓ了通知~译器说明这个函数是在其他文件中实现Q注意在C文g中的extern后面不可?#8221;C”Q。当q三个文件一L译链接时Q编译器可以找到add_cpp的具体实现?/p>
]]>
q三个函数都是得到语aIDQ但是用的场合上却是有所不同Q下面先l出三个函数的原型和解释Q?br />GetUserDefaultLangID
Returns the language identifier for the current user locale.
LANGID GetUserDefaultLangID(void);
Parameters
This function has no parameters.
Return Values
Returns the language identifier for the current user locale.
GetSystemDefaultLangID
Returns the language identifier for the system locale.
LANGID GetSystemDefaultLangID(void);
Parameters
This function has no parameters.
Return Values
Returns the language identifier for the system locale.
GetUserDefaultUILanguage
Retrieves the user UI language for the current user. If the current user has not set a language, GetUserDefaultUILanguage returns the language identifier for the system default UI language.
LANGID GetUserDefaultUILanguage(void);
Parameters
This function has no parameters.
Return Value
Returns the language identifier for the user UI language for the current user.
要理解这三个函数的用先要看看控刉?>区域和语a选项Q?br />W一代表数字货币时_W二|pȝUI和Y件界面,W三高U是非unicodeE序界面语言?br />下面是这三个面讄Ӟ不同的函数的l果Q?br /> 区域选项 语言 高
GetUserDefaultLangID 改变 不变 不变
GetSystemDefaultLangID 不变 不变 改变
GetUserDefaultUILanguage 改变 改变 不变
׃可见QGetUserDefaultUILanguage会和pȝUI的语a一_所以我们徏议在写UNICODEE序界面时用q个
函数会好些,和OS的UI一_不会让客得很奇怪?/p>
本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/nanjian011/archive/2009/10/23/4716903.aspx
http://blog.sina.com.cn/s/blog_48f93b530100fsln.html
?span>Windowsq_下用C++开发应用程序,最不想见到的情冉|怕就是程序崩溃,而要惌军_起问题的bugQ最困难的应该就是调?span>release版本了。因?span>release版本来就了很多调试信息Q更何况一般都是发布出ȝ用户使用Q?span>crash的现场很难保留和重现。本文将l出几个解决ҎQ完成对release版应用程?span>crash错误的调试。(本文只讨?span>Windowsq_MSVC环境下的调试Q对于其他^台和开发环境没有关注,请大家自己借鉴和尝试。)
上篇l出的方案一q要补充几句。通过“crash地址 + MAP文g”来定位出错代码位|虽焉要经q比较复杂的地址计算Q但却是最单实现的方式。如果仅仅想通过崩溃地址定位出错的函敎ͼ更加方便了。我在网上找C个解?span>MAP文g的小工具Q可以非常清晰的列出每个函数的地址Qƈ且可以将分析表格导出?span>Excel文g。工具下载地址Q?span>http://e.ys168.com/?tinyfunQ工L录下VCMapper.exe?/span>
Ҏ前面两篇博文Q我们要定位崩溃行代码,必须要自己根据相关信息文件进行计。如果需要处理的量比较大Q恐怕会很费力气。有没有更简单快速的办法呢?
前面几个Ҏ都是直接定位crash的代码位|,但是在比较大型的E序中,只知道这个信息还是远q不够的Q我们希望知道更多关于调用函数顺序及变量值等信息Q也是crash时调用堆栈信息?/span>
当我们把自己?span>release版本E序发布出去以后Q一般都是在用户的机器上q行。这U情况下Q对于第四种ҎQ因为需?span>pdb文g才能够正生成堆栈调用的函数行号及代码行P因此Ҏ四只适用于本?span>release版的调试Q否则只能生成不完整的堆栈信息。对于前三种ҎQ其实只需要用户告知崩溃地址Q然后在本地查找crash地址可以了Q但是定?span>crash的过E非怸方便Q如?span>crash的情冉|较多Q前三种Ҏ都不合适。而且Q前三种Ҏ均不能生成堆栈调用信息,对于debug的作用有限?/span>
上一我们已l给ZҎQ能够非常方便的通过dump文g?span>crash错误q行调试和定位;从整个流E上看还差最后一步,x样拿到crash时生的dump文g。如果可以让用户把文件发送过来自然不错,但对于类似免费共享Y件等在互联网上发布的E序呢?我们的用h不确定的Q而且用户量有可能非常大,即我们能想办法联系到用PM能挨个去攉crash信息吧?/span>
1 概述
1服务E序遵@Service Control Manager(SCM)的接口规则?/span> 启动方式?/span>3U:1 pȝ启动时自动启动;2 通过服务控制面板;3 通过使用服务函数?/span>2服务可以在没有用L录到pȝ的情况下q行?/span>
2 驱动服务Q?/span>驱动服务遵@讑֤驱动协议Q它与服务程序类|但是不与SCM交互?/span>
3 The SCM processes service control notifications in a serial fashion
4 The default security descriptor allows the LocalSystem account, and members of the Administrators and Power Users groups to stop and start services.
2 服务?/span>Win7中的新特?/span>
1 可以注册一个服务,在一个特定事件发生时启动或者停止服务?/span>
更新的函?/span>
Changes the configuration parameters of a service. This function supports managed service accounts and virtual accounts. For more information, see Service Accounts Step-by-Step Guide. |
|
Changes the optional configuration parameters of a service. This function supports new configuration information levels for processor groups and service trigger events. |
|
Creates a service object and adds it to the specified service control manager database. This function supports managed service accounts and virtual accounts. For more information, see Service Accounts Step-by-Step Guide. |
|
An application-defined callback function used with the RegisterServiceCtrlHandlerEx function. This callback function supports new extended control codes for system time changes and service trigger events. |
|
Retrieves the optional configuration parameters of a service. This function supports new configuration information levels for processor groups and service trigger events. |
|
Updates the service control manager's status information for the calling service. This function supports new extended control codes for system time changes and service trigger events. |
新的l构
Contains system time change settings. |
|
Represents a service trigger event. |
|
Contains trigger event information for a service. |
|
Contains trigger-specific data for a service trigger event. |
Service Changes for Windows
Z提高性能、可靠性、安全性、可理性等Q在Vista之后Q服务提供了很多增强的特性?/span>
Session 0 Isolation
服务Lq行?/span>Session 0Q在Vista之前Q第一个用户也是运行在Session0Q在Vista之后Q第一个用戯行在Session1Q第二个用户q行?/span>Session2Q?/span> {等Q这LL序和服务始l运行在不同?/span>Session上?/span>Session 0 不支持与用户交互的进E。这意味着Service不能向应用程序发送消息,应用E序也不能像Service发送消息。除此之外,Serviceq不能显C?/span>GUIQ如对话框,但是Service可以使用WTSSendMessage 函数在另一?/span>Session中显C对话框?/span>
Delayed AutoStart 在系l启动的一段旉Q?/span>shortly afterQ内启动服务?/span>
Failure Detection and RecoveryQ如果服务失败,SCM可以执行p|actionQ如重启服务?/span>
Preshutdown NotificationsQ是服务有够的旉来优雅的关闭?/span>
Restricted Network AcessQ?/span>
Running with Least PrivilageQ?/span>
关于服务
SCMl护者已安装的服务和驱动服务的数据库Qƈ且提供了l一的安全的控制它们的方法。数据库信息包括每个服务如何被启动?/span>
下面cd的程序?/span>SCM提供的函敎ͼ
服务E序Q?/span>Service ProgramQ:一个ؓ一个或者多个服务提供可执行代码的程序。服务程序用连接到SCM的函C及发送状态信息给SCM的函数?/span>
服务配置E序Q?/span>Service configuration programQ:一个查询和修改服务数据库的E序。服务配|程序用打开数据库的函数Q在数据库中安装和删除服务,对安装的服务查询和修攚w|参敎ͼ安全参数。服务配|程序管理服务以及驱动服务?/span>
服务控制E序Q?/span>Service Control ProgramQ:一个启动ƈ控制服务以及驱动服务的程序。服务控制程序用发送请求到SCM的函数?/span>
Service Control Manager(服务控制理?/span>)
1 pȝ启动时启动;它是一?/span>RPC (Remote procedure call) 服务器。所以服务配|和服务控制E序可以在远E机器上理服务?/span>
提供接口功能Q?/span>
理安装服务的数据库Q?/span>
启动服务Q系l启动时Q或者需要时Q?/span>
枚D安装的服?/span>
行的服务l护状态信?/span>
传输控制hQ?/span>control requestQ给正在q行的服?/span>
锁定和解锁服务数据库?/span>
Database of installed service
在注册表中维护着已安装的服务列表。注册表如下
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services.
q个键gؓ每一个安装的服务包含一个子键,子键的名字就是服务名?/span>
Database又叫?/span>ServiceActive databse或?/span>SCM database。你必须使用SCM提供的函敎ͼ而不能直接修?/span>databseQ即不能直接修改注册表?/span>
自动启动服务
在系l启动的时?/span>,SCM启动所有自启动的服务以及它们依赖的服务?/span>
启动序Q?/span>
1 按加载启动组列表中的序Q这个信息保存在ServiceGroupOrdergQ?/span>
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control
2 GroupOrderList
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control
3 每个服务的依赖列?/span>
当启动完成后Q系l执行启动认证程序(boot verification programQ根?/span>BootVerificationProgram
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control.
默认情况下,q个值是没有讄的。你可以提供E序来检系l,调用NotifyBootConfigStatus通知SCMQ报?/span>boot 状态?/span>
在成功重启后Q系l会组册表复制一份到下面目录?/span>last-known-good (LKG) configuration
HKEY_LOCAL_MACHINE\SYSTEM\ControlSetXXX\Services
按需启动服务
当服务启动之后,SCM执行下列步骤Q?/span>
获取存储在数据库中的帐户信息
d到服务帐?/span>
加蝲用户配置
在暂停状态下创徏服务
l进E分配一?/span>Logon?/span>token
允许q程执行
Service Record List
׃每个服务实体是从注册表数据库中读取的Q因?/span>SCM为每个服务创Z一?/span>Service RecordQ?/span>
一?/span>Service Record包括Q?/span>
服务?/span>
启动cd
服务状态(SERVICE_STATUSl构Q?/span>
指向依赖服务列表的指?/span>
服务在安装的时候用户名和密码是指定好的?/span>SCM在注册表中存攄户名Q在Local Security AuthorityQ?/span>LSAQ中存放密码?/span>
SCM保存两䆾用户密码。一个当前密码和一个备份密码。在服务W一ơ安装的时候用当前密码,备䆾密码是未初始化的。当用当前密码运行服务成功后Q才会将当前密码写入到备份密码?/span>SCM在收到服务状态通知后更新服务状态?/span>
驱动服务的状态是通过查询IOpȝ获得的,而不是通过状态通知Q这Ҏ?/span>Service不同的?/span>
SCM Handles
SCM支持句柄Q?/span>HandleQ来讉K下面的对象?/span>
已安装的服务的数据库Q用SCManager object来表C?/span>
服务Q由一个安装的服务代表服务对象?/span>
数据库锁
Service Programs服务E序
Main函数Q?/span>
调用StartServiceCtrlDispatcher函数q接?/span>SCMq启?/span>control dispatcherU程?/span>Control dispatcherU程循环loopQ等待在dispatch table中指定的服务的请求?/span>
例子Q?/span>
void __cdecl _tmain(int argc, TCHAR *argv[])
{
// If command-line parameter is "install", install the service.
// Otherwise, the service is probably being started by the SCM.
if( lstrcmpi( argv[1], TEXT("install")) == 0 )
{
SvcInstall();
return;
}
// TO_DO: Add any additional services for the process to this table.
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{ SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain },
{ NULL, NULL }
};
// This call returns when the service has stopped.
// The process should simply terminate when the call returns.
if (!StartServiceCtrlDispatcher( DispatchTable ))
{
SvcReportEvent(TEXT("StartServiceCtrlDispatcher"));
}
}
ServiceMain函数Q?/span>
首先调用RegisterServiceCtrlHandler函数来注?/span>SvcCtrHandler函数作ؓ服务的处理函敎ͼ然后开始初始化?/span>
VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv )
{
// Register the handler function for the service
gSvcStatusHandle = RegisterServiceCtrlHandler(
SVCNAME,
SvcCtrlHandler);
if( !gSvcStatusHandle )
{
SvcReportEvent(TEXT("RegisterServiceCtrlHandler"));
return;
}
// These SERVICE_STATUS members remain as set here
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
gSvcStatus.dwServiceSpecificExitCode = 0;
// Report initial status to the SCM
ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 );
// Perform service-specific initialization and work.
SvcInit( dwArgc, lpszArgv );
}
//
// Purpose:
// The service code
//
// Parameters:
// dwArgc - Number of arguments in the lpszArgv array
// lpszArgv - Array of strings. The first string is the name of
// the service and subsequent strings are passed by the process
// that called the StartService function to start the service.
//
// Return value:
// None
//
VOID SvcInit( DWORD dwArgc, LPTSTR *lpszArgv)
{
// TO_DO: Declare and set any required variables.
// Be sure to periodically call ReportSvcStatus() with
// SERVICE_START_PENDING. If initialization fails, call
// ReportSvcStatus with SERVICE_STOPPED.
// Create an event. The control handler function, SvcCtrlHandler,
// signals this event when it receives the stop control code.
ghSvcStopEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name
if ( ghSvcStopEvent == NULL)
{
ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
return;
}
// Report running status when initialization is complete.
ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );
// TO_DO: Perform work until service stops.
while(1)
{
// Check whether to stop the service.
WaitForSingleObject(ghSvcStopEvent, INFINITE);
ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
return;
}
}
//
// Purpose:
// Sets the current service status and reports it to the SCM.
//
// Parameters:
// dwCurrentState - The current state (see SERVICE_STATUS)
// dwWin32ExitCode - The system error code
// dwWaitHint - Estimated time for pending operation,
// in milliseconds
//
// Return value:
// None
//
VOID ReportSvcStatus( DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
// Fill in the SERVICE_STATUS structure.
gSvcStatus.dwCurrentState = dwCurrentState;
gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
gSvcStatus.dwWaitHint = dwWaitHint;
if (dwCurrentState == SERVICE_START_PENDING)
gSvcStatus.dwControlsAccepted = 0;
else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
if ( (dwCurrentState == SERVICE_RUNNING) ||
(dwCurrentState == SERVICE_STOPPED) )
gSvcStatus.dwCheckPoint = 0;
else gSvcStatus.dwCheckPoint = dwCheckPoint++;
// Report the status of the service to the SCM.
SetServiceStatus( gSvcStatusHandle, &gSvcStatus );
}
Control Handler函数Q?/span>
该函数是?/span>dispatcher thread调用的,它处理在OpCode参数中传q来的控制码Q然后调?/span>ReportSvcStatus函数来更新服务状态。当Handler收到控制码时Q只有当收到的控制码引v服务状态变化时才报告服务状态?/span>
VOID WINAPI SvcCtrlHandler( DWORD dwCtrl )
{
// Handle the requested control code.
switch(dwCtrl)
{
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
// Signal the service to stop.
SetEvent(ghSvcStopEvent);
ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
return;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
}
Service Configuration Program Tasks服务配置E序的Q?/span>
安装服务
删除服务
改变服务配置
查询服务配置
Service Control Program Tasks服务控制E序d
启动服务
停止服务
改变一个服务的DACL
Service Functions
The following functions are used or implemented by services.
Function |
Description |
An application-defined callback function used with the RegisterServiceCtrlHandler function. |
|
An application-defined callback function used with the RegisterServiceCtrlHandlerEx function. |
|
Registers a function to handle service control requests. |
|
Registers a function to handle extended service control requests. |
|
An application-defined function that serves as the starting point for a service. |
|
Registers a service type with the service control manager and the Server service. |
|
Updates the service control manager's status information for the calling service. |
|
Connects the main thread of a service process to the service control manager. |
The following functions are used by programs that control or configure services.
Function |
Description |
Changes the configuration parameters of a service. |
|
Changes the optional configuration parameters of a service. |
|
Closes the specified handle to a service control manager object or a service object. |
|
Sends a control code to a service. |
|
Sends a control code to a service. |
|
Creates a service object and adds it to the specified service control manager database. |
|
Marks the specified service for deletion from the service control manager database. |
|
Retrieves the name and status of each service that depends on the specified service. |
|
Enumerates services in the specified service control manager database based on the specified information level. |
|
Retrieves the display name of the specified service. |
|
Retrieves the service name of the specified service. |
|
Reports the boot status to the service control manager. |
|
Enables an application to receive notification when the specified service is created or deleted or when its status changes. |
|
Establishes a connection to the service control manager on the specified computer and opens the specified service control manager database. |
|
Opens an existing service. |
|
Retrieves the configuration parameters of the specified service. |
|
Retrieves the optional configuration parameters of the specified service. |
|
Retrieves a copy of the security descriptor associated with a service object. |
|
Retrieves the current status of the specified service based on the specified information level. |
|
Sets the security descriptor of a service object. |
|
Starts a service. |
Service Structures
The following structures are used with services:
?/strong> ?/strong> ?/strong> ?/strong>
版本?/strong> 日期 修改?/strong> 说明 备注 0.1 2010.07.13 phoenix 0.2 2011.01.12 phoenix
??/p>
当开发h员开始进行编码工作以及编码工作的q程中,都需要根据工E的需要配|各U工作\径,如引入第三方库、输出生成的头文件和库文件等。以前部分开发h员对工程路径的定义比较随意,相当一部分使用的是l对路径。如果只是单个开发h员完成一个单一的功能,q种做法看v来没有什么明昄~点。但是,如果是数个开发h员来合作共同完成一个功能和l构都很复杂的工E时Q这U做法的弊端凸昑և来。一个非常明昄问题是Q开发h员A他的工E交l开发h员B~译Ӟ如工E定义用了l对路径Q除非开发h员B的计机与A的计机h完全相同的文件组l结构,否则一ơ性编译通过是不可能。另外,当开发h员在一长串?#8220;Can’t find …”或?#8220;XXX undefined”的错误与警告中焦头烂额时Q也许只是因Z个不L的地方用了l对路径Q或者是大小写的差异?/p>
因此Q规范开发h员的工程配置是很有必要的。通过规范的工E配|,所有开发h员之间的工程是无~衔接的Q即M一个开发h员的工程拿到其他人的工程Ӟ无须修改M一个配|便可以一ơ性的~译成功。这L规范操作无异可以大大减少团队成员通过代码交流时的不必要成本。另外,规范的工E配|也便于SubVersion版本控制pȝ的引入,通过引入SubVersion版本控制pȝQ可以协调整个研发团队的工程q度Q控制品的里程发布。这一切,都从规范的工E配|开始?/p>
1. 《OpenSource SubVersion规范?/p>
2. 《Visual Studio 2005~程指南?/p>
MyDevelopeFolder
├─Bin
?├─Debug
?├─Program
?├─Release
?├─UnicodeDebug
?├─UnicodeProgram
?└─UnicodeRelease
├─MySDK
?├─Include
??├─BCG
??└─Boost
?└─Lib
?├─Debug
??├─BCG
??└─Boost
?├─Program
??├─BCG
??└─Boost
?├─Release
??├─BCG
??└─Boost
?├─UnicodeDebug
??├─BCG
??└─Boost
?├─UnicodeProgram
??├─BCG
??└─Boost
?└─UnicodeRelease
?├─BCG
?└─Boost
├─Project
?├─LibMyExample
??└─Document
?└─MyExampleApp
?└─Document
├─Solution
└─Temp
├─Compile
└─Link
1. MyDevelopeFolder是开发工E的根目?/p>
2. Bin目录存放所有动态链接库和可执行E序Q包括自q产出和第三方库,按编译配|名U包括对应的子目录,如Debug、Release。另外,E序q行q程中需要外部的数据文g和启动时需要的配置文g{等都可放于该目?/p>
3. MySDK存放产品目依赖、?/strong>?h文g?lib文g。其中Lib目录下根据配|名U和W三方库名称对lib文gq行理。如某项目用BCG作ؓ界面库,则BCG的头文g攄?#8220;Include\BCG”下,不同版本的库文g|于“Lib\配置名\BCG”下;目输出的lib文g直接位于“Lib\配置?#8221;下。这样品依赖的不同开发库所使用头文件与库文件和输出的头文g与库文g怺之间是独立、彼此不q涉的?/p>
4. Project是工E目录,用于存放代码Q按模块名组l次U目录。功能库工程一般的?#8220;Lib”开_以便与其它动态库区别。每个工E模块目录应包含“Document”子目录,该目录下按需要增?#8220;DBM”?#8220;DOC”?#8220;UML”三个子目录?#8220;DBM”用于存放与该模块相关的数据库设计文档Q通常是PowerDesigner文档Q?#8220;DOC”用于存放与该模块相关的一般性说明文档;“UML”用于存放与该模块相关的UML设计文档Q通常是Rational Rose文档?/p>
5. Solution是解x案目录,用于存放产品的完整解x案。通常使用解决Ҏ可以生成该品的所有版本?/p>
6. Temp是用于编译生成的中间目录Q主要存攄译过E中生成的各U中间文件?/p>
Ҏ调试信息与字W集的不同,一般的工程配置有六c,如下表所C?
名称 字符?/p>
是否包含调试信息 是否包含代码优化 Debug ANSI YES NO Release ANSI YES YES Program ANSI NO YES UnicodeDebug UNICODE YES NO UnicodeRelease UNICODE YES YES UnicodeProgram UNICODE NO YES3.工程配置
输出目录Q?..\..\Temp\Link\$(ProjectName)\$(ConfigurationName)
中间目录Q?.\..\Temp\Compile\$(ProjectName)\$(ConfigurationName)
如果需要启动本模块q行调试Q则“命o”为:..\..\Bin\$(ConfigurationName)\$(TargetFileName)
1) “附加包含目录”Q?.\..\MineSDK\Include Q如有其它目录请?#8220;;”间隔Q注意用相对\径,严禁使用l对路径?/strong>
2) 动态库使用导出宏导?导入Ӟ应在“预处理器”中定义导出宏Q不得在代码文g中定义。导出宏的一般格式ؓ“LIB_XXXXX”?/p>
3) 一般情况下不推荐用预~译头?/p>
1) “输出文g”Q?/p>
a) 若ؓDebug版时Q?(OutDir)\$(ProjectName)D.dllQ?/p>
b) 若ؓRelease版时Q?(OutDir)\$(ProjectName).dll?/p>
2) “附加库目?#8221;Q?.\..\Bin\$(ConfigurationName);..\..\MySDK\LibQ如有其它目录请?#8220;;”间隔Q注意用相对\径,严格止使用本地l对路径?/p>
3) “模块定义文g”Q一般不使用模块定义.def文gQ本可选择“从默认配|?#8230;”?/p>
一般需要配|的?#8220;生成后事?#8221;。Q何一个工E模块的生成后事仉应包括以下内容:
1) copy $(TargetPath) ..\..\Bin\$(ConfigurationName)
2) 若有导出的Lib库文Ӟ需要增加:copy $(TargetDir)$(TargetName).lib ..\..\MySDK\Lib
3) 若有导出的头文gQ需要增加: copy myhead.h ..\..\MySDK\Include
具体文g名与路径视情况而定Q但是严用绝对\径?/p>
ConfigurationName |
配置名字Q通常是Debug或者Release |
IntDir |
~译器用的中间目录Q出obj文g |
OutDir |
链接器用的输出目录 |
ProjectDir |
目目录 |
ProjectName |
目名字 |
SolutionDir |
解决Ҏ目录 |
TargetDir |
目标输出文g所在的目录 |
TargetExt |
目标输出的扩展名 |
TargetFileName |
目标输出文g名,包括扩展?/p> |
TargetName |
目标输出名,不包括扩展名 |
TargetPath |
目标输出文g的全路径?/p> |
|上关于q个问题讨论较多Q但也不外乎几种Ҏ。ȝ一下,如附中。顺便了解一个UAC?/span>
UACQ全UUser Account ControlQ用户帐hӞ
System Safe MonitorQ主机入侵防御系l)
UAC是如何工作的[3]
我们可以单的把UAC当作权限临时重分配的工具。在默认情况下,所有的非系l核心进E都只拥有标准权限,q一权限不能对系l关键区域进行修攏V对于一个程序,如果它当中含有提权申P则在q行时会弹出UACH口要求提权。如果用户允许,则程序暂时性的获得了最高权限,可以对系l关键区域进行更改;如果用户拒绝Q则E序被拒l执行。而如果程序中没有提权甌Q则pȝ会让E序q行于标准权限下。同Ӟ对于所有程序,都可以用“以管理员w䆾q行”的方式手动提权。而即便病毒感染了pȝQ它也处于UAC的监视之下,q得病毒的反清除行Z受到很大ȝ。正是凭借这一机制QUAC成ؓ了一道重要的pȝ防火墙?/span>
?Windows7QNT6.xpȝQ中Q系l取消了对移动设备Autorun.inf的支?/span>?/span>
用户界面Ҏ隔离[5]
在早期的Windows操作pȝ中,在同一用户下运行的所有进E有着相同的安全等U,拥有相同的权限。例如,一个进E可以自由地发送一个Windows消息到另外一个进E的H口。从Windows Vista开始,当然也包括Windows 7Q对于某些Windows消息Q这一方式再也行不通了。进E?或者其他的对象)开始拥有一个新的属性——特权等U?Privilege Level)。一个特权等U较低的q程不再可以向一个特权等U较高的q程发送消息,虽然他们在相同的用户权限下运行。这是所谓的用户界面Ҏ隔离 (User Interface Privilege IsolationQUIPI)?/span>
UIPI的引入,最大的目的是防止恶意代码发送消息给那些拥有较高权限的窗口以对其q行dQ从而获取较高的权限{等?/span>
UIPI的运行机?/span>
在Windows 7中,当UAC(User Account Control)启用的时候,UIPI的运行可以得到最明显的体现。在UAC中,当一个管理员用户dpȝ后,操作pȝ会创Z个o牌对?Token Object)Q第一个是理员o牌,拥有大多数特?cM于Windows Vista之前的System中的用户)Q而第二个是一个经q过滤后的简化版本,只拥有普通用L权限?/span>
默认情况下,以普通用h限启动的q程拥有普通特权等U?UIPI的等U划分ؓ低等U?low)Q普?normal)Q高{(high)Q系l?(system))。同LQ以理员权限运行的q程Q例如,用户右键单击选择“以管理员w䆾q行”或者是通过d“runas”参数调用 ShellExecuteq行的进E,q样的进E就相应地拥有一个较?high)的特权等U?/span>
q将Dpȝ会运行两U不同类型,不同Ҏ{的进E?当然Q从技术上讲这两个q程都是在同一用户?。我们可以用Windows Sysinternals工具集中的进E浏览器(Process Explorer)查看各个q程的特权等U。[6]
所以,当你发现你的q程之间Windows消息通信发生问题Ӟ不妨使用q程览器查看一下两个进E之间是否有合适的Ҏ{?/span>
UIPI所带来的限?/span>
正如我们前文所_{的划分,是ؓ了防止以下犯上。所以,有了用户界面Ҏ隔离Q一个运行在较低Ҏ{的应用程序的行ؓ受C诸多限制Q它不可以:
?/span>验证p高特权等U进E创建的H口句柄
?/span>通过调用SendMessage和PostMessage向由较高Ҏ{q程创徏的窗?/span>?/span>发送Windows消息
?/span>使用U程钩子处理较高Ҏ{q程
?/span>使用普通钩?SetWindowsHookEx)监视较高Ҏ{q程
?/span>向一个较高特权等U进E执行DLL注入
但是Q一些特DWindows消息是容许的。因些消息对q程的安全性没有太大媄响。这些Windows消息包括Q?/span>
0x000 - WM_NULL
0x003 - WM_MOVE
0x005 - WM_SIZE
0x00D - WM_GETTEXT
0x00E - WM_GETTEXTLENGTH
0x033 - WM_GETHOTKEY
0x07F - WM_GETICON
0x305 - WM_RENDERFORMAT
0x308 - WM_DRAWCLIPBOARD
0x30D - WM_CHANGECBCHAIN
0x31A - WM_THEMECHANGED
0x313, 0x31B (WM_???)
修复UIPI问题
ZWindows Vista之前的操作系l行为所设计的应用程序,可能希望Windows消息能够在进E之间自q传递,以完成一些特D的工作。当q些应用E序?Windows 7上运行时Q因为UIPI机制Q这U消息传递被L了,应用E序׃遇到兼容性问题。ؓ了解册个问题,Windows Vista引入了一个新的API函数ChangeWindowMessageFilter[7]。利用这个函敎ͼ我们可以d或者删除能够通过Ҏ{隔离?Windows消息。这像拥有较高Ҏ{的进E,讄了一个过滤器Q允讔R过的Windows消息都被d到这个过滤器的白名单Q只有在q个白名单上的消息才允许传递进来?/span>
如果我们惛_怸个消息可以发送给较高Ҏ{的进E,我们可以在较高特权等U的q程中调用ChangeWindowMessageFilter函数Q以 MSGFLT_ADD作ؓ参数消息添加进消息qo器的白名单。同LQ我们也可以以MSGFLT_REMOVE作ؓ参数这个消息从白名单中删除?/span>
一个示?/span>
对于pȝ消息的处理,接受消息的进E需要将该消息加入到白名单中Q可以通过下面的代码实玎ͼ
需要在高权限程序开始的地方加入以下代码,指定什么消息可以接?/span>
1 typedef BOOL (WINAPI *_ChangeWindowMessageFilter)( UINT , DWORD);
2
3 BOOL CVistaMsgRecvApp::AllowMeesageForVista(UINT uMessageID, BOOL bAllow)//注册Vista全局消息
4 {
5 BOOL bResult = FALSE;
6 HMODULE hUserMod = NULL;
7 //vista and later
8 hUserMod = LoadLibrary( L"user32.dll" );
9 if( NULL == hUserMod )
10 {
11 return FALSE;
12 }
13 _ChangeWindowMessageFilter pChangeWindowMessageFilter = (_ChangeWindowMessageFilter)GetProcAddress( hUserMod, "ChangeWindowMessageFilter" );
14 if( NULL == pChangeWindowMessageFilter )
15 {
16 AfxMessageBox(_T("create windowmessage filter failed"));
17 return FALSE;
18 }
19 bResult = pChangeWindowMessageFilter( uMessageID, bAllow ? 1 : 2 );//MSGFLT_ADD: 1, MSGFLT_REMOVE: 2
20 if( NULL != hUserMod )
21 {
22 FreeLibrary( hUserMod );
23 }
24 return bResult;
25 }
此时Q低{的进E就可以像高{的进E发送消息了?/span> ?Win7下如何让应用E序以管理员w䆾q行安装执行 1、法一Q?/span> runas /profile /env /user:mydomain\admin "mmc %windir%\system32\dsa.msc" 我感觉这U方法不靠谱?/span> 2、法二: 通过manifest文g使VC应用E序获得理员权?/span> q种Ҏq不错[1]?/span> <security> <requestedPrivileges> <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/> </requestedPrivileges> </security> 3、法三: 起名为setupQwin7会自己提升权限,增加manifest文g。还有一U方法是Ҏ册表。HKEY_CURRENT_USER\Software \Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers d一个字W串?名称是你的E序的\径和名字QgؓRUNASADMIN?/span> 也可以做成服务?/span> 4、其它方法: q有的网友说Q有个开源的目叫做RunAsQ就是用来以指定用户来运行程序的目Q可以参?/span> 参考网址和更多阅?/span> [1] http://hi.baidu.com/crowreturns/blog/item/f5e7cefd7546a284b801a07e.html [2] http://www.cnblogs.com/sun8134/archive/2009/10/30/1593025.html [3] 360 论坛 [4] http://topic.csdn.net/u/20100203/14/d98d8310-4971-47d1-94b1-9cdfbf159b4f.html [5] http://blog.csdn.net/jinhill/archive/2010/07/21/5752870.aspx [6] Sysinternals Utilities Index http://technet.microsoft.com/en-us/sysinternals/bb545027.aspx [7] ChangeWindowMessageFilter http://msdn.microsoft.com/en-us/library/ms632675%28VS.85%29.aspx [ Using the ChangeWindowMessageFilter function is not recommended, as it has process-wide scope. Instead, use the ChangeWindowMessageFilterEx function to control access to specific windows as needed. ChangeWindowMessageFilter may not be supported in future versions of Windows.] Q?Qhttp://it.chinawin.net/softwaredev/article-b6f9.html
1
#define WM_MYNEWMESSAGE (WM_USER + 999)<BR>UINT uMsgBall=::RegisterWindowMessage (WM_MYNEWMESSAGE )<BR>if(!uMsgBall)<BR>return FALSE;<BR>
管以前接触q一下GDI+Q但军_从现在开始系l学习GDI+Q所用的教材为《精通GDI~程》。在VS 2010以下版本的VS ~译器用GDI+都需要对GDI+环境q行初始化操作(VS 2010中的MFC 10依赖GDI+Q故不用初始化)?
VS 2003、VS 2005和VS 2008的GDI+环境初始化操作步骤一栗?
在应用程序类d一个保护权限的数据成员Q?
view plaincopy to clipboardprint?
ULONG_PTR m_gdiplusToken;
ULONG_PTR m_gdiplusToken;
在应用程序类的实现文件包含gdi+的头文gQ?
view plaincopy to clipboardprint?
#include <GdiPlus.h>
#include <GdiPlus.h>
在工E附加库加上QGdiPlus.lib
然后在应用程序类的InitInstance加上下面初始化代码:
view plaincopy to clipboardprint?
BOOL C***App::InitInstance()
{
Gdiplus::GdiplusStartupInput StartupInput;
GdiplusStartup(&m_gdiplusToken,&StartupInput,NULL);
}
BOOL C***App::InitInstance()
{
Gdiplus::GdiplusStartupInput StartupInput;
GdiplusStartup(&m_gdiplusToken,&StartupInput,NULL);
}
上面代码的作用是初始化GDI+资源?
在应用程序类的InitInstance加上下面代码Q?
view plaincopy to clipboardprint?
int C***App::ExitInstance()
{
// TODO: 在此d专用代码?或调用基c?nbsp;
Gdiplus::GdiplusShutdown(m_gdiplusToken);
return __super::ExitInstance();
}
int C***App::ExitInstance()
{
// TODO: 在此d专用代码?或调用基c?br>
Gdiplus::GdiplusShutdown(m_gdiplusToken);
return __super::ExitInstance();
}
上面代码的作用是销毁GDI+资源?
VC 6.0中用GDI+库,请参考这文章:在VC6.0中用GDI+的两U办?
现在试一下我们初始化GDI+环境是否成功。我们用GDI+的类接口在视囑֮户区l制一个字W串Q具体代码如下:
view plaincopy to clipboardprint?
CDC *pDC = pView->GetDC();
Gdiplus::Graphics graphics(pDC->m_hDC);
Gdiplus::Pen pen(Gdiplus::Color(255,0,0,255));
Gdiplus::SolidBrush brush(Gdiplus::Color(255,0,0,255));
Gdiplus::FontFamily fontfm(L"宋体");
Gdiplus::Font font(&fontfm,24,Gdiplus::FontStyleRegular,Gdiplus::UnitPixel);
CRect rt;
pView->GetClientRect(&rt);
Gdiplus::PointF pointF(rt.Width()/2,rt.Height()/2);
graphics.DrawString(L"GDI+E序C意",-1,&font,pointF,&brush);
graphics.ReleaseHDC(pDC->m_hDC);
pView->ReleaseDC(pDC);
CDC *pDC = pView->GetDC();
Gdiplus::Graphics graphics(pDC->m_hDC);
Gdiplus::Pen pen(Gdiplus::Color(255,0,0,255));
Gdiplus::SolidBrush brush(Gdiplus::Color(255,0,0,255));
Gdiplus::FontFamily fontfm(L"宋体");
Gdiplus::Font font(&fontfm,24,Gdiplus::FontStyleRegular,Gdiplus::UnitPixel);
CRect rt;
pView->GetClientRect(&rt);
Gdiplus::PointF pointF(rt.Width()/2,rt.Height()/2);
graphics.DrawString(L"GDI+E序C意",-1,&font,pointF,&brush);
graphics.ReleaseHDC(pDC->m_hDC);
pView->ReleaseDC(pDC);
效果囑֦下:
使用GDI+一些注意事:
1.在DLL中用GDI+库,只需要包含GdiPlus.h和GdiPlus.libQ初始化GDI+环境的工作只需要在主调用程序做Q否则在DLL初始化代码中初始化GDI+环境Ҏ发生DLL重入的错误(以前我犯q这L错误Q?
2.GDI+接口参数使用的是unicode字符集,因ؓ调用MGDI+cL口时其字W串参数都必ȝ保是unicode字符。在多字节字W集环境下开发常量字W串可以通过L宏{?变量多字节字W{unicode字符可以使用Windows API函数MultiByteToWideChar或ATL的A2W宏?
3. GDI+的对象和GDI句柄一P同样会占用资源,一ơ用过多的GDI+的对象甚至会发生E序崩溃的现象。因此必随时将不必要的GDI+的对象占用的资源释放掉,如上例的Qgraphics.ReleaseHDC(pDC->m_hDC)?
本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/clever101/archive/2010/12/08/6063670.aspx
作者:朱金?br>来源Q?a >http://blog.csdn.net/clever101/
我发C用GDI+来制作画囑ַL调色板极为方便(q个工作如果让GDI来做不知要写多少代码Q。下面我们学习一下GDI+的线性渐变画PLinearGradientBrushcȝ用法Q具体代码如下:
view plaincopy to clipboardprint?
CDC *pDC = pView->GetDC();
// 定义一个画囑֯?nbsp;
Gdiplus::Graphics graphics(pDC->m_hDC);
// 获取视图客户区大?nbsp;
CRect rt;
pView->GetClientRect(&rt);
// 定义一个线性渐变画P按红黄蓝l的序四种颜色渐变
LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/2),Color(255,255,0,0),Color(255,0,0,255));
Color colors[] = {
Color(255, 255, 0, 0), // red
Color(255, 255, 255, 0), //yellow
Color(255, 0, 0, 255), // blue
Color(255, 0, 255, 0)}; // green
REAL positions[] = {
0.0f,
0.33f,
0.66f,
1.0f};
linGrBrush.SetInterpolationColors(colors, positions,4);
// 填充指定区域矩Ş
graphics.FillRectangle(&linGrBrush,rt.Width()/2,0,80,rt.Height()/2);
CDC *pDC = pView->GetDC();
// 定义一个画囑֯?br> Gdiplus::Graphics graphics(pDC->m_hDC);
// 获取视图客户区大?br> CRect rt;
pView->GetClientRect(&rt);
// 定义一个线性渐变画P按红黄蓝l的序四种颜色渐变
LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/2),Color(255,255,0,0),Color(255,0,0,255));
Color colors[] = {
Color(255, 255, 0, 0), // red
Color(255, 255, 255, 0), //yellow
Color(255, 0, 0, 255), // blue
Color(255, 0, 255, 0)}; // green
REAL positions[] = {
0.0f,
0.33f,
0.66f,
1.0f};
linGrBrush.SetInterpolationColors(colors, positions,4);
// 填充指定区域矩Ş
graphics.FillRectangle(&linGrBrush,rt.Width()/2,0,80,rt.Height()/2);
效果如下Q?
q个U性渐变画刷很单,是按垂直方向(即y轴方向)渐变的。我感兴的是画L两个参数Point(100,0),Point(100,rt.Height()/2)Q书上介l的L的颜色和l点颜色的位|和要填充的矩Ş之间的关pL怎样的?我们看到上面的画LL和终点的高度和要填充的矩形的高度是一LQ都是rt.Height()/2。我们把d的高度羃ؓ原来的一般,看看有什么效果,卛_义ؓQ?
view plaincopy to clipboardprint?
LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/4),Color(255,255,0,0),Color(255,0,0,255));
LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/4),Color(255,255,0,0),Color(255,0,0,255));
效果囑֦下:
我们发现上图{于q箋用两个画刷填充这个矩形。假如填充的目标矩Ş的高度小于画L高度Q又会是怎样的效果呢Q代码改为:
view plaincopy to clipboardprint?
LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/2),Color(255,255,0,0),Color(255,0,255,0));
Color colors[] = {
Color(255, 255, 0, 0), // red
Color(255, 255, 255, 0), //yellow
Color(255, 0, 0, 255), // blue
Color(255, 0, 255, 0)}; // green
REAL positions[] = {
0.0f,
0.33f,
0.66f,
1.0f};
linGrBrush.SetInterpolationColors(colors, positions,4);
// 填充指定区域矩Ş
graphics.FillRectangle(&linGrBrush,rt.Width()/2,0,80,rt.Height()/4);
LinearGradientBrush linGrBrush(Point(100,0),Point(100,rt.Height()/2),Color(255,255,0,0),Color(255,0,255,0));
Color colors[] = {
Color(255, 255, 0, 0), // red
Color(255, 255, 255, 0), //yellow
Color(255, 0, 0, 255), // blue
Color(255, 0, 255, 0)}; // green
REAL positions[] = {
0.0f,
0.33f,
0.66f,
1.0f};
linGrBrush.SetInterpolationColors(colors, positions,4);
// 填充指定区域矩Ş
graphics.FillRectangle(&linGrBrush,rt.Width()/2,0,80,rt.Height()/4);
效果囑֦下:
我们看到q时矩Ş区域的填充只使用了画L一部分。这时或许我们可以得Z个简单的l论Q用d填充多边形区域,有点cM于铺地砖Q地砖好比画PI地好比要填充的区域区域?
思考题Q?
U性渐变画LL和终点的坐标值和要填充的矩Ş之间是什么关p?
本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/clever101/archive/2010/12/08/6063697.aspx
控g通知格式
控g通知l历了一个演变过E,因?span>SendMessage( )的变?span>Message?span>wParam?span>lParam有三U格式?span>
W一控g通知格式
W一控g通知格式只是H口消息的子集。它的特征格式如下:WM_XXXX。它主要来自下面?span>3U消息类型:
(1)表示一个控件窗口要么已l被创徏或销毁,要么已经被鼠标单ȝ消息Q?span>WM_PARENTNOTIFYQ?span>
(2)发送到父窗口,用来l制自nH口的消息,例如Q?span> WM_CTLCOLOR?span>WM_DRAWITEM?span>WM_MEASUREITEM?span>WM_DELETEITEM?span>WM_CHARTOITEM?span>WM_VKTOITEM?span>WM_COMMAND?span>WM_COMPAREITEM
(3)有滚动调控g发送,通知父窗口滚动窗口的消息Q?span>WM_VSCROLL?span>WM_HSCROLL
W二控g通知格式
W二控g通知格式与命令消息共享,它的特征格式如下Q?span>WM_COMMAND?span>
?span>WM_COMMAND中,lParam用来区分是命令消息还是控仉知消息Q如?span>lParam?span>NULLQ则q是个命令消息,否则lParam里面攄必然是控g的句柄,是一个控仉知消息?span>对于wParam则是低位攄是控?span>IDQ高位放的是相应的消息事件?span>
W三控g通知格式
q个才真正涉及到我们要讲的内容,同时他也是最为灵zȝ一U格式。它的特征格式如下:WM_NOTIFY?span>
?span>WM_NOTIFY中,lParam中放的是一个称?a name=baidusnap0>NMHDRl构的指针。在wParam中放的则是控件的ID?/span>
NMHDRl构的由?/span>
NMHDRl构是很值得一提的Q该l构包括有关制作该通知的控件的M内容Q而不受空间和cd的限Ӟ他的来历也是很有意思的?span>
在最初的windows3.x中,Ҏ׃存在什?span>WM_NOTIFYQ控仉知它们父窗口,如鼠标点?span>,控g背景l制事gQ通过发送一个消息到父窗口。简单的通知仅发送一?span>WM_COMMAND消息Q包含一个通知码和一个在wParam中的控gID及一个在lPraram中的控g句柄。这样一来,wParam?span>lParam都被填充了Q没有额外的I间来传递一些其它的消息Q例如鼠标按下的位置和时间?span>
Z克服q个困难Q?span>windows3.x提Z一个比较低U的解决{略Q那是l一些消息添加一些附加消息,最为明昄是控g自画用到?span>DRAWITEMSTRUCT。不知道大家对这个结构熟悉不Q不q,如果你是老手Q你应该非常清楚q个l构Q这个结构包含了9个内容,几乎你需要控制的信息都给你提供了。ؓ什么说它比较低U呢Q因Z同的消息附加的内容不同,l果是一盘散沙,非常混ؕ?span>
?span>win32中,MS又提Z一个更好的解决ҎQ引q?strong>NMHDRl构。这个结构的引进是消息l一hQ利用它可以传递复杂的信息。这个结构的布局如下Q?span>
NMHDR
{
HWnd hWndFrom ; 相当于原WM_COMMAND传递方式的lParam
UINT idFrom ; 相当于原WM_COMMAND传递方式的wParamQ?span>low-orderQ?span>
UINT code ; 相当于原WM_COMMAND传递方式的Notify Code(wParam"s high-order)
}Q?span>
对于q个l构的应用于WM_NOTIFY信息l构Q结?span>WM_NOTIFY变成了Q?span>
A、无附加信息。结构变得很单,是一?strong>NMHDRl构?span>
B、有附加信息。定义一个大的结构,它的W一个元素就?strong>NMHDRl构Q它的后面放|附加信息?span>
WM_NOTIFYl构的好?/span>
除了上面我们所说的好处外,WN_NOTIFYq有自己的独特的好处Q?span>
׃在大l构中,W一个成员ؓNMHDR,q样一来,我们可以利用指?strong>NMHDR的指针来传递结构地址Q根据指针的Ҏ,无论消息有没有附加信息,q个指针都适用Q也能够很方便的q行强制转换?/span>
分析ON_NOTIFY
cd导可以创?span>ON_NOTIFY消息映射入口q提供一个处理函数的框架Q来处理 WM_NOTIFYcd的消息?span>ON_NOTIFY消息映射宏有如下语法.
ON_NOTIFY(wNotifyCode,id,memberFxn)
其中Q?span>wNotifyCode:要处理的通知消息通知码。比如上面我们提到的LVN_KEYDOWNQ?span>Id:控g标识IDQ?span>MemberFxn:处理此消息的成员函数?span>
此成员函数有如下的原型声?span>:
afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result);
比如:假设你想成员函数OnKeydownList1处理ClistCtrl(标识ID=IDC_LIST1Q的 LVN_KEYDOWN消息,你可以用类向导d如下的消息映?span>:
ON_NOTIFY( LVN_KEYDOWN, IDC_LIST1, OnKeydownList1 )
在上面的例子?span>,cd导提供如下函?span>:
void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKey= (LV_KEYDOWN*)pNMHDR;
*pResult = 0;
}
q时cd导提供了一个适当cd的指针,你既可以通过pNMHDRQ也可以通过 pLVKey来访问这个通知l构?/span>
ON_NOTIFY_RANGE
有时我们可能需要ؓ一l控件处理相同的WM_NOTIFY消息。这旉要?span>ON_NOTIFY_RANGE而不?span>ON_NOTIFY。不q,很不q的是,VC6?span>ClassWizardq不支持q个消息Q所以我们必L工添加。方法和一般的手工d的消息一P不过需要注意的是:
(1)当你使用 ON_NOTIFY_RANGE?span>,你需要指定控件的ID范围.其消息映入口及函数原型如下:
ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )
其中Q?span>wNotifyCode:消息通知?span>.比如:LVN_KEYDOWN?span>id: W一控g的标?span>ID?span>
idLast:最后一个控件的标识ID。(标识g定要q箋Q?span>memberFxn: 消息处理函数?span>
(2)成员函数必须有如下原型申明:afx_msg void memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result );
l束语:
WM_NOTIFY是一个很值得思考的l构Q另外他?span>Reflect Message联系非常紧密Q我们接下来p讨论一下反消息?/span>