The following code tries to load the OpenGL glGenBuffers method but fails to do so. Could you help to comment what is the reason and how to work around ?
program Project2;
{$APPTYPE CONSOLE}
{$R *.res}
uses
StrUtils, SysUtils, Windows;
var
Library_OpenGL: HMODULE;
begin
try
Library_OpenGL := LoadLibrary('opengl32.dll');
Writeln(Ifthen(GetProcAddress(Library_OpenGL, 'glGenBuffers') <> nil, ' glGenBuffers Success', ' glGenBuffers Failed'));
Writeln(Ifthen(GetProcAddress(Library_OpenGL, 'glGenBuffersARB') <> nil, ' glGenBuffersARB Success', ' glGenBuffersARB Failed'));
FreeLibrary(Library_OpenGL);
Library_OpenGL := LoadLibrary('atioglxx.dll'); // ATI 4850
Writeln(Ifthen(GetProcAddress(Library_OpenGL, 'glGenBuffers') <> nil, ' glGenBuffers Success', ' glGenBuffers Failed'));
Writeln(Ifthen(GetProcAddress(Library_OpenGL, 'glGenBuffersARB') <> nil, ' glGenBuffersARB Success', ' glGenBuffersARB Failed'));
FreeLibrary(Library_OpenGL);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
ReadLn;
end.
If we properly check the return of the API, instead of dumping if it fails or not, we get the answer to why it fails pretty quick.
Replace
Writeln(Ifthen(GetProcAddress(Library_OpenGL, 'glGenBuffers') <> nil, ' glGenBuffers Success', ' glGenBuffers Failed'));
with
Win32Check(GetProcAddress(Library_OpenGL, 'glGenBuffers') <> nil);
and we have an exception:
EOSError: System Error. Code: 127.
The specified procedure could not be found
So the reason GetProcAddress fails is that the opengl library does not export a glGenBuffers function. A check with "depends" confirms that.
A quick search about missing functions in opengl leads us to wglGetProcAddress:
The wglGetProcAddress function returns the address of an OpenGL
extension function for use with the current OpenGL rendering context.
If you read the documentation throughly, you'll notice that simply calling gwlGetProcAddress supplying glGenBuffers will fail. You need a rendering context for it to return the address of the function.
Below is my shortest project which succeeds in getting the function's address. It use a GUI application since a rendering context requires a window, and a default VCL form suits the requirements (see "remarks" in SetPixelFormat). (Disclaimer: I don't know anything about opengl, code below is not necessarily correct, it also omits error checking which you should be really avoiding).
uses
opengl;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
Library_OpenGL: HMODULE;
DC: HDC;
PxFmt: TPixelFormatDescriptor;
GLRC: HGLRC;
glGenBuffers: procedure(n: GLsizei; buffers: PGLuint); cdecl;
begin
Library_OpenGL := LoadLibrary('opengl32.dll');
DC := GetDC(Handle);
ZeroMemory(#PxFmt, SizeOf(PxFmt));
PxFmt.nSize := SizeOf(PxFmt);
PxFmt.nVersion := 1;
PxFmt.dwFlags := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;
PxFmt.iPixelType := PFD_TYPE_RGBA;
PxFmt.cColorBits := 32;
PxFmt.cDepthBits := 24;
PxFmt.cStencilBits := 8;
SetPixelFormat(DC, ChoosePixelFormat(DC, #PxFmt), #PxFmt);
GLRC := wglCreateContext(DC);
wglMakeCurrent(DC, GLRC);
#glGenBuffers := wglGetProcAddress('glGenBuffers');
Assert(#glGenBuffers <> nil);
...
end;
While probably too late to be useful to the original questioner, the following might help others who find this question...
Delphi (possibly even very early versions) includes an OpenGL unit which provides basic functionality. I understand this is limited to functions present in OpenGL 1.1.
Delphi XE6 (released in April 2014 according to Wikipedia) added an OpenGLExt unit, including functionality up to OpenGL 4.3. This unit provides glGenBuffers and many other extension functions, but the extension functions are only loaded if your program explicitly calls InitOpenGLext (which itself just makes many wglGetProcAddress calls).
Using the two units together can save substantial effort.
I just got a simple tutorial working (just drawing a white triangle) using GLFW along with Delphi's OpenGL and OpenGLExt units.
Using GLFW in Delphi requires a unit to bind the GLFW functions, along with compiled GLFW DLLs. The following Github repository includes such a unit and DLLs together (though this isn't the one I used): https://github.com/neslib/DelphiGlfw
The essential calls before you can call extension functions are as follows. I've omitted error checking, but comments in the code should help with that.
uses Winapi.OpenGL, Winapi.OpenGLExt,
glfw; //The glfw unit name may be different, depending where you obtained it.
var window:pGLFWwindow;
begin
glfwInit; //Returns 0 if it fails.
window:=glfwCreateWindow(640,480,pchar(utf8encode('My Test Window')),
nil,nil); //Returns nil if it fails.
glfwMakeContextCurrent(window);
// Next line assigns function pointers for extension functions, so glGenBuffers will work.
InitOpenGLext;
// Add any other setup and your rendering loop here, and clean up after the loop.
// ...
end;
Related
I am trying to copy directory:
procedure CopyBigDirWithSubdirs;
{$IOCHECKS ON}
begin
try
TDirectory.Copy(SrcPath, DstPath);
except
on E: EInOutError do something
end;
end;
In my case it is crucial to check disk full condition and I hoped that catching EInOutError exception would solve my problem. But as far as I could find out TDirectory methods do not notify of this condition at all. The situation is even worse because TDirectory.copy can write part of subdirs, face disk full condition and terminate, so I have to check the whole directory tree to be sure that my directory is copied properly. Does anybody know better solution?
{$IOCHECKS ON} isn't relevant here. That's for legacy Pascal I/O. And likewise for EInOutError, you aren't ever going to get that from functions in the IOUtils unit.
The real problem here is that TDirectory.Copy is, like so much of IOUtils, broken by design. There appears to be no error checking whatsoever implemented in TDirectory.Copy. For what it is worth, the rule at my place of work is that IOUtils must not be used in our code.
You are going to have to either write your own code which does include some error checking, or find a third party library to do the work.
Certainly on Windows then you should use IFileOperation to do this. As a benefit you'll even be able to show the standard system progress dialog. And because the code is provided by the system rather than by Embarcadero, you can expect it to work.
If you require support for other platforms then you may have to work a little harder to find suitable code.
As using IFileOperation interface looks like most practical solution I've written the function based on it:
function CopyItem(const Src, Dest: string ): HRESULT;
const
FOF_SILENT = $0004;
FOF_NOCONFIRMATION = $0010;
FOF_NOCONFIRMMKDIR = $0200;
FOF_NOERRORUI = $0400;
FOF_NO_UI =(FOF_SILENT or FOF_NOCONFIRMATION or FOF_NOERRORUI or FOF_NOCONFIRMMKDIR); // don't display any UI at all
var
lFileOperation: IFileOperation;
psiFrom: IShellItem;
psiTo: IShellItem;
opAborted : longbool;
begin
//We probably don't need to call CoInitializeEx/CoUninitialize pair as it could have been called by Delphi library
CoInitializeEx(nil, COINIT_APARTMENTTHREADED or COINIT_DISABLE_OLE1DDE);
// check arguments and create the IFileOperation interface,
if (Src='') or (Dest='') then Result := E_INVALIDARG
else Result := CoCreateInstance(CLSID_FileOperation, nil, CLSCTX_ALL, IFileOperation, lFileOperation);
// Set the operation flags. Turn off all UI from being shown to the user
if Succeeded(Result) then Result := lFileOperation.SetOperationFlags(FOF_NO_UI);
// Create IShellItem-s from the supplied source and dest paths.
if Succeeded(Result) then Result := SHCreateItemFromParsingName(PWideChar(wideString(Src)),
nil, IShellItem, psiFrom);
if Succeeded(Result) then Result := SHCreateItemFromParsingName(PWideChar(wideString(Dest)),
nil, IShellItem, psiTo);
// This method does not copy the item, it merely declares the item to be copied
if Succeeded(Result) then Result := lFileOperation.CopyItem(psiFrom, psiTo, nil, nil);
// This method is called last to execute those actions that have been specified earlier
if Succeeded(Result) then Result := lFileOperation.PerformOperations;
// Check now if the operation was aborted by the system
if Succeeded(Result) then
begin
lFileOperation.GetAnyOperationsAborted(opAborted);
if opAborted then Result := ERROR_WRITE_FAULT;
end;
CoUninitialize;
end;
As you can see from the code the solution is not complete because in case of disc full error (my reason for all this fiddling with lFileOperation) PerformOperations returns S_OK (!!!) and I can find the error only by calling GetAnyOperationsAborted which does not specify the error condition exactly but merely sets opAborted flag. Then I have to guess the real case of abortion.
I want to query Active Directory in an app developed with Delphi (7 and up), but do not want to include "ActiveDs_TLB" in the "uses" clause to keep the EXE size down. When querying WMI it is possible to use the IBindCtx and IMoniker interfaces to avoid linking in the type library (see How do I use WMI with Delphi without drastically increasing the application's file size? for a solution).
Is it possible to do the same when performing AD queries? I my case I want to retrieve "IADsUser" and "IADsComputer". I am aware that I can decrease the EXE size by manually copying only the required definitions from "ActiveDs_TLB" into my program or to use an LDAP query, but I would prefer a solution similar to the one described for WMI.
I'm no Active Directory expert, but I just created two D7 console applications, one accessing the WnNTSystemInfo object using the ActiveDS_TLB.Pas type library and the other using late binding do do the same thing, namely get the ComputerName from AD.
First, the late binding one:
program ActiveDSLBConsole;
{$APPTYPE CONSOLE}
uses
SysUtils, ActiveX, ComObj;
var
SI : OleVariant;
S : String;
begin
CoInitialize(Nil);
SI := CreateOleObject('WinNTSystemInfo');
S := SI.ComputerName;
writeln(S);
readln;
end.
(what took me longest writing the above was checking the registry for the name of
the object to create)
Anyway, I hope that shows that, yes, you can query AD via late binding and that this minimal example will get you started querying AD that way.
The equivalent AD console application using ActiveDS_Tlb is
program ActiveDSConsole;
{$APPTYPE CONSOLE}
uses
SysUtils, ActiveX, ActiveDS_Tlb;
var
SI : IADsWinNTSystemInfo;
S : String;
begin
CoInitialize(Nil);
SI := CoWinNTSystemInfo.Create;
S := SI.ComputerName;
writeln(S);
readln;
end.
These have .Exe sizes of
ActiveDSConsole : 390144 bytes
ActiveDSLBConsole : 87552 bytes (late bound)
So there's evidently quite a bit of code pulled in to support the use
of the tlb objects, but neither is huge.
FWIW, the above re-written as Button1Click handlers of a minimalist VCL app gives Exe sizes
of
using ActiveDS_TLB : 396288 bytes
late bound : 392704 bytes
the difference between these two seems fairly marginal to me, but there's a clear
size advantage to late binding in a minimal D7 console application. Your mileage may vary,
so probably best to "suck it and see", if you'll pardon the mixed metaphors.
Btw, late binding has the advantage that you don't always have to supply arguments for each of the parameters in an interface method. And you can call a method with this special syntax that the compiler was enhanced to allow (when automation support was added, in D2) for variants that it knows contain late-bound automation objects:
(from an MS Word late binding example)
Table := MSWord.ActiveDocument.Tables.Add(Range:= MSWord.Selection.Range, NumRows:= Rows, NumColumns:= Columns, DefaultTableBehavior:= wdWord9TableBehavior, AutoFitBehavior:= wdAutoFitFixed);
Martyn's answer filled in the missing pieces. Here's an example on how to query IADsUser using late binding:
program GetUserObjectPath;
{$APPTYPE CONSOLE}
uses SysUtils, ActiveX, ComObj;
function GetObject (const Name: WideString) : IDispatch;
var
Moniker : IMoniker;
Eaten : Integer;
BindContext : IBindCtx;
begin
OleCheck (CreateBindCtx (0, BindContext));
OleCheck (MkParseDisplayName (BindContext, PWideChar (Name), Eaten,
Moniker));
OleCheck (Moniker.BindToObject (BindContext, NIL, IDispatch, Result));
end; { GetObject }
procedure Query_AD (const sQuery: String);
var
vUser : OleVariant;
begin
vUser := GetObject (sQuery); // = IADsUser
WriteLn ('Name = ' + vUser.FullName);
end; { Query_AD }
var
sQuery, sDomain, sUserName : String;
begin
sDomain := GetEnvironmentVariable ('USERDNSDOMAIN');
sUserName := GetEnvironmentVariable ('USERNAME');
sQuery := Format ('WinNT://%s/%s,user', [sDomain, sUserName]);
CoInitialize (NIL);
try
Query_AD (sQuery);
finally
// Causes Access Violation if AD query does not happen in subroutine
CoUninitialize;
end; { try / finally }
WriteLn;
Write ('Press [Enter] to continue ...');
ReadLn;
end.
The actual AD query should happen in a subroutine (here "Query_AD"), otherwise calling "CoUninitialize" is going to lead to an access violation (see Why does CoUninitialize cause an error on exit? for an explanation).
I'm writing an app I'd like to be backwardly compatible to some extent on XP, or at the very least windows vista.
EDIT FOR CLARITY: I need to be able to do what the first code snippet below does, but in XP. "Does anybody know the best approach to take under XP, given the functions aren't available in USER32.DLL.?"
My initial prototype code on windows 7 just called CreateProcess to start up displayswitch.exe, which is deployed with windows 7.
if you are not familiar with it, it's a handy little utility that is what gets invoked when you press the windows key and the letter P. you can read more about it here.
while this was adequate, i subsequently needed to sense the current state (eg internal vs external or extend vs clone), so i have now coded up a winapi solution that works well on windows 7 (and i presume 8). it involves making calls to SetDisplayConfig and QueryDisplayConfig in User32.DLL
The pertinent section of it is here (minus the many, many structures i had to hand craft in pascal code from the original klingon).
function getTopology : DISPLAYCONFIG_TOPOLOGY_ID ;
var NumPathArrayElements,
NumModeInfoArrayElements : UINT32;
var PathArrayElements_Size,
ModeInfoArrayElements_Size : UINT32;
error : Longint;
paths : PDISPLAYCONFIG_PATH_INFO_array;
info : PDISPLAYCONFIG_MODE_INFO_array;
begin
NumModeInfoArrayElements := 0;
Result := DISPLAYCONFIG_TOPOLOGY_EXTERNAL;
inc(result);
error := GetDisplayConfigBufferSizes(QDC_DATABASE_CURRENT,NumPathArrayElements,NumModeInfoArrayElements);
case error of
ERROR_SUCCESS :
begin
PathArrayElements_Size := sizeof(DISPLAYCONFIG_PATH_INFO) * NumPathArrayElements ;
ModeInfoArrayElements_Size := sizeof(DISPLAYCONFIG_MODE_INFO) * NumModeInfoArrayElements;
GetMem(paths,PathArrayElements_Size);
try
GetMem(info,ModeInfoArrayElements_Size );
try
error := QueryDisplayConfig(QDC_DATABASE_CURRENT,NumPathArrayElements, paths,NumModeInfoArrayElements, info,result);
case error of
ERROR_SUCCESS :;
else
Result := DISPLAYCONFIG_TOPOLOGY_EXTERNAL;
inc(result);
end;
finally
FreeMem(info,ModeInfoArrayElements_Size );
end;
finally
FreeMem(paths,PathArrayElements_Size);
end;
end;
end;
end;
function setTopology ( top : DISPLAYCONFIG_TOPOLOGY_ID) : boolean;
var flags : dword;
begin
result := false;
flags := DecodeDISPLAYCONFIG_TOPOLOGY_ID_SDC(top);
if flags <> 0 then
begin
result := SetDisplayConfig(0,nil,0,nil,SDC_APPLY or flags) = ERROR_SUCCESS;
end;
end;
Since these functions don't exist in XP (as far as I know), I am looking for a stable way of achieving a similar thing in XP. whilst i am coding in Delphi, it's not necessary that the solution be presented as such. i am quite happy to just look at how it's done, or read a description of the appropriate steps, and implement it myself.
(removed full listing as it was confusing the issue as it did not appear like a question)
This is an attempt to rephrase my previous question as a result of the feeedback which it received.
I want a simple network communication which I can use as an underlying framework and never have to look at again. I just want o push a string from one PC to another and get a string in response. I don't want to have to worry about opening conenctions, keeping them open, reopening them if they close, etc.
I want to concentrate on my application and have a simple functional API along the lines of:
SendStringToOtherPc() : String; // Called at PC #1.
// Returns PC #2's result string
// or "" on error (or throws exception)
ProcessReceivedStringAndReply(); // Called at PC # 2. Sends result string
I do need to know if the other PC replied or not; and, if so, what the result string was
also "nice to have" would be for both PCs to initiate communication. If not, I can have one of them (the client poll), or have the other send its communication as a reply to the heartbeat which I need to add.
I presume that those with multiple fprojects under their belts have a "starter" framework which they use for every new project, just adding the application specific log - and it's such a framwork, or abstraction layer, that I want. Can anyone point me at a URL?
I know nothing of socket programming and don't really have time to learn. If I do, some other project will suffer.
While I do respect the argument that I should understand what my software is doing, there is a valid counter-arguement that everyone should not have to develop this particular wheel for himself, and surely there is some FOSS around which does what I want?
Thanks in advance.
Update: I seem to have started a little controversy, with some thinking me lazy or doomed to disaster. So, maybe I should explain a little of my history.
I spent three decades developing telecoms software and we always followed the OSI 7 layer model. I was generally layer 3, the network layer, and no matter whether it was a telephone exchange, base station or hanset, whether the protocol was ISDN, ISUP, DECT, GSM, GPRS, UMTS or a propietary satellite protocol, I could always instuct a Serveice Access Point of Layer 2, the data transport layer, "hey, you! Get this mesage to the other guy and tell me what his reply is". Did I know how it was done? Did I care?
#CosmicPrund, who will probably be awarded the answer unless someone points me at a Layer 2, said "The true answer to this question is that all you need is learn how to use Indy" and I beg to disagree.
Someone will, but not me if I can help it. I already leanred too many skills, programming languages, databse systems, oprerating systems and will always avoid learning more that an overview of another if I can. Like Sir Isaac Newton, I would prefer to stand on the shoulders of giants.
Software is just getting too big for one guy. Surely none of you start each project from scratch? I guess you reuse the networking code from a previous project(?) and that reusable code is my "Layer 2". And my question is where can I download such code and use it without understanding its inner workings?
Does anyone know of such a thing?
Answer: I used Indy and got what I wanted. I will porbably try to build up a library of functions which I can use as a network abstraction layer.
I have a free framework that will do all this. The benefit is that you can use it without any knowledge of sockets whatsoever. You can safely ignore connects and disconnects because this is all handled by the framework (the underlying comms framework keeps a continuous connection via configurable pings, etc). A message queueing threading model is also built into the framework. I have a demo for your exact example as well. The downside is obviously a steep learning curve. Have a look at http://www.csinnovations.com/framework_delphi.htm
The true answer to this question is that all you need is learn how to use Indy. To prove my point I'll give you a 89 lines unit that actually implements all you requested, plus a proof-of-concept sample of how to use it.
Before I show the code I'd like to mention:
89 lines of code can't be called a framework. It's just a thin wrapper that's simply not worth it. Sooner or later you'd run into stuff that requires direct access to the underlying Indy framework.
Someone with more Indy experience would probably write this using even less lines of code.
I could even make it shorter myself, since I included two overloaded "StartServer" methods for ease of demonstration.
Implementing this using components dropped on a form would cut the number of lines further.
Here's the "framework" unit:
unit UTcpIntercom;
interface
uses IdContext, IdCustomTCPServer, IdTCPServer, IdBaseComponent,
IdComponent, IdTCPConnection, IdTCPClient, SysUtils;
type
EIntercomError = class(Exception);
TReceivedText = procedure(const TextFromClient:string; var Response:string) of object;
TReceivedTextProc = procedure(const TextFromClient:string; var Response:string);
TIntercomServer = class(TIdCustomTCPServer)
protected
Event: TReceivedText;
Proc: TReceivedTextProc;
HostGreeting: string;
public
function DoExecute(AContext: TIdContext): Boolean; override;
end;
function SendTextToComputer(const TextToSend, HostToSend, HostGreeting:string; PortNumber: Integer): string;
function StartServer(PortNumber:Integer; const HostGreeting:string; OnReceivedText: TReceivedText):TIntercomServer;overload;
function StartServer(PortNumber:Integer; const HostGreeting:string; OnReceivedText: TReceivedTextProc):TIntercomServer;overload;
implementation
function SendTextToComputer(const TextToSend, HostToSend, HostGreeting:string; PortNumber: Integer): string;
var Id: TIdTCPClient;
begin
Id := TIdTCPClient.Create(nil);
try
Id.Host := HostToSend;
Id.Port := PortNumber;
Id.Connect;
try
if Id.IOHandler.ReadLn <> HostGreeting then
raise EIntercomError.Create('Host is invalid: ' + HostToSend);
Id.IOHandler.WriteLn(TextToSend);
Result := Id.IOHandler.ReadLn;
Id.Disconnect;
finally Id.Disconnect;
end;
finally Id.Free;
end;
end;
function StartServer(PortNumber:Integer; const HostGreeting:string; OnReceivedText: TReceivedText):TIntercomServer;overload;
begin
Result := TIntercomServer.Create(nil);
Result.Bindings.Add.Port := PortNumber;
Result.HostGreeting := HostGreeting;
Result.Event := OnReceivedText;
Result.Active := True;
end;
function StartServer(PortNumber:Integer; const HostGreeting:string; OnReceivedText: TReceivedTextProc):TIntercomServer;overload;
begin
Result := TIntercomServer.Create(nil);
Result.Bindings.Add.Port := PortNumber;
Result.HostGreeting := HostGreeting;
Result.Proc := OnReceivedText;
Result.Active := True;
end;
{ TIntercomServer }
function TIntercomServer.DoExecute(AContext: TIdContext): Boolean;
var Text, Response: string;
begin
AContext.Connection.IOHandler.WriteLn(HostGreeting);
Text := AContext.Connection.IOHandler.ReadLn;
Response := '';
if Assigned(Event) then
Event(Text, Response)
else if Assigned(Proc) then
Proc(Text, Response)
else
Response := 'No handler assigned.';
AContext.Connection.IOHandler.WriteLn(Response);
AContext.Connection.Disconnect;
Result := True;
end;
end.
Here's the code that uses the unit. Notice the DoSomethingWithTextFromClient, that's essentially your ProcessReceivedStringAndReply method. Also notice the use of StartServer and SendTextToComputer.
program Project9;
{$APPTYPE CONSOLE}
uses
SysUtils,
UTcpIntercom in 'UTcpIntercom.pas';
procedure DoSomethingWithTextFromClient(const TextFromClient: string; var Response:string);
var i: Integer;
C: Char;
Len: Integer;
begin
Response := TextFromClient;
Len := Length(Response);
for i:=1 to (Length(Response) div 2) do
begin
C := Response[Len-i+1];
Response[Len-i+1] := Response[i];
Response[i] := C;
end;
end;
begin
try
try
with StartServer(1000, 'Test', #DoSomethingWithTextFromClient) do
begin
WriteLn(SendTextToComputer('12345678', '127.0.0.1', 'Test', 1000));
Free;
end;
Readln;
except on E:Exception do
begin
WriteLn(E.ClassName);
WriteLn(E.Message);
Readln;
end;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
I wrote Delphi debug visualizer for TDataSet to display values of current row, source + screenshot: http://delphi.netcode.cz/text/tdataset-debug-visualizer.aspx . Working good, but very slow. I did some optimalization (how to get fieldnames) but still for only 20 fields takes 10 seconds to show - very bad.
Main problem seems to be slow IOTAThread90.Evaluate used by main code shown below, this procedure cost most of time, line with ** about 80% time. FExpression is name of TDataset in code.
procedure TDataSetViewerFrame.mFillData;
var
iCount: Integer;
I: Integer;
// sw: TStopwatch;
s: string;
begin
// sw := TStopwatch.StartNew;
iCount := StrToIntDef(Evaluate(FExpression+'.Fields.Count'), 0);
for I := 0 to iCount - 1 do
begin
s:= s + Format('%s.Fields[%d].FieldName+'',''+', [FExpression, I]);
// FFields.Add(Evaluate(Format('%s.Fields[%d].FieldName', [FExpression, I])));
FValues.Add(Evaluate(Format('%s.Fields[%d].Value', [FExpression, I]))); //**
end;
if s<> '' then
Delete(s, length(s)-4, 5);
s := Evaluate(s);
s:= Copy(s, 2, Length(s) -2);
FFields.CommaText := s;
{ sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');}
end;
Now I have no idea how to improve performance.
That Evaluate needs to do a surprising amount of work. The compiler needs to compile it, resolving symbols to memory addresses, while evaluating properties may cause functions to be called, which needs the debugger to copy the arguments across into the debugee, set up a stack frame, invoke the function to be called, collect the results - and this involves pausing and resuming the debugee.
I can only suggest trying to pack more work into the Evaluate call. I'm not 100% sure how the interaction between the debugger and the evaluator (which is part of the compiler) works for these visualizers, but batching up as much work as possible may help. Try building up a more complicated expression before calling Evaluate after the loop. You may need to use some escaping or delimiting convention to unpack the results. For example, imagine what an expression that built the list of field values and returned them as a comma separated string would look like - but you would need to escape commas in the values themselves.
Because Delphi is a different process than your debugged exe, you cannot direct use the memory pointers of your exe, so you need to use ".Evaluate" for everything.
You can use 2 different approaches:
Add special debug dump function into executable, which does all value retrieving in one call
Inject special dll into exe with does the same as 1 (more hacking etc)
I got option 1 working, 2 should also be possible but a little bit more complicated and "ugly" because of hacking tactics...
With code below (just add to dpr) you can use:
Result := 'Dump=' + Evaluate('TObjectDumper.SpecialDump(' + FExpression + ')');
Demo code of option 1, change it for your TDataset (maybe make CSV string of all values?):
unit Unit1;
interface
type
TObjectDumper = class
public
class function SpecialDump(aObj: TObject): string;
end;
implementation
class function TObjectDumper.SpecialDump(aObj: TObject): string;
begin
Result := '';
if aObj <> nil then
Result := 'Special dump: ' + aObj.Classname;
end;
initialization
//dummy call, just to ensure it is linked c.q. used by compiler
TObjectDumper.SpecialDump(nil);
end.
Edit: in case someone is interested: I got option 2 working too (bpl injection)
I have not had a chance to play with the debug visualizers yet, so I do not know if this work, but have you tried using Evaluate() to convert FExpression into its actual memory address? If you can do that, then type-cast that memory address to a TDataSet pointer and use its properties normally without going through additional Evaluate() calls. For example:
procedure TDataSetViewerFrame.mFillData;
var
DS: TDataSet;
I: Integer;
// sw: TStopwatch;
begin
// sw := TStopwatch.StartNew;
DS := TDataSet(StrToInt(Evaluate(FExpression)); // this line may need tweaking
for I := 0 to DS.Fields.Count - 1 do
begin
with DS.Fields[I] do begin
FFields.Add(FieldName);
FValues.Add(VarToStr(Value));
end;
end;
{
sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');
}
end;