How to create a TSvnItem for a specified URL - delphi

Short Version
How to perform svn cat in Delphi's subversion api wrapper?
Long Version
I want to get the contents of a file in subversion.
Here's a random public repository that lets you cat (i.e. display the contents of) a file:
>svn cat http://svn.code.sf.net/p/unicon/code/trunk/unicon/README
Unicon 13.x README
This is the Unicon distribution. Please tell us
where it compiles, and when and how it doesn't.
...snip...
So that works.
Now how to do it in Delphi?
How do i read the contents (i.e. cat) a file in Delphi's Subversion API wrapper?
Based on this Stackoverflow answer, i try:
SvnItem: TSvnItem;
SvnItem := TSvnItem.Create(SvnClient, nil, 'http://svn.code.sf.net/p/unicon/code/trunk/unicon/README');
Unfortunately the call to create a TSvnItem throws an exception:
EAprError: The given path is misformatted or contained invalid characters
So what am i doing wrong?
For The Lazy
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, SvnClient;
procedure Main;
var
SvnClient: TSvnClient;
SvnItem: TSvnItem;
url: string;
begin
// Set the global variable where the subversion DLLs can be found.
BaseDllDir := ExtractFilePath(ParamStr(0)); //Setting a global variable (yes, seriously)
SvnClient := TSvnClient.Create;
SvnClient.Initialize;
url := 'http://svn.code.sf.net/p/unicon/code/trunk/unicon/README';
SvnItem := TSvnItem.Create(SvnClient, nil, url);
end;
begin
try
Main;
except
on E: Exception do
begin
ExitCode := 1;
Writeln(Format('[%s] %s', [E.ClassName, E.Message]));
end;
end;
end.
See also
https://sourceforge.net/projects/radstudioverins/
Programmatically adding, deleting and committing files into a subversion repository using Delphi
reading SVN:externals from working copy

It's not an answer; but it is a workaround for the next guy.
Thing that should work (but doesn't)
function GetSubversionFileContents(Client: TSvnClient; Url: string): TStream;
var
item: TSvnItem;
buffer: TBytes;
ms: TMemoryStream;
begin
//WARNING: This code doesn't work.
item := TSvnItem.Create(Client, nil, Url); //throws exception
buffer := item.GetBaseFile;
item.Free;
ms := TMemoryStream.Create;
ms.Size := Length(buffer);
if Length(buffer) > 0 then
ms.Write(buffer[0], Length(buffer));
ms.Seek(0, soFromBeginning);
end;
Hack workaround
function GetSubversionFileContents(Client: TSvnClient; Url: string): TStream;
var
http: IWinHttpRequest;
stm: IStream;
begin
// Issue the WebDAV HTTP GET ourselves.
// (SVN is just a wrapper around WebDAV)
http := CoWinHttpRequest.Create;
http.Open('GET', url, False);
if Client.UserName <> '' then
http.SetCredentials(Client.UserName, Client.Password, HTTPREQUEST_SETCREDENTIALS_FOR_SERVER);
http.Send(EmptyParam);
stm := IUnknown(http.ResponseStream) as IStream;
Result := TOleStream.Create(stm);
//TODO: Re-write the rest of the Subversion API into something modern (i.e. post-1995).
end;
Subversion is just an implementation of WebDAV - which is just REST but without the cool name. Which means you can issue a GET to the url to get the contents yourself directly.

Related

How to correctly download csv without 'save as' dialog' using TWebbrowser and Delphi?

I subscribe to a secure https web page containing a button that downloads some data as csv. I am trying to automate the download without the 'save as' dialog appearing but always seem to get an empty file downloaded. I suspect it has something to do with file type I'm using with IdHttp as most of my code works correctly.
Please can anyone help with my use of IdHttp or see where else I am going wrong?
The download button on the site calls some javascript to perform the download as follows
<a class="dlCSV" href="javascript:void(0);" onclick="dl_module.DownloadCsv();return false;">Download in CSV format…</a>
In Delphi I use a TWeb browser to log on securely and navigate to the page.
Clicking the download button in the TwebBrowser by hand shows the 'save as' dialog and then correctly downloads the csv data, defaulting to the filename 'data.csv'.
Automating clicking the button using execScript (below) also works, again showing the 'save as' dialog and correctly downloading the data with the same default filename.
procedure TForm1.BtnClickDownloadbuttonClick(Sender: TObject);
var TheDocument : IHTMLDocument2; // current HTML document
HTMLWindow: IHTMLWindow2; // parent window of current HTML document
begin
TheDocument := WebBrowser1.Document as IHTMLDocument2; // Get reference to current document
if not Assigned(TheDocument) then
Exit;
HTMLWindow := TheDocument.parentWindow; // Get parent window of current document
if Assigned(HTMLWindow) then
try
HTMLWindow.execScript('dl_module.DownloadCsv()', 'JavaScript'); // execute JS function to do download
except
on E : Exception do
begin
showmessage ('Exception class name = '+E.ClassName+ slinebreak
+ 'Exception message = '+E.Message);
end //on E
end;
end;
Then I added TLama's code from here How do I keep an embedded browser from prompting where to save a downloaded file? to use IDownloadManager to intercept the download and prevent the 'save as' dialog. This is where it seems to go wrong as I then get an empty file downloaded, and not with the name data.csv.
My code for function TWebBrowser.Download, TWebBrowser.InvokeEvent, function TWebBrowser.QueryService and TForm1.FormCreate are identical to that provided by TLama in the link above.
My procedure TForm1.Button1Click is the same except that I changed the download function being called to the one on my page by changing the line
HTMLWindow.execScript('SRT_stocFund.Export()', 'JavaScript');
to
HTMLWindow.execScript('dl_module.DownloadCsv()', 'JavaScript');
and my procedure TForm1.BeforeFileDownload is identical except that because I'm on a secure site I added the variable
var
LHandler: TIdSSLIOHandlerSocketOpenSSL; //<< on a https site
and after creating the Filestream I added the lines
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
IdHTTP.IOHandler := LHandler;
The issue seems to be in procedure TForm1.BeforeFileDownload where I note that the value of FileSource is
https://www.the_web_site_name/Ashx/GenericCSV.ashx.
There is a short delay while IdHTTP.Get(FileSource, FileStream); executes and then a file is created on my hard disc but called 'GenericCSV.ashx' (not data.csv) and the file is zero bytes long and completely empty.
Any ideas why its not downloading the file called data.csv (Do I somehow have to execute GenericCSV.ashx as well? if so how?)
For info here is my version of procedure TForm1.BeforeFileDownload
procedure TForm1.BeforeFileDownload(Sender: TObject; const FileSource: WideString; var Allowed: Boolean);
var
IdHTTP: TIdHTTP;
FileTarget: string;
FileStream: TMemoryStream;
LHandler: TIdSSLIOHandlerSocketOpenSSL; // added as its a https site
begin
FileSourceEdit.Text := FileSource;
Allowed := ShowDialogCheckBox.Checked;
if not Allowed then
try
IdHTTP := TIdHTTP.Create(nil);
try
FileStream := TMemoryStream.Create;
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil); //<<< added as its a https site
IdHTTP.IOHandler := LHandler; //<<< added as its a https site
try
IdHTTP.HandleRedirects := True;
IdHTTP.Get(FileSource, FileStream);
FileTarget := IdHTTP.URL.Document;
if FileTarget = '' then
FileTarget := 'File';
FileTarget := ExtractFilePath(ParamStr(0)) + FileTarget;
FileStream.SaveToFile(FileTarget);
finally
FileStream.Free;
end;
finally
IdHTTP.Free;
end;
ShowMessage('Downloading finished! File has been saved as:' + sLineBreak +
FileTarget);
except
on E: Exception do
ShowMessage(E.Message);
end;
end;
After you login, you can use this code to retrieve cookies from TWebBrowser
procedure GetHttpOnlyCookie(const AUrl: string; var ACookies: string);
const
INTERNET_COOKIE_HTTPONLY = 8192;
var
i: Integer;
hModule: THandle;
InternetGetCookieEx: function(lpszUrl, lpszCookieName, lpszCookieData
: PAnsiChar; var lpdwSize: DWORD; dwFlags: DWORD; lpReserved: pointer)
: BOOL; stdCall;
CookieSize: DWORD;
CookieData: PAnsiChar;
begin
LoadLibrary('wininet.dll');
hModule := GetModuleHandle('wininet.dll');
if (hModule <> 0) then
begin
#InternetGetCookieEx := GetProcAddress(hModule, 'InternetGetCookieExA');
if (#InternetGetCookieEx <> nil) then
begin
CookieSize := 1024;
Cookiedata := AllocMem(CookieSize);
try
if InternetGetCookieEx(PAnsiChar(AUrl), nil, Cookiedata, CookieSize, INTERNET_COOKIE_HTTPONLY, nil) then
begin
ACookies:=CookieData;
end;
finally
FreeMem(Cookiedata);
end;
end;
end;
end;
Then you just parse your cookies and add them (you have to create CookieManager in IdHTTP first)
IdHTTP1.CookieManager.AddServerCookie();
Then you start your download and it should work if you passed all parameters correctly (unfortunately, it is not possible to find out what your site requires).
Thank you smooty86 but I think its time I gave up trying to doing it this way and simply parse the page I can see.
I don't mind trying to understand code and adapting it to my needs but its so much harder trying to follow hints and suggestions when I'm working in the dark and especially don't know what parameters are needed everywhere. (I'm not daft, I've been programming for nearly 30 years and have spent over 4 years developing this particular data processing application but rarely touch web stuff)
However, the progress so far is...
Running your GetHttpOnlyCookie code after a successful login using automated filling in of the fields and clicking the login button returned an empty string so I used this code instead that at least seemed to return something that looked a little similar to your cookie string, ie seveveral strings separated by semicolons, most being name=value. (IdCookieManager1 is connected to IdHttp)
CookieList := Tstringlist.Create ;
try
CookieList.Delimiter := ';' ;
document := WebBrowser1.Document as IHTMLDocument2;
CookieList.DelimitedText := document.cookie;
for i := 0 to CookieList.Count-1 do
IdCookieManager1.AddCookie(CookieList[i],LOGIN_URL)
finally
CookieList.Free;
end;
Then in my original procedure BeforeFileDownload I try to log IdHttp into the site as well using code I adapted from here Log in to website from Delphi and the the cookies held in the cookie manager.
Displaying the string returned showed lots of HTML that appeared to represent the oringinal log in page and not the page you see after log in
procedure TFrmInportGrades.BeforeFileDownload(Sender: TObject; const FileSource: WideString; var Allowed: Boolean);
var
FileTarget: string;
FileStream: TMemoryStream;
request : Tstringlist;
s : string;
begin
FileSourceEdit.Text := FileSource;
Allowed := ShowDialogCheckBox.Checked;
if not Allowed then
begin
try
FileStream := TMemoryStream.Create;
IdHTTP.CookieManager := IdCookieManager1;
s := LogInIdHttp; //<<<< log in the IdHttp
showmessage(s); //<<<< debug
IdHTTP.Get(FileSource, FileStream);
FileTarget := IdHTTP.URL.Document;
if FileTarget = '' then
FileTarget := 'File';
FileTarget := ExtractFilePath(ParamStr(0)) + FileTarget;
FileStream.SaveToFile(FileTarget);
finally
FileStream.Free;
end;
ShowMessage('Downloading finished! File has been saved as:' + sLineBreak +
FileTarget);
end;
end;
The login code I used is below but I don't really know what I am doing here or what needs to be put into the Request.Add() parameters. I used 'Inspect element' from firefox to get the name of the user and password boxes and put the correct users name and password after the '=' sign in lines {3} and {4}. In lines {2},{6} and {7} I put the url of the log in site. I've no idea what lines {1}, {2}, {5} do or even if they are correct or necessary
function TFrmInportGrades.LogInIdHttp: string;
var
Request: TStringList;
Response: TMemoryStream;
LHandler: TIdSSLIOHandlerSocketOpenSSL; // added as its a https site
begin
Result := '';
try
Response := TMemoryStream.Create;
try
Request := TStringList.Create;
try
{1} Request.Add('op=login');
{2} Request.Add('redirect=https://www.thewebsite.com/Login.aspx' );
{3} Request.Add('ctl00$ctl00$Body$Body$loginManager$ctl00$loginEmailInput=usernme');
{4} Request.Add('ctl00$ctl00$Body$Body$loginManager$ctl01$passwordInput=password'});
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil); //<<< added as its a https site
IdHTTP.IOHandler := LHandler; //<<< added as its a https site
IdHTTP.AllowCookies := True;
IdHTTP.HandleRedirects := True;
{5} IdHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
{6} IdHTTP.Post('https://www.thewebsite.com/Login.aspx', Request, Response);
{7} Result := IdHTTP.Get('https://www.thewebsite.com/Login.aspx');
finally
Request.Free;
end;
finally
Response.Free;
end;
except
on E: Exception do
ShowMessage(E.Message);
end;
end;
The net result of all this is that I don't get a file created at all now, not even a zero byte one. This all seems very overcomplicated simply to avoid or automate the 'Save As' dialog and is requiring lots of code that I won't be able to maintan afterwards. Unless somebody has a simpler solution I'll just parse what I can see (BTW I tried TEmbeddedWebBrowser but there is so little documentation for it I couldn't see how to make it download correctly. Might try again later.) Thank you for trying to help!

Download a html page using url with parameters [duplicate]

There's a web services I want to call in my application, I can use it with importing the WSDL or by just use "HTTP GET" with the URL and parameters, so I prefer the later because it's simple thing.
I know I can use indy idhttp.get, to do the job, but this is very simple thing and I don't want to add complex indy code to my application.
UPDATE: sorry if I was not clear, I meant by "not to add complex indy code", that I don't want add indy components for just this simple task, and prefer more lighter way for that.
Calling a RESTful web service using Indy is pretty straight forward.
Add IdHTTP to your uses clause. Remember that IdHTTP needs the "HTTP://" prefix on your URLs.
function GetURLAsString(const aURL: string): string;
var
lHTTP: TIdHTTP;
begin
lHTTP := TIdHTTP.Create;
try
Result := lHTTP.Get(aURL);
finally
lHTTP.Free;
end;
end;
You could use the WinINet API like this:
uses WinInet;
function GetUrlContent(const Url: string): string;
var
NetHandle: HINTERNET;
UrlHandle: HINTERNET;
Buffer: array[0..1024] of Char;
BytesRead: dWord;
begin
Result := '';
NetHandle := InternetOpen('Delphi 5.x', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if Assigned(NetHandle) then
begin
UrlHandle := InternetOpenUrl(NetHandle, PChar(Url), nil, 0, INTERNET_FLAG_RELOAD, 0);
if Assigned(UrlHandle) then
{ UrlHandle valid? Proceed with download }
begin
FillChar(Buffer, SizeOf(Buffer), 0);
repeat
Result := Result + Buffer;
FillChar(Buffer, SizeOf(Buffer), 0);
InternetReadFile(UrlHandle, #Buffer, SizeOf(Buffer), BytesRead);
until BytesRead = 0;
InternetCloseHandle(UrlHandle);
end
else
{ UrlHandle is not valid. Raise an exception. }
raise Exception.CreateFmt('Cannot open URL %s', [Url]);
InternetCloseHandle(NetHandle);
end
else
{ NetHandle is not valid. Raise an exception }
raise Exception.Create('Unable to initialize Wininet');
end;
source: http://www.scalabium.com/faq/dct0080.htm
The WinINet API uses the same stuff InternetExplorer is using so you also get any connection and proxy settings set by InternetExplorer for free.
Actually code in accepted answer did't work for me. So I modified it a little bit so it actually returns String and gracefully closes everything after execution. Example returns retrieved data as UTF8String so it will work well for ASCII as well as for UTF8 pages.
uses WinInet;
function GetUrlContent(const Url: string): UTF8String;
var
NetHandle: HINTERNET;
UrlHandle: HINTERNET;
Buffer: array[0..1023] of byte;
BytesRead: dWord;
StrBuffer: UTF8String;
begin
Result := '';
NetHandle := InternetOpen('Delphi 2009', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if Assigned(NetHandle) then
try
UrlHandle := InternetOpenUrl(NetHandle, PChar(Url), nil, 0, INTERNET_FLAG_RELOAD, 0);
if Assigned(UrlHandle) then
try
repeat
InternetReadFile(UrlHandle, #Buffer, SizeOf(Buffer), BytesRead);
SetString(StrBuffer, PAnsiChar(#Buffer[0]), BytesRead);
Result := Result + StrBuffer;
until BytesRead = 0;
finally
InternetCloseHandle(UrlHandle);
end
else
raise Exception.CreateFmt('Cannot open URL %s', [Url]);
finally
InternetCloseHandle(NetHandle);
end
else
raise Exception.Create('Unable to initialize Wininet');
end;
Hope it helps for somebody like me who was looking for easy code how to retrieve page content in Delphi.
Cheers, Aldis :)
In newer Delphi versions it is better to use THTTPClient from System.Net.HttpClient unit, since it is standard and cross-platform. Simple example is
function GetURL(const AURL: string): string;
var
HttpClient: THttpClient;
HttpResponse: IHttpResponse;
begin
HttpClient := THTTPClient.Create;
try
HttpResponse := HttpClient.Get(AURL);
Result := HttpResponse.ContentAsString();
finally
HttpClient.Free;
end;
end;
If it's okay to download to a file, you can use TDownloadURL from the ExtActns unit. Much simpler than using WinInet directly.
procedure TMainForm.DownloadFile(URL: string; Dest: string);
var
dl: TDownloadURL;
begin
dl := TDownloadURL.Create(self);
try
dl.URL := URL;
dl.FileName := Dest;
dl.ExecuteTarget(nil); //this downloads the file
finally
dl.Free;
end;
end;
It's also possible to get progress notifications when using this. Simply assign an event handler to TDownloadURL's OnDownloadProgress event.
Using Windows HTTP API might be easy too.
procedure TForm1.Button1Click(Sender: TObject);
var http: variant;
begin
http:=createoleobject('WinHttp.WinHttpRequest.5.1');
http.open('GET', 'http://lazarus.freepascal.org', false);
http.send;
showmessage(http.responsetext);
end;
https://msdn.microsoft.com/ru-ru/library/windows/desktop/aa382925.aspx
http://forum.lazarus.freepascal.org/index.php?topic=14609.0
Save a file downloaded via WinHTTP to disk, using Delphi XE
How to use "WinHttp.WinHttpRequest.5.1" asynchronously?
http://www.delphigroups.info/2/4/217167.html
https://github.com/fabriciocolombo/delphi-rest-client-api/blob/master/src/HttpConnectionWinHttp.pas
In the code above I imply that COM was already initialized for the main VCL thread. Reportedly it might not be always the case for simplistic apps or for LCL apps. Also it would definitely not be the case for async (multithread) work.
Below is the snippet from a real code running. Note - the functionality is bonus. It is not required to work. So while I do issue requests, I do not care about their results, that result is ignored and dumped.
procedure TfmHaspList.YieldBlinkHTTP(const LED: boolean; const Key_Hardware_ID: cardinal);
var URL: WideString;
begin
URL := 'http://127.0.0.1:1947/action.html?blink' +
IfThen( LED, 'on', 'off') + '=' + IntToStr(Key_Hardware_ID);
TThread.CreateAnonymousThread(
procedure
var Request: OleVariant;
begin
// COM library initialization for the current thread
CoInitialize(nil);
try
// create the WinHttpRequest object instance
Request := CreateOleObject('WinHttp.WinHttpRequest.5.1');
// open HTTP connection with GET method in synchronous mode
Request.Open('GET', URL, False);
// set the User-Agent header value
// Request.SetRequestHeader('User-Agent', 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0');
// sends the HTTP request to the server, the Send method does not return
// until WinHTTP completely receives the response (synchronous mode)
Request.Send;
// // store the response into the field for synchronization
// FResponseText := Request.ResponseText;
// // execute the SynchronizeResult method within the main thread context
// Synchronize(SynchronizeResult);
finally
// release the WinHttpRequest object instance
Request := Unassigned;
// uninitialize COM library with all resources
CoUninitialize;
end;
end
).Start;
end;
Use the Synapse TCP/IP function in the HTTPSEND unit (HTTPGetText, HTTPGetBinary). It will do the HTTP pull for you and doesn't require any external DLL's other than Winsock. The latest SVN release works perfectly well in Delphi 2009. This uses blocking function calls, so no events to program.
Update: The units are very light, and are not component based. The latest version from SVN runs perfectly well in Delphi XE4 also.
If your application is Windows-only, I would suggest using WinSock. It's simple enough, allows to execute any HTTP request, can work both synchronously and asynchronously (using non-blocking WSASend/WSARecv with callbacks or good old send/recv in a dedicated thread).

Delphi - MemoryStream or FileStream

I am downloading an EXE file from internet using Indy (idHTTP), and I can use memorystream or filestream to save it to disk, but I really do not know if there is any difference between them (maybe in the result structure of the file?). I could't find yet an answer for this.
Where, here are 2 simple functions to simulate what I am doing:
Function DownloadMS(FUrl, Dest: String): Boolean;
var
Http: TIdHTTP;
Strm: TMemoryStream;
Begin
Result := False;
Http := TIdHTTP.Create;
Strm := TMemoryStream.Create;
With Http, Strm Do
Try
Try
Get(FUrl, Strm);
If (Size > 0) Then
Begin
Position := 0;
SaveToFile(Dest);
Result := True;
end;
Except
end;
Finally
Strm.Free;
Http.Free;
end;
end;
Function DownloadFS(FUrl, Dest: String): Boolean;
var
Http: TIdHTTP;
Strm: TFileStream;
Begin
Result := False;
Http := TIdHTTP.Create;
Strm := TFileStream.Create(Dest, fmCreate);
With Http, Strm Do
Try
Try
Get(FUrl, Strm);
Result := (Size > 0);
Except
end;
Finally
Strm.Free;
Http.Free;
end;
end;
What you experts think about using one or other type (memorystream or filestream)? Is there any difference in the structure of the EXE file when using one or other type? What type is recommended?
Thank you! Have a nice weekend!
There is no difference between TMemoryStream or TFileStream from the stream point of view.
They are both streams and hold a stream of bytes and are both derived from TStream.
You can implement your function generalized like this
function DownloadToStream( const AUrl : String; ADest : TStream ): Boolean;
var
LHttp: TIdHTTP;
begin
LHttp := TIdHTTP.Create;
try
LHttp.Get( AUrl, ADest );
Result := ADest.Size > 0;
finally
LHttp.Free;
end;
end;
and call it with a TFileStream
var
LStream : TStream;
begin
LStream := TFileStream.Create( 'MyFile.exe', fmCreate );
if DownloadToStream( '', LStream ) then
...
end;
or TMemoryStream or whatever stream instance you like
In many cases there will be no point in putting an intermediate memory stream in between the download and the file. All that will do is consume memory because you have to put the entire file in memory before you can put it to disk. Using a file stream directly avoids that issue.
The main situation where the file stream option has problems is if you want to be sure that you've downloaded the entire file successfully before saving to disk. For example, if you are overwriting a previous version of a file, you may want to download it, check a hash signature, and only then overwrite the original file. In that scenario you need to put the file to some temporary location before over-writing. You could use a memory stream, or you could use a file stream using a temporary file name.

How to employ DPROJ <ProjectGUID> within Delphi code?

I want to use a GUID to uniquely identify my Application and to get at this value from within the code. I see that there is a GUID that would be ideal in the DPROJ:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{D4DB842C-FB4C-481B-8952-77DA04E37102}</ProjectGuid>
Does this get into the exe anywhere, eg as a resource? If not, what is the neatest way of linking in this GUID value into my exe file and reading it in code. The above GUID resides in a dedicated text file and is pasted into the DPROJ with my DprojMaker tool, so I can INCLUDE it in anything you might suggest.
Thanks
AFAIK the <ProjectGUID> is not embedded in the Exe file, but you can create an application to read the project guid and insert as a resource in your exe.
Check this sample app which read a file a create/updates a resource in a exe.
program UpdateResEXE;
{$APPTYPE CONSOLE}
uses
Classes,
Windows,
SysUtils;
//you can improve this method to read the ProjectGUID value directly from the dproj file using XML.
procedure UpdateExeResource(Const Source, ResourceName, ExeFile:string);
var
LStream : TFileStream;
hUpdate : THANDLE;
lpData : Pointer;
cbData : DWORD;
begin
LStream := TFileStream.Create(Source,fmOpenRead or fmShareDenyNone);
try
LStream.Seek(0, soFromBeginning);
cbData:=LStream.Size;
if cbData>0 then
begin
GetMem(lpData,cbData);
try
LStream.Read(lpData^, cbData);
hUpdate:= BeginUpdateResource(PChar(ExeFile), False);
if hUpdate <> 0 then
if UpdateResource(hUpdate, RT_RCDATA, PChar(ResourceName),0,lpData,cbData) then
begin
if not EndUpdateResource(hUpdate,FALSE) then RaiseLastOSError
end
else
RaiseLastOSError
else
RaiseLastOSError;
finally
FreeMem(lpData);
end;
end;
finally
LStream.Free;
end;
end;
begin
try
if ParamCount<>3 then
begin
Writeln('Wrong parameters number');
Halt(1);
end;
Writeln(Format('Adding/Updating resource %s in %s',[ParamStr(2), ParamStr(3)]));
UpdateExeResource( ParamStr(1), ParamStr(2), ParamStr(3));
Writeln('Done');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Now from your app, you can use the Post build events to call this application on this way
"C:\The path where is the tool goes here\UpdateResEXE.exe" "C:\The path of the file which contains the ProjectGUID goes here\Foo.txt" Project_GUID "$(OUTPUTPATH)"
And use like so :
{$APPTYPE CONSOLE}
uses
Windows,
Classes,
System.SysUtils;
function GetProjectGUID : string;
var
RS: TResourceStream;
SS: TStringStream;
begin
RS := TResourceStream.Create(HInstance, 'Project_GUID', RT_RCDATA);
try
SS:=TStringStream.Create;
try
SS.CopyFrom(RS, RS.Size);
Result:= SS.DataString;
finally
SS.Free;
end;
finally
RS.Free;
end;
end;
begin
try
Writeln(Format('Project GUID %s',[GetProjectGUID]));
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
Why not just hard-code your own GUID inside your code itself? The Code Editor has a CTRL+SHIFT+G keyboard shortcut for generating a new GUID string at the current active line of code. You can tweak that declaration into a constant variable for your code to use as needed, eg:
const
MyGuid: TGUID = '{04573E0E-DE08-4796-A5BB-E5F1F17D51F7}';

How do I use WMI with Delphi without drastically increasing the application's file size?

I am using Delphi 2010, and when I created a console application that prints "Hello World", it takes 111 kb. If I want to query WMI with Delphi, I add WBEMScripting_TLB, ActiveX, and Variants units to my project. If I perform a simple WMI query, my executable size jumps to 810 kb. I
Is there anyway to query WMI without such a large addition to the size of the file? Forgive my ignorance, but why do I not have this issue with C++?
Here is my code:
program WMITest;
{$APPTYPE CONSOLE}
uses
SysUtils,
WBEMScripting_TLB,
ActiveX,
Variants;
function GetWMIstring(wmiHost, root, wmiClass, wmiProperty: string): string;
var
Services: ISWbemServices;
SObject: ISWbemObject;
ObjSet: ISWbemObjectSet;
SProp: ISWbemProperty;
Enum: IEnumVariant;
Value: Cardinal;
TempObj: OLEVariant;
loc: TSWbemLocator;
SN: string;
i: integer;
begin
Result := '';
i := 0;
try
loc := TSWbemLocator.Create(nil);
Services := Loc.ConnectServer(wmiHost, root {'root\cimv2'}, '', '', '', '',
0, nil);
ObjSet := Services.ExecQuery('SELECT * FROM ' + wmiClass, 'WQL',
wbemFlagReturnImmediately and wbemFlagForwardOnly, nil);
Enum := (ObjSet._NewEnum) as IEnumVariant;
if not VarIsNull(Enum) then
try
while Enum.Next(1, TempObj, Value) = S_OK do
begin
try
SObject := IUnknown(TempObj) as ISWBemObject;
except SObject := nil;
end;
TempObj := Unassigned;
if SObject <> nil then
begin
SProp := SObject.Properties_.Item(wmiProperty, 0);
SN := SProp.Get_Value;
if not VarIsNull(SN) then
begin
if varisarray(SN) then
begin
for i := vararraylowbound(SN, 1) to vararrayhighbound(SN, 1) do
result := vartostr(SN[i]);
end
else
Result := SN;
Break;
end;
end;
end;
SProp := nil;
except
Result := '';
end
else
Result := '';
Enum := nil;
Services := nil;
ObjSet := nil;
except
on E: Exception do
Result := e.message;
end;
end;
begin
try
WriteLn('hello world');
WriteLn(GetWMIstring('.', 'root\CIMV2', 'Win32_OperatingSystem',
'Caption'));
WriteLn('done');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
UPDATE:
When I compile the following sample from MSDN with Microsoft Visual C++ 2008 (console application), it is 76 kb.
#Mick, you can access the WMI without import the WBEMScripting from Delphi, using the IBindCtx and IMoniker interfaces.
Check this simple code (Tested in Delphi 2010 and Windows 7), the exe file size is 174 kb.
program WmiTest;
{$APPTYPE CONSOLE}
uses
SysUtils
,ActiveX
,ComObj
,Variants;
function GetWMIstring(wmiHost, root, wmiClass, wmiProperty: string): string;
var
objWMIService : OLEVariant;
colItems : OLEVariant;
colItem : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
function GetWMIObject(const objectName: String): IDispatch;
var
chEaten: Integer;
BindCtx: IBindCtx;//for access to a bind context
Moniker: IMoniker;//Enables you to use a moniker object
begin
OleCheck(CreateBindCtx(0, bindCtx));
OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten, Moniker));//Converts a string into a moniker that identifies the object named by the string
OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));//Binds to the specified object
end;
begin
objWMIService := GetWMIObject(Format('winmgmts:\\%s\%s',[wmiHost,root]));
colItems := objWMIService.ExecQuery(Format('SELECT * FROM %s',[wmiClass]),'WQL',0);
oEnum := IUnknown(colItems._NewEnum) as IEnumVariant;
while oEnum.Next(1, colItem, iValue) = 0 do
begin
Result:=colItem.Properties_.Item(wmiProperty, 0); //you can improve this code ;) , storing the results in an TString.
end;
end;
begin
try
CoInitialize(nil);
try
WriteLn(GetWMIstring('.', 'root\CIMV2', 'Win32_OperatingSystem','Caption'));
Readln;
finally
CoUninitialize;
end;
except
on E:Exception do
Begin
Writeln(E.Classname, ': ', E.Message);
Readln;
End;
end;
end.
ActiveX and/or Variants would add 36KB at most.
It's WBEMScripting_TLB that adds about 650KB to your project.
It's not huge in lines of code but more than declaring quite a few classes, interfaces and constants, it includes OleServer in its uses.
And THAT brings the whole Controls unit with its heavy baggage.
when delphi builds an executable, it statically links in the delphi runtime libraries. this results in a larger executable, however as the rtl is statically linked, deployment is easier, and there's an element of future proofing.
you can configure delphi to use runtime packages by enabling Build with runtime packages in the Project / Options. however you'll have to ensure the delphi rtl packages are available, and you may encounter issues with debugging.
this static vs runtime linking behaviour probably explains the differences you're seeing between delphi and c++.
The difference you're seeing, in part anyway, is because VC++ uses dynamically linked runtime libraries by default; the runtime libraries are loaded from DLLs when the app runs, and therefore the code isn't present in the executable.
Delphi, OTOH, by default links in all of the runtime library code unless you build with runtime packages enabled. This difference in default configurations will account for the majority of the size differences between the executables.
Well, I don't know about WBEMScripting_TLB, but ActiveX.pas is a pretty huge unit. It's almost 7000 lines on my D2010 install. If you have to bring any significant amount of that into your code, then you can expect it will add a few hundred K to your EXE size.
How big is the TLB, by the way?

Resources