Delphi:有什么方法可以链接到 BPL 中尚未打包的变量吗?
我正在 RAD Studio 2007 中开发一个项目,使用 C++ 中的 VCL 类。
TDBLookupControl 是 VCL 和 TDBLookupControl 的一部分。有一些不良行为,这是由于使用内部变量 SearchTickCount
引起的。
var
SearchTickCount: Integer = 0; //file scope in DBCtrls.pas
procedure TDBLookupControl.ProcessSearchKey(Key: Char);
var
TickCount: Integer;
S: string;
begin
//some code removed for brevity
TickCount := GetTickCount;
if TickCount - SearchTickCount > 2000 then SearchText := '';
SearchTickCount := TickCount;
//some code removed for brevity
end;
但是,SearchTickCount
从未在 VCL 内打包,如下例所示。
extern PACKAGE int SearchTickCount;
我想在我的 C++ 代码中将 SearchTickCount
设置为零(按需)。 在我的代码中外部它使 c++ 编译。但是,链接器(显然)无法找到该变量。
namespace Dbctrls
{
extern int SearchTickCount;
}
// later on, inside a function
Dbctrls::SearchTickCount = 0;
有什么方法/解决方法可以链接到这个变量吗?
编辑: 不幸的是,我们还使用了一些从 TDBLookupControl 派生的自定义控件,因此我试图避免创建更多自定义控件。
I'm working on a project in RAD Studio 2007, using VCL classes in c++.
TDBLookupControl is part of VCL & has some undesirable behaviour, which is caused by use of an internal variable SearchTickCount
var
SearchTickCount: Integer = 0; //file scope in DBCtrls.pas
procedure TDBLookupControl.ProcessSearchKey(Key: Char);
var
TickCount: Integer;
S: string;
begin
//some code removed for brevity
TickCount := GetTickCount;
if TickCount - SearchTickCount > 2000 then SearchText := '';
SearchTickCount := TickCount;
//some code removed for brevity
end;
However, SearchTickCount
has never been PACKAGEd inside the VCL, as in example below.
extern PACKAGE int SearchTickCount;
I'd like to set SearchTickCount
to zero (on demand) in my c++ code.
Externing it in my code makes the c++ compile. However, the linker (obviously) cannot find the variable.
namespace Dbctrls
{
extern int SearchTickCount;
}
// later on, inside a function
Dbctrls::SearchTickCount = 0;
Is there any way/workaround to link to this variable?
EDIT:
Unfortunately, we're also using some custom controls that derive from TDBLookupControl so I was tring to avoid creating more custom controls.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
问题
SearchTickCount
是在单元的实现部分中声明的全局(单元级)变量,不应该在该单元之外访问它。如果您使用 Delphi 而不是 C++ Builder,您也会遇到同样的问题。明智的解决方案
TDBLookupControl
子类化,覆盖ProcessSearchKey()
并确保它使用您自己的SearchTickCount
,这是一个易于访问的解决方案。令人高兴的是ProcessSearchKey()
是虚拟的,理论上这应该可行,但实际上代码依赖于FListField
,这是一个私有字段,所以我们将回到第 1 步。TDBLookupControl
复制到您自己的TMyDBLookupControl
并确保您可以访问SearchTickCount
。这绝对有效。HACKY 解决方案
当然,黑客更有趣。 CPU 可以毫无问题地找到
SearchTickCount
,因为该地址被编码到构成ProcessSearchKey
代码的 ASM 指令中。 CPU能读的,我们也能读。评估
ProcessSearchKey
方法的代码,它只使用一个全局变量 (SearchTickCount
),并在两个地方使用它。首先在这个测试中:然后在这条指令中:
如果您查看该例程的反汇编列表,很容易发现全局变量访问,因为它在方括号中给出了变量的地址,没有其他限定符。为了使
if
工作,编译器会执行如下操作:对于赋值,编译器会执行以下操作:
如果您查看汇编器指令的左侧,您可以轻松地看到实际的操作码,在十六进制表示法。例如 SUB EAX,[$000000] 看起来像这样:
我的黑客解决方案利用了这个。我获取实际过程的地址 (
TDBLookupControl.ProcessSearchKey
),扫描代码查找操作码 (2B 05
) 并获取地址。就是这样,并且有效。当然,这有潜在的问题。这取决于使用这些确切的寄存器编译的代码(在我的示例中为
EAX
)。编译器可以自由选择不同的寄存器。我使用Delphi7和Delphi 2010进行了测试,代码编译为调试,编译为不调试。在所有 4 种情况下,编译器都选择使用EAX
作为SUB
指令,并且在 3/4 种情况下选择使用ESI
作为MOV
指令。因此,我的代码只查找SUB
指令。另一方面,如果代码运行一次,则代码每次都运行。代码一旦发布就不会改变,所以如果你可以在开发机器上正确测试,你就不会在客户端机器上遇到讨厌的反病毒软件。但使用风险自负,这毕竟是一种黑客行为!
这是代码:
您可以像这样使用 hacky 版本:
The problem
SearchTickCount
is a global (unit level) variable declared in the implementation section of the unit, it's not supposed to be accessed outside that unit. You'd have the same problem if you were working with Delphi, not C++ Builder.The sane solutions
TDBLookupControl
, overrideProcessSearchKey()
and make sure it uses your ownSearchTickCount
, one that's easily accessible. HappilyProcessSearchKey()
is virtual, in theory this should work, but in practice the code depends onFListField
, that's a private field so we'll be back to square 1.TDBLookupControl
to your ownTMyDBLookupControl
and make sure you can access theSearchTickCount
. This would definitively work.The HACKY solution
Ofcourse, hacks are a lot more fun. The CPU has no problem finding the
SearchTickCount
because the address is coded into the ASM instructions that make upProcessSearchKey's
code. What the CPU can read, we can read.Evaluating the code for the
ProcessSearchKey
method, it only uses one global variable (SearchTickCount
) and it uses it in two places. First in this test:then in this instruction:
If you look into the disassembly listing of that routine, global variable access is easily spotted, because it gives the address of the variable in square brackets, with no other qualifier. For the
if
to work the compiler does something like this:For the assignment, the compiler does something like this:
If you look at the left of the assembler instruction, you can easily see the actual opcode, in HEX notation. For example the SUB EAX, [$000000] looks like this:
My hacky solution exploits this. I get the address of the actual procedure (
TDBLookupControl.ProcessSearchKey
), scan the code looking for the opcode (2B 05
) and grab the address. That's it, and it works.Of course, this has potential problems. It depends on the code being compiled with those exact registers (
EAX
in my example). The compiler is free to choose different registers. I tested with both Delphi7 and Delphi 2010, with code compiled for Debug and compiled without Debug. In all 4 cases the compiler chose to useEAX
for theSUB
instruction, and in 3/4 cases chose to useESI
as the register for theMOV
instruction. Because of that my code only looks for theSUB
instruction.On the other hand if the code works once, the code works every time. Code doesn't change once released, so if you can properly test on the development machine, you will not get nasty AV's at client's machine. But use at your own risk, this is, after all, a hack!
Here's the code:
You use the hacky version like this:
另外 2 个选项:
Hacky
修复有问题的实现,并确保所有包都与我自己的 VCL 版本一起工作
Sane
我之前错过了这个实现细节。仅当字段类型为 fkData 或 fkInternalCalc 时才会检查
SearchTickCount
。拥有计算字段(fkCalculated)应该完全避免这个问题。Another 2 options:
Hacky
Fix the problematic implementation and make sure all packages work with my own version of VCL
Sane
TDBLookupControl
sI missed this implementation detail before.
SearchTickCount
is checked only if the field kind is fkData or fkInternalCalc. Having calculated fields (fkCalculated) should totally avoid the problem.由于 TDBLookupControl::ProcessSearchKey 是虚拟的,因此是指向该函数的指针
不返回实际的非静态成员函数指针地址。
相反,它返回一个 vtable 地址,该地址指向一个 thunk
(一小段代码,将虚拟函数重定向到正确的派生函数
对象非静态成员函数)。下面的代码计算出最终的(非虚拟)
基于thunk的成员函数TDBLookupControl::* ProcessSearchKey的地址
Since TDBLookupControl::ProcessSearchKey is virtual, a pointer to this function
doesn't return an actual non static member function pointer address.
Instead, it returns a vtable address, which points to a thunk
(a small bit of code that redirects the virtual function to correct derived
object non static member function). Below code figures out the final (non-virtual)
address of the member function TDBLookupControl::* ProcessSearchKey based on thunk