代码之家  ›  专栏  ›  技术社区  ›  user2703897

Delphi XE8/从TToolbutton单击调用windows api函数GetClipboardFormatName时对内存位置的访问无效

  •  0
  • user2703897  · 技术社区  · 7 年前

    我在Delphi XE8中有一个strage effekt,我想知道是否有人可以复制它并对其进行解释!

    我使用一个局部变量作为缓冲区调用windows api函数GetClipboardFormatName来接收剪贴板格式名称。

    当从TButton Click处理程序执行此操作时,它会按预期工作;当从TToolButton Click处理程序执行此操作时,它不会工作,getlasterror返回998/ERROR\u NOACCESS/Invalid access to memory location。

    这在Delphi 7下不会发生!

    我不是在寻找解决方法,我只是想知道这里发生了什么。我做错什么了吗?IDE安装有问题吗(2个开发人员)?这是XE8中的错误吗?

    这是一个演示单元:

    DFM文件

    object Form3: TForm3
      Left = 0
      Top = 0
      Caption = 'Form3'
      ClientHeight = 311
      ClientWidth = 643
      Color = clBtnFace
      Font.Charset = DEFAULT_CHARSET
      Font.Color = clWindowText
      Font.Height = -11
      Font.Name = 'Tahoma'
      Font.Style = []
      OldCreateOrder = False
      PixelsPerInch = 96
      TextHeight = 13
      object Panel1: TPanel
        Left = 0
        Top = 0
        Width = 643
        Height = 41
        Align = alTop
        Caption = 'Panel1'
        TabOrder = 0
        object Button1: TButton
          Left = 16
          Top = 10
          Width = 148
          Height = 25
          Caption = 'Standard TButton ==> OK'
          TabOrder = 0
          OnClick = Button1Click
        end
      end
      object Memo1: TMemo
        Left = 0
        Top = 70
        Width = 643
        Height = 241
        Align = alClient
        Lines.Strings = (
          'Memo1')
        TabOrder = 1
      end
      object ToolBar1: TToolBar
        Left = 0
        Top = 41
        Width = 643
        Height = 29
        ButtonHeight = 21
        ButtonWidth = 289
        Caption = 'ToolBar1'
        ShowCaptions = True
        TabOrder = 2
        object ToolButton1: TToolButton
          Left = 0
          Top = 0
          Caption = 'Standard TToolBar / TToolButton ==> ERROR_NOACCESS'
          ImageIndex = 0
          OnClick = ToolButton1Click
        end
      end
    end
    

    PAS文件

    unit Unit3;
    
    interface
    
    uses
        Winapi.Windows,
        Winapi.Messages,
        System.SysUtils,
        System.Variants,
        System.Classes,
        Vcl.Graphics,
        Vcl.Controls,
        Vcl.Forms,
        Vcl.Dialogs,
        Vcl.StdCtrls,
        Vcl.ExtCtrls,
        Vcl.ComCtrls,
        Vcl.ToolWin;
    
    type
        TForm3 = class(TForm)
            Panel1: TPanel;
            Memo1: TMemo;
            Button1: TButton;
            ToolBar1: TToolBar;
            ToolButton1: TToolButton;
            procedure Button1Click(Sender: TObject);
            procedure ToolButton1Click(Sender: TObject);
        private
            procedure say(s: string);
            procedure ListFormats;
            function GetRegisteredClipBoardFormatName(Format: word): string;
            function IsPredefinedFormat(format: word): boolean;
        { Private-Deklarationen }
        public
        { Public-Deklarationen }
        end;
    
    var
        Form3: TForm3;
    
    implementation
    
    uses
        clipbrd;
    
    const arPredefinedFormats: array[0..27] of word = (
            CF_TEXT,
            CF_BITMAP,
            CF_METAFILEPICT,
            CF_SYLK,
            CF_DIF,
            CF_TIFF,
            CF_OEMTEXT,
            CF_DIB,
            CF_PALETTE,
            CF_PENDATA,
            CF_RIFF,
            CF_WAVE,
            CF_UNICODETEXT,
            CF_ENHMETAFILE,
            CF_HDROP,
            CF_LOCALE,
            CF_MAX,
            CF_DIBV5,
            CF_MAX_XP,
            CF_OWNERDISPLAY,
            CF_DSPTEXT,
            CF_DSPBITMAP,
            CF_DSPMETAFILEPICT,
            CF_DSPENHMETAFILE,
            CF_PRIVATEFIRST,
            CF_PRIVATELAST,
            CF_GDIOBJFIRST,
            CF_GDIOBJLAST);
    
    {$R *.dfm}
    
    procedure TForm3.ToolButton1Click(Sender: TObject);
    begin
        ListFormats;
    
    end;
    
    
    procedure TForm3.Button1Click(Sender: TObject);
    begin
        ListFormats;
    end;
    
    
    procedure TForm3.ListFormats;
    var
        index: integer;
    begin
        for index := 0 to clipboard.formatcount - 1 do
        begin
            if not IsPredefinedFormat(clipboard.formats[index]) then
            begin
                say('Format: ' + inttostr(clipboard.formats[index]));
                say('Name: ' + GetRegisteredClipBoardFormatName(clipboard.formats[index]));
            end;
        end;
    end;
    
    procedure TForm3.say(s: string);
    begin
        memo1.lines.add(s);
    end;
    
    
    function TForm3.IsPredefinedFormat(format: word): boolean;
    var
        index: integer;
    begin
        for index := low(arPredefinedFormats) to high(arPredefinedFormats) do
        begin
            if arPredefinedFormats[index] = format then
            begin
                result := true;
                exit;
            end;
        end;
        result := false;
    end;
    
    //------------------------------------------------------------------------------------------
    (*
      Strange effekt in function GetClipboardFormatName
      when compiled with Delphi XE8 und Win 7.
    
    
    
      If this function is called from tbutton click, then everything ist ok!
    
      If this function is called from ttoolbutton click (and perhaps other controls...?)
      then the call to GetClipboardFormatName fails with getlasterror = 998
      which means
    
        ERROR_NOACCESS
        998 (0x3E6)
        Invalid access to memory location.
    
      which indicates that there is a problem with the local variable fmtname.
    
    
    
      Some Facts...
    
      * effekt happens under delphi xe8
      * effekt did not happen under delphi 7
      * it doesn't matter if I zero the memory of fmtname before using it.
      * it doesn't matter if I call OpenClipboard / CloseClipboard
      * if I use a local variable, then it does not work with ttoolbutton. The memorylocation of the local variable is
        slightly different from the case when it's called from tbutton.
      * if I use a global variable instead of a local variable, then it works with tbutton and ttoolbutton
        since it's the same memorylocation for both calls
    
    
      I'm NOT LOOKING FOR A WORKAROUND, I just would like to know if anybody can
      reproduce the behaviour and has an explanation as to why this is happening.
    
      Is there something wrong with using local variables for windows api calls in general?
    
    *)
    //------------------------------------------------------------------------------------------
    
    
    function TForm3.GetRegisteredClipBoardFormatName(Format: word): string;
    var
        fmtname: array[0..1024] of Char;
    begin
        if OpenClipboard(self.handle) then    //<--- does not make a difference if called or not
        begin
    
            if GetClipboardFormatName(Format, fmtname, SizeOf(fmtname)) <> 0 then
            begin
                result := fmtname;
            end else
            begin
                result := 'Unknown Clipboard Format / GetLastError= ' + inttostr(getlasterror);
            end;
    
            CloseClipboard;
        end else say('OpenClipboard failed');
    end;
    
    //------------------------------------------------------------------------------------------
    
    
    
    
    end.
    
    1 回复  |  直到 7 年前
        1
  •  1
  •   David Heffernan    7 年前

    您的代码已损坏。错误如下:

    GetClipboardFormatName(Format, fmtname, SizeOf(fmtname))
    

    文件 GetClipboardFormatName 描述了 cchMaxCount 参数如下:

    要复制到缓冲区的字符串的最大长度(以字符为单位)。如果名称超过此限制,它将被截断。

    您传递的是字节长度,而不是字符长度。在Delphi 7中 Char 是的别名 AnsiChar ,一种8位类型,但在Unicode Delphi,2009及更高版本中, 烧焦 是的别名 WideChar ,一种16位类型。

    因此,在XE8(Unicode Delphi)下,您声称缓冲区的长度是实际长度的两倍。

    您必须更换 SizeOf(fmtname) 具有 Length(fmtname) .

    我还应该提到,当您发现ANSI-Delphi和Unicode-Delphi之间的行为差异时,Delphi 2009中从8位ANSI到16位UTF-16 Unicode的变化应该始终是您的第一个怀疑。在您的问题中,您想知道这是Delphi错误还是安装问题,但您首先想到的应该是文本编码问题。据报道,几乎每次都有可能是罪魁祸首的症状。


    顺便说一句,这对 GetRegisteredClipBoardFormatName 作为GUI表单的实例方法。它没有提到 Self 这和你的形式课没有任何关系。这应该是一个低级助手方法,它不是GUI表单类型的一部分。