Jump to content

Mark Williams

Members
  • Content Count

    274
  • Joined

  • Last visited

Everything posted by Mark Williams

  1. I am trying to load a TFDQuery from a memory stream using the default format. I can do this fine from file: Query.LoadFromFile('c:\data.DAT', sfAuto); If I load the same first into a stream and then call LoadFromStream I get error code 710 "Invalid Binary Storage Format". I have the DefaultStoreExt of the ResourceOptions for the Query's connection component set to ".DAT" and its DefaultStoreFormat to sfAuto. Is there anything else I should be doing? NB I have also tried setting the DefaultStoreExt without the lading period.
  2. Mark Williams

    FireDAC TFDQuery LoadFromStream default format

    have found cause of problem. Had to do with something else. Please ignore.
  3. Mark Williams

    Controlling MS Word Window from Delphi form

    I create an instance of Word using TWordApplication: WordApp := TWordApplication.Create(frmWord); WordApp.ConnectKind := ckNewInstance; The Word app is owned by my form. TFrmWord is basically just a form with a toolbar, which is designed to sit above the Word app. FrmWord needs: to be visible when the Word app it has created is visible. minimize when the Word app is minimized. Move when the Word app is moved. Resize when the Word app is resized And vice versa for the Word app (ie any movement, resizing, minmimizing etc needs to be relected back to my form). Basically, I am trying to get this to work as if my form and the Word instance are one form. Before anyone suggests, I have tried OLEContainers. They are, of course, buggy, but there are other problems: It is problematic with the Word ribbon when more than one container's open. It has horrific refresh problems when the OLEContainer is moved on to a second monitor. If the user opens a word side panel (eg search or help) and then closes it the Word object does not refresh with the result that there is blank space where the panel used to be and detecting this and trying to refresh is difficult. There were a number of other problems, which I can't remember now, but so many as I gave up the ghost. So I worked with SetWindowPos and timer's instead. It took weeks of trial and error to do what I want it to do and in the end I had it working reasonably well. However, it seems that in a recent Windows update the way SetWindowPos works has changed. Apparently, MS has decided to place further restrictions on one app trying to pop itself on top of another active app. I can understand why, but it has caused problems for the way I have tried to marry up my form and the Word app. Before I spend weeks more time trying to figure out how now to do this, I thought it was worth a post to see if anyone can think of a simpler way of achieving this.
  4. Mark Williams

    Controlling MS Word Window from Delphi form

    I don't have any experience with Office plugins. I thought about it at the time and it looked difficult and I seem to remember there were certain things I thought I would be unable to do using a plugin. I considered having a look at Add-in Express, but I'm keen to avoid reliance on third party tools as far as possible. I think I'll have a look at it and see if it can do what I want.
  5. In order to pop up a login box I have removed my main form from the autocreation section and I create it in the project file as follows: Application.Initialize; Application.MainFormOnTaskbar := False; Application.CreateForm(TfrmMain, frmMain); Application.Run; The reason I do this is to stop the main form showing if the login process is cancelled. The main form has an initialization and finalization section. If the login process is cancelled so that the main form never shows, the initialization section is called, but the finalization section does not get called. In fact, the app hangs and has to be terminated via task manager. if not DoLogIn(self, CurrServer, MainUser, ltStartUp) then begin Application.Terminate; exit; end; If I insert a Show command prior to Application.Terminate all works as expected. Does anyone know why Application.Terminate does not terminate the app without a call to "Show" and is there aw away around this (other than calling Show)?
  6. Mark Williams

    Finalization section not called unless main form shown

    Ok. Thanks that makes sense. I was testing whether mainform was assigned in the onCreate event of the first form created.
  7. Mark Williams

    Finalization section not called unless main form shown

    I have Application.ShowMainForm:=false, but it still flickers after call to terminate. I cannot figure out why it is now doing this. It wasn't when I originally posted and as far as I can see my code is the same. But as mentioned above, there is apparently no mainform.
  8. Mark Williams

    Finalization section not called unless main form shown

    The "exit" does nothing. I was under the misapprehension that the current function would run to its conclusion even after terminate call. Unfortunately, I have been fiddling around trying to find out how to solve the problem and now I can't recreate it! My main form flashes on the screen at the point of application.terminate and it closes properly. Can't seem to get it back to its original state. Will just have to live with the form momentarily flashing on screen. Thanks for the help.
  9. Mark Williams

    Finalization section not called unless main form shown

    VCL The form creation code is in the proj file. You can't assign the form as the main form other than in project options. It's readonly. Which has made me realise I don't have a mainform. Maybe that's the problem
  10. I'm using TWebModule in an ISAPI DLL. I am using it to return data from a database in xml format using IXMLDocument. Last night I had a problem with text data which included characters such as "/". I assume the returned text was being formatted somewhere along the line to insert escape characters with the result that the received xml at the other end is always slightly short so that you end up with a badly formed xml document. So, for example, if the returned text had two "/" characters the xml document when received is short the last two characters. My code on the server side is: Response.Content := doc.XML.Text; Response.ContentLength :=length(doc.XML.text); I also tried determining the ContentLength using "length(Response.Content)", but that made no difference. I edited the data in the database to remove the "/" characters and all worked fine. I have now tried to replicate the problem so that I can seek some advice as to possible causes, but the xml is no longer being truncated. I am puzzled and keen to avoid the issue for the future, but unable to provide any more info because I can't now replicate the problem. Does anyone have any ideas as to what may have caused the problem initially?
  11. Mark Williams

    TWebModule response content truncated before return

    That makes sense. Many thanks.
  12. Mark Williams

    TWebModule response content truncated before return

    No video link? However, don't think it would help. There is no exception and in any event my code is wrapped in try...except statements with exceptions outputted to a log file. Also, as mentioned in my post I cannot now reproduce the problem and don't understand why it was happening in the first place. I am just keen to avoid a repeat.
  13. Mark Williams

    ShellExecute and passing of password

    Thanks again. I have decided to go down a different route altoghether and instead of having separate apps, house them all within the same app.
  14. Mark Williams

    ShellExecute and passing of password

    Thanks to all. I probably need to rethink my approach.
  15. Mark Williams

    ISAPI DLL concurrent requests

    Thanks for this information. I will go through it all asap. Likely, I will have some questions if you don't mind. Bit of a newbie to ISAPI!
  16. Mark Williams

    ISAPI DLL concurrent requests

    Spot on! That was the problem. As for WebGarden, I have check and you create a WebGarden by increasing the ApplicationPool's Maximum Worker Processes above 1. I am now running as non-web garden. I have done a lot of research based on your helpful points above and eventually the mist began to clear. I now have a working ISAPI DLL which appears to be thread safe. Unfortunately, I couldn't find any good examples of how to do what I was trying to do. My DLL uses global variables stored in an inifile. I don't want to load the inifile each time a new thread is fired to avoid repeated loading of the ini file.. So I need to load the inifile once when the DLL is first loaded. Also, I need to have a threadsafe logging system and finally I use a MimeTable in the DLL and I don't want to load that for each request. Below is my relevant code (with the detail removed) to show I have gone about the above. It is working as far as I can see, but I would appreciate any tips for improvement or re potential pitfalls. Hopefully, this will then help others who are new ISAPI dll. It took me many hours of head scratching to get this far. The dpr library WebServer; uses Winapi.ActiveX, System.Win.ComObj, System.SysUtils, Web.WebBroker, Web.Win.ISAPIApp, Web.Win.ISAPIThreadPool, WinAPI.Isapi2, WinAPI.Windows, System.SyncObjs, WebModule in 'WebModule.pas' {WebModule1: TWebModule}; {$R *.res} //Read online somewhere that it is best to initialize/Finalize your globals in the following function //rather than in Initialization/Finalization sections of the webmodule} function GetExtensionVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall; begin Result := Web.Win.ISAPIApp.GetExtensionVersion(Ver); CriticalSection := TCriticalSection.Create; StartServer; end; function TerminateVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall; begin Result := Web.Win.ISAPIApp.GetExtensionVersion(Ver); CloseServer; CriticalSection.Free; end; exports GetExtensionVersion, HttpExtensionProc, TerminateExtension; begin CoInitFlags := COINIT_MULTITHREADED; Application.Initialize; Application.WebModuleClass := WebModuleClass; {Application.MaxConnections:=100;} //if you want to increase beyond the default 32 for example NumberOfThreads:=Application.MaxConnections; //see comments following code samples. This is necessary even if you do not want to change the MaxConnections Application.Run; end. Stripped down WebModule unit WebModule; interface uses WinApi.Windows, System.SysUtils, System.Classes, Web.CGIHTTP, Web.HTTPApp, Web.ReqMulti, System.SyncObjs, ThreadFileLog, IDGlobalProtocols {, etc...}; type TLogType = (leNone, leCriticalError, leMajorError, leMinorError, leMajorEvents, leDevelopment); type TWebModule1 = class(TWebModule) FDQuery: TFDQuery; FDPhysPgDriverLink: TFDPhysPgDriverLink; FDConnection: TFDConnection; procedure WebModule1HeadAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); procedure WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); procedure WebModuleAfterDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); private { Private declarations } public { Public declarations } end; var WebModuleClass: TComponentClass = TWebModule1; CriticalSection: TCriticalSection; const EncryptionKey='Q9__M_f_qc1EX8ce98HsQrYG_s4aRGQj5G_Ixu4XTQs___esfxdH_kf_lg5_8lnk'; Procedure StartServer; Procedure CloseServer; implementation Uses web.WebBroker; var //these will be accessible to all threads ServerStarted:boolean; LogType:TLogType; LogFile, PGVendorLib:string; FThreadFileLog : TThreadFileLog; MimeTable:TIDMimeTable; {%CLASSGROUP 'System.Classes.TPersistent'} {$R *.dfm} procedure AddToLog(str: string; whichLogType:TLogType); var Log:string; begin try if (LogType=leNone) or (ord(whichLogType)>ord(LogType)) then //we are not logging these types of events or errors exit; case whichLogType of leCriticalError: Log:='CRITICAL ERROR: '; leMajorError: Log:='MAJOR ERROR: '; leMinorError: Log:='MINOR ERROR: '; leMajorEvents: Log:='MAJOR EVENT: '; leDevelopment: Log:='DEVELOPMENT: '; end; Log:=Log+str; FThreadFileLog.Log(LogFile, Log); Except end; end; Procedure CreateMimeTable; begin try if not assigned(MimeTable) then begin MimeTable:=TIDMimeTable.create; AddToLog('Mime Table Create', leDevelopment); end; except on E: Exception do AddToLog('Error CreateMimeTable: '+e.Message, leMajorError) end; end; Procedure StartServer; var appPath:String; iniFile:TIniFile; begin if serverStarted then //Probably overkill as should only be called once exit; CriticalSection.Enter; try try FThreadFileLog := TThreadFileLog.Create(); appPath:=GetModuleName(HInstance); if pos('\\?\', appPath)>0 then //this is something to do with the Windows function GetModuleFileName and uncpaths appPath:=copy(appPath, 5, MaxInt); iniFile:=TIniFile.Create(changeFileExt(appPath, '.ini')); try if not FileExists(iniFile.FileName) then begin LogFile:='c:\ProgramData\Log.txt'; AddToLog('IniFile does not exist at location: "'+iniFile.FileName, leCriticalError); Exit; end; AddToLog('Server Started', leMajorEvents); LogType:=TLogType(iniFile.ReadInteger('GENERAL', 'log_type', 1)); LogFile:=inifile.ReadString('GENERAL', 'log_file', ''); if (LogType<>leNone) and (LogFile<>'') then begin if not DirectoryExists(ExtractFilePath(LogFile)) and not ForceDirectories(ExtractFilePath(LogFile)) then LogFile:='c:\ProgramData\Log.txt'; end; PGVendorLib:=inifile.ReadString('LOCATIONS', 'pg_vendor_lib', ''); finally iniFile.Free; end; AddToLog('LogType:'+inttostr(ord(logType)), leDevelopment); AddToLog('LogFile: '+LogFile, leDevelopment); AddToLog('FDPhysPGDriverLink.VendorLib: '+PGVendorLib, leDevelopment); AddToLog('Server Started', leMajorEvents); ServerStarted:=true; except try AddToLog('Server could not be loaded: '+SysErrorMessage(GetLastError), leCriticalError); except end; end; finally CriticalSection.Leave; end; end; Procedure CloseServer; begin CriticalSection.Enter; try if assigned(MimeTable) then try MimeTable.Free; except end; if assigned(FThreadFileLog) then try FThreadFileLog.Free; except end; try AddToLog('Server Closed', leMajorEvents); except end; finally CriticalSection.Leave; end; end; procedure TWebModule1.WebModule1HeadAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var fileName:string; begin Handled:=true; try //If this action needed the mimetable it would call CreateMimeTable at this point FileName:=trim(Request.GetFieldByName('Location')); if FileName='' then begin SetResponse(Response, 400, 'Bad Request', 'Head'); exit; end; if fileExists(FileName) then SetResponse(Response, 200, 'OK') else SetResponse(Response, 404, 'NotFound'); except end; end; procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin FDPhysPgDriverLink.VendorLib:=PGVendorLib; AddToLog('Before Dispatch - PathInfo: '+Request.PathInfo, leDevelopment); AddToLog('Before Dispatch - Method: '+Request.Method, leDevelopment); AddToLog('MaxConnections: : '+Application.MaxConnections.ToString, leDevelopment); AddToLog('ActiveConnections: : '+Application.ActiveCount.ToString, leDevelopment); AddToLog('InactiveConnections: : '+Application.InActiveCount.ToString, leDevelopment); AddToLog('CachedConnections: : '+Ord(Application.CacheConnections).ToString, leDevelopment); end; procedure TWebModule1.WebModuleAfterDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin AddToLog('After Dispatch - Method: '+Request.Method, leDevelopment); end; end. ThreadFileLog unit unit ThreadFileLog; interface uses Windows, ThreadUtilities, System.Classes; type PLogRequest = ^TLogRequest; TLogRequest = record LogText : String; FileName : String; end; TThreadFileLog = class(TObject) private FThreadPool: TThreadPool; procedure HandleLogRequest(Data: Pointer; AThread: TThread); public constructor Create(); destructor Destroy; override; procedure Log(const FileName, LogText: string); end; implementation uses System.SysUtils; procedure LogToFile(const FileName, LogString: String); var c1, c2 : dword; i:integer; s:string; begin try c1 := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0); i:=0; while c1=INVALID_HANDLE_VALUE do begin if i>200 then //2 secs elapsed and haven't been able to access log exit else begin inc(i); Sleep(10); c1 := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0); end; end; if c1 <> INVALID_HANDLE_VALUE then begin SetFilePointer(c1, 0, nil, FILE_END); S:=DateTimeToStr(Now)+': '+LogString+sLineBreak+SLineBreak; WriteFile(c1, s[1], length(s)*SizeOf(Char), c2, nil); CloseHandle(c1); end except end; end; constructor TThreadFileLog.Create(); begin FThreadPool := TThreadPool.Create(HandleLogRequest, 1); end; destructor TThreadFileLog.Destroy; begin FThreadPool.Free; inherited; end; procedure TThreadFileLog.HandleLogRequest(Data: Pointer; AThread: TThread); var Request: PLogRequest; begin Request := Data; try LogToFile(Request^.FileName, Request^.LogText); finally Dispose(Request); end; end; procedure TThreadFileLog.Log(const FileName, LogText: string); var Request: PLogRequest; begin New(Request); Request^.LogText := LogText; Request^.FileName := FileName; FThreadPool.Add(Request); end; end. Other possible useful information: The Application The application created in the project file is a TWebApplication not a TApplication. It exposes MaxConnections, ActiveConnections and InActiveConnections, CachedConnection. MaxConnections The maximum no of connections to your DLL. By default this is 32. So up to 32 new threads will be spawned (but note comments re NumberOfThreads below). You can increase or decrease this number as requi ActiveConnections This reports the number of connections currently in use. InactiveConnections If CachedConnections is true new threads are not disposed of after use, but cached for future use. whilst cached they are recorded as InactiveConnections. ISAPIThreadPool unit This is added by default and deals with threading. NumberOfThreads Global variable declared in the ISAPIThreadPool unit. It is set by default to 25. So whilst the default MaxConnections is 32 (so you can theoretically have 32 connections) unless you increase the NumberOfThreads you will in effect be allowed only 25 connections! The example project code deals with this.
  17. Mark Williams

    ISAPI DLL concurrent requests

    WebGarden? Is this when you increase the Maximum Worker Processes of the Application Pool above 1? If so, it works when I do this, but I appreciate it is not the solution and I have reduced it again to 1. Nothing in WebModule's OnCreate and onDestroy. BTW what event should I use to free objects etc? Nothing in the Event Viewer. I am writing everything to a log file. See my last but one post. Also, can you advise me how I can monitor the ActiveConnections property from the WebModule? I can't figure out how to access the Application object. BTW thanks. I'll get some test projects uploaded asap.
  18. Mark Williams

    ISAPI DLL concurrent requests

    Web Web.config: <?xml version="1.0" encoding="UTF-8"?> <configuration> <system.net> <mailSettings> <smtp from=""> <network host="" /> </smtp> </mailSettings> </system.net> </configuration> I'll have to work on a MCVE. But do you have any suggestionsat this point why multi-threading may not to be working?
  19. Mark Williams

    ISAPI DLL concurrent requests

    I think maybe I didn't explain myself very well. Apologies. Plus I have managed to improve things a little since my first post. I noticed that my DLL was including Madshi's exception units. I have removed these and there is now an improvement. Whereas the DLL appeared to be hanging on concurrent calls resulting in a timeout error, my client app now gets a ENetHTTPClientException "Server returned an invalid or unrecognised response". But whereas the DLL was freezing it now continues to run albeit rejecting any concurrent requests. I don't know what code to post as I am not getting any exceptions for any of my event handlers (I do have the event wrapped in try except blocks). I add a log entry in the BeforeDispatch event to advise what method etc has been called and one in the AfterDispatch to advise when it is done. I log the http response codes issued by the handlers when they are done. I have set up a test app to fire off rapid requests via threads to download 14 files at slightly staggered intervals (10ms) so that they overlap. Accordingly, there are probably 14 concurrent requests being made at the same time. Only 1 returns successfully all the others fire a ENetHTTPException. The first request executes correctly. I log the BeforeDispatch and AfterDispatch events and these fire ok. I also log the Response returned by the dlls' download method and this logs correctly for the first call, which means it has completed okay. For concurrent calls, the BeforeDispatch event is not called. Also, within each method I log the fact it has been called as the first line of the method's code. For concurrent calls, the DLL is not even entering the handler for the method being called. It does not trigger any exception even though wrapped in a try except block. I think the problem is a threading problem within the DLL, but I don't understand why. As mentioned above, if within the ISS ApplicationPool for the DLL I increase the number of Maximum Working Processes above 1 then all is okay (presumably as long as I don't make concurrent requests that exceed the max working processes) save for the fact that each (concurrent?) request seems to run a new DLL instance rather than making use of DLL threading. Whilst this sort of resolves the problem, I am certain it is not the way to do this from all I have read on the web. To get threading working I can't work out whether there is something I need to configure on my website (it is configured for limitless connections), the ApplicationPool (can't see anything) or the app (and I used the wizard to create it so I would have thought not.. Below is the code from the DLL dpr file: library MWCWebServer; uses Winapi.ActiveX, System.Win.ComObj, System.SysUtils, Web.WebBroker, Web.Win.ISAPIApp, Web.Win.ISAPIThreadPool, WebModule in 'WebModule.pas' {WebModule1: TWebModule}; {$R *.res} exports GetExtensionVersion, HttpExtensionProc, TerminateExtension; begin try CoInitFlags := COINIT_MULTITHREADED; Application.Initialize; Application.WebModuleClass := WebModuleClass; {Application.MaxConnections:=200; NumberOfThreads:=Application.MaxConnections; Application.CacheConnections:=true;} Application.Run; end. I have tried setting MaximumConnections, CacheConnections. NumberOfThreads and none of these seem to do anything. But I can't imagine for a moment that the default no of threads for a webmodule is 1. So I guess it has to be something else. I have tried to monitor how many connections there are. But within the webModule I cannot figure out how to access the Application property!
  20. This is in connection with the AddHeader function of the IHTTPRequest. I'm trying to add a multiline text which also contains numerous characters such as = signs etc. I am getting the above error as a result. Is it even possible to do this? Should I be formatting the text in some way before adding it? UrlEncode?
  21. Thanks. I do write both server and client. I have it working with multiformdata at moment and am going to leave it at that for now as this is a fairly minor part of a hefty development and I have managed to get myself bogged down in it. I will revisit it when I have a bit more time and do some research online. But thanks for all your help.
  22. I tried Base64. This may have worked, but I think I'm running into a limitation as to permissible size of header. I absolutely agree that it is food for the payload, but I have tried and failed to submit/receive content using THTTPClient and TWebRequest components. My code for submitting the component is below, but when I try and access the TWebRequest's content it remains stubbornly empty. FHttpClient:=THttpClient.Create; try With FHttpClient do begin data:=TStringStream.Create; try data.WriteString(content); ContentType:='text/html'; Accept:='text/plain, */*'; AcceptCharSet:='utf-8'; LResponse:=FHttpClient.Post(Script, data); finally data.Free; end; end; I have now resorted to submitting it as multipartdata, which works, but seems overkill just to submit a string.
  23. Hi Thanks, As far as I can see (and with my limited understanding of the protocols) it is compliant with the requirements for headers. Each line break is coded +Chr(32)+sLineBreak; There are no line breaks inserted other than in this manner, but I still get the error. Does anyone know of any function for encoding text in away that will satisfy the protocol?
  24. Trying to analyse the structure of a table using TFDMetaDataQuery with MetaDataKind set to mkTableFields. This returns just about everything I need apart from (as far I can see) a field's default value. I can see it returns TFDDataAttributes and that this includes an attribute caDefault which advises if a field has a default value, but I can see now way of ascertaining what the default value is. The metadata structure can be found at: http://docwiki.embarcadero.com/RADStudio/Rio/en/Metadata_Structure_(FireDAC)
×