2011年12月3日
摘要: 总结了BSTR,CComBSTR及ATL字符串转换宏。 阅读全文
2011年12月1日
在C++ DOM编程中,通过DOM对象接口的各种函数获得的指向各种DOM对象的接口指针,其引用计数的变化是怎样的?在程序中该如何使用(显式或隐式调用AddRef)和释放(显式调用Release)接口指针呢?
MSDN DOM sample 上的一个示例很好的说明了这些原则,下面对这篇文章的代码做些总结说明。
总结一下,DOM接口指针使用和释放的规律为:
1. 通过DOM对象接口函数(当然也包括通过CoCreateInstance获得的IXMLDOMDocument)获得的接口指针,需要显式的调用 Release释放。这些函数如IXMLDOMDocument->createElement, IXMLDOMNode->appendChild。
2. 对于手动赋值的接口指针(如代码中的pElemA = pElemOut),需要显式的在被赋值的指针上(pElemA)调用AddRef和Release,来增、减引用计数。
3.其他的使用则无需增减引用计数。
其他:
Release()返回引用计数减1后的值,即如果原来引用计数为1,则Release()返回值为0.
IXMLDOMNode->appendChild()的输出参数接口指针在值上与输入参数接口指针相同,因此二者指向同一个DOM对象。
2009年5月11日
在C实现COM接口系列1中实现的com接口IFoo与使用它的客户耦合在一起,没有实现在各自分离的模块,因此不符合模块化编程思想。本期添加类厂支持,以使接口的实现与接口的使用相分离。
---------------------------------------------------
类厂的作用到底是什么?
将接口的实现与客户使用分离开来吗?
不尽然。使用CoCreateInstance,客户可以完全不必知道类厂的存在,而创建组件,获取组件实现的接口并使用。
即COM库可以完全抛开类厂的概念,而是提供一个这样的函数原型:
CoCreateObject(REFID rclsid,...,REFID riid,void **ppItf);
用户在调用的时候可以对riid提供IID_Unknown或者特定于该对象的一个接口,直接获取该对象的IUnknown或特定的接口指针。
可以看到,这正是CoCreateInstance所作的事情。
1 类厂提供了间接创建类对象的方式:用户可以先获取并持有类厂接口指针,通过该指针所指向的类厂接口创建类对象。适用于需要创建多个(或重复创建)类对象的地方,减少了每次都要定位对象库并把对象库装入内存的开销。
2 类厂提供了保证组件库留在内存不被卸载出去的另一种方法:类厂接口函数LockServer。组件库维护一个库范围计数器,只有该计数器为0时,组件库才允许自己被卸载出内存。(与此相对,引用计数是类对象范围的,通过该类实现的各个接口来维护。如果一个类对象的引用计数达到0,那么该对象占有的内存就被释放,该对象上的接口指针也不再有效。)
除了调用LockServer锁定组件库以外,当创建的组件个数大于0时,组件库也不能被卸载。也可以说,调用一次LockServer()的作用相当于创建了一个组件。
-----------------------------------------------------------------------
客户一侧:
1 使用一个接口需要知道哪些信息?
备选:
接口IID
类对象(类厂)CLSID(或ProgID)
接口函数原型(参数个数,类型,返回值)
实现接口组件的线程模型(进程内、进程外、远程)?
类型库typelib信息?
服务一侧:
2 实现一个组件和接口以供客户调用,需要提供哪些东西?
备选:
所有客户使用组件和接口所需的内容
额外的还有:
--------------------------------------------------------------------
为dll添加.def文件与直接在需要导出的函数定义处指定_declspec( dllexport )有区别吗?如果有是什么区别?
我发现在outdll.c中这样指定:
__declspec( dllexport ) HRESULT DllGetClassObject (REFCLSID rclsid, REFIID riid, void **ppv)
会产生编译错误:
1>------ Build started: Project: outside, Configuration: Debug Win32 ------
1>Compiling...
1>outdll.c
1>d:\outside-cf\outside\outdll.c(19) : error C2375: 'DllGetClassObject' : redefinition; different linkage
1> c:\program files\microsoft visual studio 8\vc\platformsdk\include\objbase.h(833) : see declaration of 'DllGetClassObject'
1>Build log was saved at "file://d:\outside-cf\outside\Debug\BuildLog.htm"
1>outside - 1 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed, 1 up-to-date, 0 skipped ==========
c2375的解释意思是出错的函数使用的链接指示符与之前声明的不同。
Compiler Error C2375
'function' : redefinition; different linkage
The function is already declared with a different linkage specifier.
objbase.h中声明了DllGetClassObject()函数:
STDAPI DllGetClassObject(IN REFCLSID rclsid, IN REFIID riid, OUT LPVOID FAR* ppv);
而使用.def文件就没有问题。
-----------------------------------------------------------------------------
初次执行结果:
问题就是总有一个分配的内存没有释放:
根据打印出来的内存地址可以判断,应该是先创建的类厂对象的内存没有释放。
检查代码,main()中并没有忘记调用Release(pCF)释放类厂对象。打印Release(pCF)的返回值,发现是1,即在类厂接口指针上少调用了一次Release,那么,究竟是哪里少的呢?
main()函数中有关类厂对象引用计数的地方就是CoGetClassObject和Release(CreateInstance跟类厂自己的引用计数无关),这是一对增加引用计数和减少引用计数的对应操作,所以,main()中应该没有问题。
那么,就只有创建类厂对象的时候了。下面看一下类厂对象是如何创建的。
首先,main调用CoGetClassObject,该函数就调用dll中的DllGetClassObject。由于是第一次调用(不考虑其他客户使用该dll的情况),程序执行到CreateClassFactory(...),该函数执行完后,类厂对象的引用计数是1。
由于创建成功,因此继续向下执行到QueryInterface,此时,类厂对象的引用计数变成了2。然后,DllGetClassObject返回,com库函数CoGetClassObject也应该返回。注意,此时的类厂对象引用计数已经是2了!
因此,问题就出在这里。main调用一次CoGetClassObject后,类厂对象的引用计数是2,而不是我想向中的1。于是,后面调用一次Release也就当然无法释放掉类场对象了。
1 HRESULT DllGetClassObject (REFCLSID rclsid, REFIID riid, void **ppv)
2 {
3 *ppv = 0;
4 if (IsEqualCLSID (rclsid, &CLSID_Outside))
5 {
6
7 if (!vpcfOutside)
8
9 {
10
11 HRESULT hr = CreateClassFactory (&CLSID_Outside, CreateOutside,
12 &IID_IClassFactory, &vpcfOutside);
13
14 if (hr != NOERROR)
15
16 return hr;
17 }
18
19 return QueryInterface (vpcfOutside, riid, ppv);
20
21 }
22
23 return E_FAIL;
24 }
找到了原因,改正就很容易了。这里我觉得需要把DllGetClassObject作如下修改:
1 HRESULT DllGetClassObject (REFCLSID rclsid, REFIID riid, void **ppv)
2 {
3 *ppv = 0;
4 if (IsEqualCLSID (rclsid, &CLSID_Outside))
5 {
6
7 if (!vpcfOutside)
8
9 {
10
11 HRESULT hr = CreateClassFactory (&CLSID_Outside, CreateOutside,
12 &IID_IClassFactory, &vpcfOutside);
13
14 if (hr != NOERROR)
15
16 return hr;
17
18 if(IsEqualIID(riid,&IID_IClassFactory))
19 {
20 *ppv = vpcfOutside;// Set *ppv to vpcfOutside directly instead of QueryInterface if first time creation
21 return NOERROR;
22 }
23 else
24 {
25 Release(vpcfOutside);// Any interface requested (riid) other than IID_ClassFactory and IID_Unknown not support by class factory,
26 // call Release to free the memory.
27 return E_FAIL;
28 }
29
30 }
31
32 return QueryInterface (vpcfOutside, riid, ppv);
33
34 }
35
36 return E_FAIL;
37 }
修改后在执行,内存都正常释放了。
-------------------------------------------------------------------------------------------
CreateClassFactory代码说明
1 HRESULT CreateClassFactory (REFCLSID rclsid,
2 HRESULT (*pfnCreate)(IUnknown *, REFIID, void **),
3 REFIID riid, void **ppv)
4 {
5 ClassFactory *this;
6 HRESULT hr;
7
8 *ppv = 0;
9 if (hr = Alloc (sizeof (ClassFactory), &this))
10 return hr;
11
12 this->icf.lpVtbl = &vtblClassFactory;
13 this->cRef = 1; // After this call, cRef==1
14
15 this->pfnCreate = pfnCreate;
16
17 hr = QueryInterface (&this->icf, riid, ppv); // After this call, cRef==2
18 Release (&this->icf); // Corresponds to "this->cRef = 1", ater this call, cRef==1
19
20 return hr;
21 }
可以看到,两行代码的效果是对引用计数增1及减1,这两行代码执行后,对引用计数的影响互相抵消,等于没有改变引用计数。那么,把这两行同时注释掉,是不是可以呢?
我的回答是:在本例中可以。因为这两行代码之间的QueryInterface总是可以执行成功的(因为是用IDD_ClassFactory来调用该函数的)。所以,即便把这两行代码同时注释掉,CreateClassFactory执行结束后,类厂对象的引用计数也增了1,以后调用Release就可以释放掉类厂对象占用的内存。
但是,如果CFQueryInterface的代码编写中除了错误,比如,像这样写:
1 static HRESULT CFQueryInterface (IClassFactory *pcf, REFIID riid, void **ppv)
2 {
3 ClassFactory *this = IMPL (ClassFactory, icf, pcf);
4
5 if (IsEqualIID (riid, &IID_IUnknown) ||
6 // IsEqualIID (riid, &IID_IClassFactory)) // Comment out this condition to create an error
7 *ppv = &this->icf;
8 else
9 {
10 *ppv = 0;
11 return E_NOINTERFACE;
12 }
13
14 AddRef ((IClassFactory *)*ppv);
15
16 return NOERROR;
17 }
那么,这两行代码之间的QueryInterface就会执行出错,那么类厂对象占用的内存就永远没有机会释放了。
也就是说,AddRef和Release虽然在作用上对引用计数来说相互抵消,但Release函数提供了释放对象内存的机会(当引用计数为0时),如果不成对的调用他们,也就失去了管理对象内存(释放对象占用的内存)的机会。
---------------------------------------------------------------------------
组件库outside文件说明:
IFoo.h IFoo接口声明
outside.c 组件对象、IFoo接口实现
cf.c 类厂对象、IClassFactory接口实现
outdll.c 组件库导出函数实现
outside.def 组件库模块定义文件,导出函数声明
outside.reg 组件库注册文件
----------------------------------------------------------------------------
源码:
outside-cf
2009年4月29日
用C实现的一个基本COM接口IFoo(来自COM Programmer's Cookbook)
把该文中实现的代码整理汇总到一个项目中。目前只是实现到一个中间阶段,重点在说明COM接口的实现原理,还没有包含类厂的部分。以后还需陆续添加类厂等高级功能。
文件组成:
ifoo.h COM接口IFoo,接口ID IID_IFoo 声明文件。
outside.c COM接口实现。这里实现IFoo的是一个结构体COutside.
util.h 一些宏定义、全局函数、变量声明文件。
main.c 笔者为实现项目添加的文件。提供main函数、内存管理函数Alloc,Free的实现(封装C运行库函数malloc和free.)、接口ID定义。
COM接口到底是什么?
COM接口是一个指向虚函数表的指针。通过这个指针可以访问内存中某处的各个功能块,执行预定义的功能,完成用户的任务。这些功能块以函数的形式存在(想不出还有其他形式:))并被调用。它们有一个共同点:都包含一个指针参数,指向这些功能要操作的数据地址。在C++中,这个地址就是对象的首地址,也就是类成员函数中隐含的this指针。在C函数中并没有这种现成的便利,因此代码实现中在接口定义时仍使用了接口指针(HRESULT (__stdcall * QueryInterface) (IFoo * This, const IID * const, void **)),而在接口函数实现时根据结构体布局结构,从这个接口指针推算得到对象实例指针。
typedef struct IFoo
{
struct IFooVtbl * lpVtbl;
} IFoo;
typedef struct IFooVtbl IFooVtbl;
struct IFooVtbl
{
HRESULT (__stdcall * QueryInterface) (IFoo * This, const IID * const, void **) ;
ULONG (__stdcall * AddRef) (IFoo * This) ;
ULONG (__stdcall * Release) (IFoo * This) ;
HRESULT (__stdcall * SetValue) (IFoo * This, int) ;
HRESULT (__stdcall * GetValue) (IFoo * This, int *) ;
};
COM接口的要求:
每一个COM接口(指向的虚函数表)的头三个函数必须是IUnknown接口的函数:QueryInterface,AddRef和Release。在C++中,称为从IUnknown接口继承。
对于调用QueryInterface响应查询IID_IUnknwon得到的接口指针值,同一个对象实现的所有接口必须相同。这是判断两个COM对象是否是同一个对象的标准。
宏定义“#define IUNK_VTABLE_OF(x) ((IUnknownVtbl *)((x)->lpVtbl))“说明
在预处理输出文件main.i中可以找到IUnknownVtbl和IFooVtbl的声明:
typedef struct IUnknownVtbl
{
HRESULT ( __stdcall *QueryInterface )(
IUnknown * This,
const IID * const riid,
void **ppvObject);
ULONG ( __stdcall *AddRef )(
IUnknown * This);
ULONG ( __stdcall *Release )(
IUnknown * This);
} IUnknownVtbl;
struct IUnknown
{
struct IUnknownVtbl *lpVtbl;
};
struct IFooVtbl
{
HRESULT (__stdcall * QueryInterface) (IFoo * This, const IID * const, void **) ;
ULONG (__stdcall * AddRef) (IFoo * This) ;
ULONG (__stdcall * Release) (IFoo * This) ;
HRESULT (__stdcall * SetValue) (IFoo * This, int) ;
HRESULT (__stdcall * GetValue) (IFoo * This, int *) ;
};
该宏定义的作用就是把IFoo接口中的IFooVtbl类型的指针拿出来((x)->lpVtbl)),并强制转换((IUnknownVtbl *))成IUnknownVtbl。
“强制转换”的结果是什么呢?是怎么做到的呢?
很明显,结果就是得到的指针不再是IFooVtbl *类型,而是变成了IUnknownVtbl *类型。至于做法,系统应该记录每一个变量、表达式的类型。当进行强制类型转换时,就(临时地)修改其类型为转换到的类型。
同理,QueryInterface, AddRef, Release宏定义中的(IUnknown *)也是这种用法。
可以看到,宏“IUNK_VTABLE_OF“的作用是供宏QueryInterface,宏AddRef,宏Release引用,把IFooVtbl *类型转换为IUnknownVtbl *类型,最终达到调用IUnknownVtbl中定义的三个QueryInterface,AddRef,Release函数。
那么,这种大费周章的目的是什么呢?为什么不以IFooVtbl中三个函数的定义形式(不通过强制转换来转换成必须的类型),直接调用IFooVtbl中定义的函数呢?虽然强制转换在参数值上并不会造成改变,最终调用的也是IFooVtbl定义的函数(FooQueryInterface,FooAddRef,FooRelease)。
为什么一定要通过IUnknown接口指针调用这三个函数呢?修改QueryInterface宏定义如下:
#define QueryInterface(pif, iid, pintf) \
(((pif)->lpVtbl)->QueryInterface(pif, iid, (void **)(pintf)))
即通过IFoo接口指针来调用由IUnknown引入的函数,有什么不对的地方吗?
试验表明,将QueryInterface宏定义如下也可以编译通过,执行起来也没有出现任何异常。
#define QueryInterface(pif, iid, pintf) \
(((pif)->lpVtbl)->QueryInterface(pif, iid, (void **)(pintf)))
对于IUnknown接口的三个函数,调用时传递的参数是IUnknown *类型(见QueryInterface, AddRef, Release宏定义),而函数定义中(FooQueryInterface, FooAddRef, FooRelease)声明的参数是IFoo *类型,这种不一致的情况是怎么出现的?这种不一致不会有问题吗?
这种不一致的产生是由于从不同的角度看待引起的。如果从IUnknown接口来看,那么接口函数中的第一个参数类型就是IUnknown *;如果从IFoo来看,那么第一个参数的类型就是IFoo *。
这种不一致性只是针对于编译器对于类型的编译要求有意义的,在接口实现及使用时,传递给lpVtbl->QueryInterface, lpVtbl->AddRef,lpVtbl->Release的第一个参数在值上都是相同的,都是实现该接口的内存地址(在本例中是COutside对象的首地址)。
一些语法现象回顾
函数指针变量定义、赋值及调用。
HRESULT (__stdcall * pQI) (IFoo * This, const IID * const, void **) ;
定义一个函数指针变量pQI,该变量指向“返回HRESULT,取3个参数分别为类型IFoo *,const IID * const, void **”的函数。
typedef HRESULT (__stdcall * QIType) (IFoo * This, const IID * const, void **) ;
定义一个函数指针类型,该类型的指针指向“返回HRESULT,取3个参数分别为类型IFoo *,const IID * const, void **”的函数。
HRESULT __stdcall QueryInterface(IFoo * This, const IID * const, void **) ;//函数声明示例
pQI = 0; // 函数指针赋值,0表示不指向任何函数。
pQI = QueryInterface; // 函数指针赋值,pQI指向QueryInterface。
pQI = &QueryInterface; // 与上面等价。
QueryInterface(&this->ifoo, riid, ppv); // 使用函数名直接调用
pQI(&this->ifoo, riid, ppv); // 函数指针调用
(*pQI)(&this->ifoo, riid, ppv); // 第二种函数指针调用方式
宏定义、展开规则
对于宏,一直有一种雾里看花的感觉,似乎很随意,怎么来都行,比如:
#define AddRef(pif) \
(IUNK_VTABLE_OF(pif)->AddRef((IUnknown *)(pif)))
宏定义应该是可以嵌套的,即宏定义的“内容“中还可以包含(嵌套)宏,如本例,“IUNK_VTABLE_OF”就是嵌套宏。在展开的时候,将嵌套的宏也一并展开(替换成定义的内容),直到不再有宏为止。
那么就有两个疑问:
1。如果被嵌套的宏包含(直接或间接)定义的宏,那么展开就没完没了,死循环了。
2。如果定义的内容中有跟定义的宏同名的字符串(比如上面的例子IUNK_VTABLE_OF),那么怎么区分这同名的东东是嵌套的宏(需要展开),还是一般的字符串(不需要展开)?
函数调用规范约定、main函数调用规范。
一开始把几个文件汇总到项目里时,编译通不过,错误提示大致意思是,不能把一种调用规范的函数指针转换成另一种调用规范的函数指针。后来把调用规范改为 /Gz(__stdcall),编译为(Compile As)改为/TC(Compile As C Code)就好了。
想来是对于.c文件,编译器缺省使用的是__cdecl,而IFoo中的接口宏定义在win32下展开成了__stdcall,所以出现了矛盾。而使用/Gz强制未声明调用规范的函数使用__stdcall,实现就与声明一致了。
(size_t)&(((s *)0)->m)
c++程序员也许都知道,访问地址“0”处的成员是一大忌,会造成GP。然而,取地址“0”处的成员的地址,却是个合法的操作。虽然地址“0”处并没有什么内容,但是,如果在地址0处存放一个内容,那么该内容中的成员也是有地址的。本例中正是巧妙地利用这种方法,从接口地址计算得出实现该接口的实例地址,进而访问实例的内部变量。
------------------------------------------------------------------------------------
2009年5月6日
附上源码:/Files/gracelee/outside.zip
代码执行结果:
2009年2月19日
终于弄清楚了原来说的同一个头文件不能被两次或两次以上包含是针对同一个源文件而言的。借用80后的流行语,真是汉哪!
原贴地址:http://www.keil.com/forum/docs/thread10237.asp
作者 Per Westermark
The
#ifndef xx
#define xx
...
#endif
method is to make sure that a header file isn't included more than once from the same c file.
You can not - and normally don't want to - stop multiple c files from including the same header file.
A header file is included because:
1) You have specifically added a line #include "xx" or #include <xx> in the source file. Don't do that unless you want the file to be included :)
2) You are including one header file, that it it's turn (one or more steps away) includes another header file. But a header file should only contain a recursive #include if it really needs that other file for some declarations. Hence, you need to include it.
What does this mean?
If the header file must be seen by multiple source files, you can't use it to allocate global variables, since the linker would then complain about multiple sets of global variables with the same name. This can be solved with the following:
//globals.h
#ifndef _GLOBALS_H
#define _GLOBALS_H
#if defined MAIN
#define EXTERN
#else
#define EXTERN extern
#endif
...
EXTERN int my_global_variable;
#endif // _GLOBALS_H
// main.c
#define MAIN
#include "globals.h"
...
// misc.c
#include "globals.h"
...
In this case, only the inclusion in main.c will result in an "allocation" of global variables, because the #define EXTERN will be empty. All other source files that includes "globals.h" will just see the type information for the global variables.
2009年1月16日
用户自定义类(class)类型可以当作系统内建类型(build-in type)来处理,对这一点我一直很惊奇,也很迷惑,特别是在类定义、继承关系很复杂的时候,要找到来龙去脉真的很抓头。最近工作中碰到这一块的东西,顺便借这个机会澄清一些概念。
看下面代码:
1 class CBase {
2 public:
3 CBase()
4 {
5 cout << "CBase constructor()" << endl;
6 }
7 virtual ~CBase()
8 {
9 cout << "CBase destructor()" << endl;
10 }
11
12 operator long()
13 {
14 cout << "CBase::operator long()" << endl;
15 return 0;
16 }
17
18 operator char()
19 {
20 cout << "CBase::operator char()" << endl;
21 return 'a';
22 }
23 }
24 ;
25 class CDerived:public CBase
26 {
27 public:
28 CDerived()
29 {
30 cout << "CDerived constructor()" << endl;
31 }
32
33 virtual ~CDerived()
34 {
35 cout << "CDerived destructor()" << endl;
36 }
37
38 operator long()
39 {
40 cout << "CDerived::operator long()" << endl;
41 //return CBase::operator long();
return *((CBase *)this);//change the above code to this looks better
42 }
43
44 };
45
46 void main()
47 {
48 CDerived d;
49 long lTmp = d;
50 cout << "lTmp=" << lTmp << endl;
51 char cTmp = d;
52 cout << "cTmp=" << cTmp << endl;
53
54 }
由于定义了操作符重载CDerived::operator long() 和CBase::operator long(),49行得以编译通过。同理,定义了CBase::operator char(),51行可以编译。
执行结果为:
CBase constructor()
CDerived constructor()
CDerived::operator long()
CBase::operator long()
lTmp=0
CBase::operator char()
cTmp=a
CDerived destructor()
CBase destructor()
这里涉及到的概念主要有:
1 类成员操作符重载(使得用户定义类型转换为内建类型成为可能。对于用户定义类型之间的转换,还可以通过构造函数的方式进行)
2 自动类型转换。自动类型转换发生的情况有以下几种:
函数调用时传递的实参类型与函数声明中指定的参数类型不匹配
函数返回的对象类型与函数声明中指定的返回类型不匹配
表达式中操作数的类型不一致(这正是上面例子中的情况)
有意思的是,即使不定义CBase::operator char(),上面的51行仍能通过,真得感叹编译器的聪明才智了,或许只是编译器source中多了一些的if{}else{}呢?
2008年10月31日
看看最新的随笔,竟然有一年多没来了,自己设的博客标题,真是对自己的讽刺。
很久以前写的帖子,现在看到还有人在回复,有点感动,也受到一点鼓舞:如果有什么是永恒的话,那么文字当属一种吧,虽然我苍白的语言会在网络浩瀚而五光十色的信息中被深埋而无人注意,毕竟也是一种曾经存在过,发生过的见证。
记录自己的心情,让生命在这种书写中找到归属和安慰,这就是博客于我的魅力吧。
2007年8月3日
工作中经常会需要计算一大串字节的异或,用计算器手工一个一个的输入太累人了,碰巧这两天在关注VBScript,于是就想到写了这个工具。这是我第一次用VB,代码很简单,希望能对你有用。
'==========================================================================
'
'
' NAME: calc_xor.vbs
'
' COMMENT: Calculates the result of 'xor' all elements in the input
'
'==========================================================================
Option Explicit
Dim strInput
'Promt for string to search for in log files
strInput = InputBox("Enter data to calc on.","calc xor","")
if strInput = "" then
wscript.quit
end If
'MsgBox(Len(strInput))
Dim arrayBytes()
ReDim arrayBytes(Len(strInput)/2)
Dim i,nBytes
Dim chHalf1,chHalf2,chWhole
i = 1
nBytes = 0
Do While (i < Len(strInput))
'Skip spaces between elements
Do While Asc(Mid(strInput,i,1)) = 32 'space
i = i + 1
Loop
chHalf1 = Asc(Mid(strInput,i,1))
chHalf2 = Asc(Mid(strInput,i+1,1))
'Check and convert first half
If chHalf1 >= 48 And chHalf1 <= 57 Then
chHalf1 = chHalf1 - 48
ElseIf chHalf1 >= 65 And chHalf1 <= 70 Then
chHalf1 = chHalf1 - 65 + 10
ElseIf chhalf1 >= 97 And chHalf1 <= 104 Then
chHalf1 = chHalf1 - 97 + 10
Else
MsgBox("invalid character")
wscript.quit
End If
' Check and convert the second half
If chHalf2 >= 48 And chHalf2 <= 57 Then
chHalf2 = chHalf2 - 48
ElseIf chHalf2 >= 65 And chHalf2 <= 70 Then
chHalf2 = chHalf2 - 65 + 10
ElseIf chHalf2 >= 97 And chHalf2 <= 104 Then
chHalf2 = chHalf2 - 97 + 10
Else
MsgBox("invalid character")
wscript.quit
End If
' Combine the first and second halves together to form a whole byte
chWhole = chHalf1 * 16 + chHalf2
arrayBytes(nBytes) = chWhole
i = i + 2
nBytes = nBytes + 1
Loop
'MsgBox(CStr(nBytes) + " bytes all together")
Dim WshSHell
set WshShell = CreateObject("WScript.Shell")
WshShell.Run("calc")
WScript.Sleep(100)
WshShell.AppActivate("Calculator")
WScript.Sleep(100)
WshShell.SendKeys("{F6}") 'Change to Decimal
'WshShell.SendKeys("{F4}") 'Change to single byte
WScript.Sleep(100)
WshShell.SendKeys("0")
For i =1 to nBytes
WshShell.SendKeys("{^}")
WScript.Sleep(100)
WshShell.SendKeys(arrayBytes(i-1))
WScript.Sleep(100)
WshShell.SendKeys("{=}")
WScript.Sleep(100)
Next
WshShell.SendKeys("{F5}") 'Change to Hex for easy recognition
2007年7月31日
写代码时经常会用到用sprintf格式化某个字符串,比如:
sprintf(mess,"This is field 1,Field 2,Field 3\n");
当这样的域很多的时候,一行放不下,为了便于阅读,需要把它们分成几行:
sprintf(mess,"This is filed 1,\
Field 2,\
Field 3,\
...
Field n\n");
但是,这样带来个问题,就是格式化后的mess的各个域之间就产生了不想要的字符(如空格等,使用UE可以清楚的看到),原因是由于使用了续行符"\"(line-continuation character),而不用续行符又无法通过编译(C2001)。
这时可以用双引号来把各个域分隔开,这样就既解决了可读性的问题,又解决了编译问题:
sprintf(mess,"This is filed 1,"
"Field 2,"
"Field 3,"
...
"Field n\n");
2007年6月19日
1.noun 生活,生计
What Londoners do to make a living have changed considerably since the 19th century.
19世纪以来,伦敦人谋生的方式发生了很大改变。
几个常见的词组:
living room
living condition
2.adj 活的
London is itself a living museum with thousand years of history and culture.
伦敦以其几千年的历史和文化,其本身就是一座博物院。
“living”作为形容词用就有必要提一下“live”。“live”作形容词也有“活的”意思,但感觉上这里似乎不能替换“living”,不知道对不对。