Delphi 中的字符串的使用

心血来潮,仔细分析比较了一下 Delphi 字符串的各种陷阱。
 
1. 字符串的声明:
 
    可以用以下三种方式来声明字符串:
 
    1)  定义方式:
           S: string;
         初始化:
            S := ‘Hello World’;
         说明:
         这种情况是编译器默认处理的情况,编译器初始化了字符
         串 S,分配内存调整引用计数,其中 S[0] 为字符串长度
         ,但是不能直接访问,必须使用 Length 或者 SetLength
         函数来访问。
 
   2)  定义方式:
           S: PChar;
         初始化:
            S := ‘Hello World’;
         说明:
         这种情况下,实际上编辑器先做了字符串的初始化,再将字符
         串赋值给字符指针。Delphi 编译器自动处理 PChar 和 String 的
         转换过程。
 
    3) 定义方式:
           S: array [1..256] of Char;
        初始化:
            S := ‘Hello World’;
        说明:
       
        这种情况下,对字符串的初始化长度必须小于给定的数组长度,
        否则默认情况下编译出错。
       
--
另外,不能使用的是 s: array of Char; 这种方式的定义,这种方式定义了一个动态数组必须使用 SetLength 来初始化长度,但仍然不能直接对其进行字符串赋值,只能是当作一个数组来使用了。
 
2. 使用 FillChar 字符串和数组的区别:
 
对于字符串的 FillChar 初始化:
 
        const Len = 10;
       var S: string;
       begin
           SetLength(S, Len);
           FillChar(S[1], Len, 0);
       end;
 
字符串的 FillChar 调用的第一个参数必须是符串的第一个字符,下标是从 1 开始的。 注意,S[0] 是不允许访问的。也可以使用 PChar 来做这件事情:
 
    var
           S: String;
           P: PChar;
   begin
           SetLength(S, 5);
           P := PChar(S);
           FillChar(P[0], 5, 0);
   end;
   
    此时,对 P 进行 FillChar 操作时,实际上时按照数组方式来进行
   ,在进行 PChar 转换后, P[0] = S[1] ,因此 P 的下标为 0 。
 
        S := ‘Hello’;
       P := PChar(S);
  
   则: S[1] = P[0] = ‘H’
3. PChar 和 String 的通用:
 
 procedure TForm1.Button1Click(Sender: TObject);
var
  S: string;
  P: PChar;
begin
  S := ‘Hello World’;
  P := PChar(S);
   ShowMessage(P);
end; 
 
 即:PChar 可以直接当作字符串来使用。连 S := S + P 这样的式子都没有错。
 
4. P:PChar 的使用
 
    S := ‘Hello’;
   P := PChar(S);
 
    实际上 P 此时理论上(使用字符串常数初始化时实际上不是这样)
    指向 S[1],需要特别注意 的是,PChar 是指向字符的指针,因此又存在
    如下关系:
 
    P[0] = S[1] = P^ = ‘H’
 
    P^ 指的是指针指向的内容。同样也存在这样的关系:
 
    Integer(P) = Integer(@p[0])
 
    当使用 SetLength(S, 10) 来初始化 S 时,下列关系成立:
 
    Integer(P) = Integer(@p[0]) = Integer(@S[1])
 
    但是使用字符串常数初始化时实际上不是这样:
 
    Integer(P) = Integer(@p[0]) != Integer(@S[1])
 
    但是 S[1] 的地址却和 P 的地址不同,是否 S := ‘Hello’ 时,S 被复制了?
   (这儿又是一个编译器魔法)
 
5. PChar 和 PShortString
 
    1) PChar 的误区:
 
    由于 PChar 往往能够和字符串通用,例如:
 
        P: PChar;
       P := PChar(S);
       ShowMessage(P)
  
   因此,往往会误理解为 PChar 就是字符串,所以在获取字符时
    会使用错误用法:
 
        P^[0] (错误用法)
 
    正确的用法是:
 
        P[0]
 
    P 实际上是指向了第一个字符的指针。
 
    2) 看看 PShortString 吧:
 
    对于 PShortString ,它是一个指向字符串的指针。因此,如果
  
       PStr: PShortString;
 
    则 PStr^ 指的是一个字符串,因此获取字符串的内部字符时使用
 
        PStr^[0] (合法形式,但需要注意此时下标从 0 开始)
 
    这里使用 PShortString 又有一个很奇怪的场景:
 
        PStr: PShortString;
       S: string;
        PStr := PShortString(S); // (正确用法)
       // PStr := @S; // (错误用法)
 
    显然,这个用法相当让人费解,不禁让人怀疑其实 String 内部就是一个指针(编译器魔法)错误用法 PStr := @S 合乎对指针的理解: PStr 是指向字 符串的指针,那么现在就取字符串的地址并赋值给它。但是事实上却不管用。正确用法: PStr := PShortString(S); 包括 P := PChar(S) 都显然不 符合指针的定义的。
  
   此处可见 Delphi 本身语法的不统一性。
后记
——
 
    本人接触 Delphi 已经不下 5 年,但平时若不仔细推敲 Delphi 语法,仍然会深陷其编译器陷阱中,其实很惭愧,本人以前太浮躁。
    从某种意义上讲,Delphi 在快速开发的同时隐藏了太多的细节,但这些细节又隐藏的不太好,时常又会暴露给程序员。
    此处涉及到的字符串相关的类型还比较少,除了上述类型外,Delphi 中的字符串还包括 Ansi 和 Wide 系列。
699 次阅读

Delphi生成的ActiveX创建模板的研究--Com对象是如何创建的

Delphi 生成的XXX_TLB.pas文件中生成了一个类

TActiveFormX = class(TOleObject)

end;

另外在XXX_Impl.pas文件中同样生成了一个类:TActiveFormX,经过研究,这两个类的确是
一个是接口,一个是实现类。

前者TActiveFormX从TOleObject继承,并且实现了方法:

procedure TActiveFormX.InitControlData;
const
CEventDispIDs: array [0..8] of DWORD = (
$000000C9, $000000CA, $000000CB, $000000CC, $000000CD, $000000CE,
$000000CF, $000000D0, $000000D1);
CTFontIDs: array [0..0] of DWORD = (
$FFFFFE00);
CControlData: TControlData2 = (
ClassID: ‘{B90C2778-2158-48F5-A279-3A52BA87B423}’;
EventIID: ‘{8791E2BA-D57D-4CC9-91DA-81403D4E1144}’;
EventCount: 9;
EventDispIDs: @CEventDispIDs;
LicenseKey: nil (*HR:$80040154*);
Flags: $0000001D;
Version: 401;
FontCount: 1;
FontIDs: @CTFontIDs);
begin
ControlData := @CControlData;
TControlData2(CControlData).FirstEventOfs := Cardinal(@@FOnActivate) – Cardinal(Self);
end;

该方法负责定义这个OLE控件的类ID,事件接口ID,在TOleObject的方法TOleControl.createInstance中
则利用到了这些注册信息,创建COM对象:

procedure TOleControl.createInstance;
var
ClassFactory2: IClassFactory2;
LicKeyStr: WideString;

procedure LicenseCheck(Status: HResult; const Ident: string);
begin
if Status = CLASS_E_NOTLICENSED then
raise EOleError.createFmt(Ident, [ClassName]);
OleCheck(Status);
end;

begin
if not (csDesigning in ComponentState) and
(FControlData^.LicenseKey <> nil) then
begin
//创建COM对象,其中FControlData^.ClassID为子类定义的COM对象的ID
OleCheck(CoGetClassObject(FControlData^.ClassID, CLSCTX_INPROC_SERVER or
CLSCTX_LOCAL_SERVER, nil, IClassFactory2, ClassFactory2));
LicKeyStr := PWideChar(FControlData^.LicenseKey);
LicenseCheck(ClassFactory2.cr&#101;ateInstanceLic(nil, nil, IOleObject,
LicKeyStr, FOleObject), SInvalidLicense);
end else
LicenseCheck(Cocr&#101;ateInstance(FControlData^.ClassID, nil,
CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IOleObject,
FOleObject), SNotLicensed);
end;

COM对象的实现单元XXX_Impl.pas则定义了该COM对象的实现,并且创建了一个TActiveXControlFactory,
用于生成该COM对象:

initialization
TActiveFormFactory.cr&#101;ate(
ComServer,
TActiveFormControl,
TActiveFormX,
Class_ActiveFormX,
1,
”,
OLEMISC_SIMPLEFRAME or OLEMISC_ACTSlik&#101;LABEL,
tmApartment);
end.

因此前一个TActiveXForm 被称为OLEProxy,即Ole的调用网关。

COM系统CocreateInstance(IID)的时候会请求相应的TActiveFormFactory调用相应的createIntance方法。

868 次阅读