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.
Related
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;
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).
Our programming dept just spent about a non-mythical man-month tracking down what we think is a bug in a 3rd party component, here's their copyrighted source code:
function TGDIPPicture.GetImageSizes: boolean;
var
multi: TGPImage;
pstm: IStream;
hGlobal: THandle;
pcbWrite: Longint;
begin
result := false;
if Empty then
Exit;
if FDataStream.Size = 0 then
Exit;
hGlobal := GlobalAlloc(GMEM_MOVEABLE, FDataStream.Size);
if (hGlobal = 0) then
raise Exception.Create('Could not allocate memory for image');
try
pstm := nil;
// Create IStream* from global memory
CreateStreamOnHGlobal(hGlobal, TRUE, pstm);
pstm.Write(FDataStream.Memory, FDataStream.Size,#pcbWrite);
multi := TGPImage.Create(pstm);
FWidth := multi.GetWidth;
FHeight := multi.GetHeight;
Result := true;
multi.Free;
finally
GlobalFree(hGlobal);
end;
end;
We found the problem was with TMS's AdvOfficeTabSet. If we added tabs, then it crashed, if we didn't add tabs then it didn't crash. (the crash was one of those un-debuggable app hangs that hits you 10 steps after the real problem).
Following Raymond Chen's advice I replaced GMEM_MOVEABLE with GPTR and it appears to have fixed the problem.
I'm wondering if anyone can tell me if the above code had any legitimate reason for using GMEM_MOVEABLE. AFAIK it's only for the clipboard and it should always be used with GlobalAlloc.
while I was typing this another programmer got an error in the GlobalFree function using my code. So, apparently this doesn't work either. Could really use some help here!
*CreateStreamOnHGlobal is a Windows API function. (which apparently prefers GMEM_MOVEABLE)
*TGPImage is part of TMS's implementation of the GDI+ library.
Jonathan has identified the obvious problem, that being the double free of the HGLOBAL. But as you have found, the use is GMEM_MOVEABLE is correct.
Frankly, the code seems needlessly complex. I suggest you use the built in stream adapter and avoid any GlobalAlloc. To get an IStream you just need to do this:
pstm := TStreamAdapter.Create(FDataStream);
That's it.
I am handling from my Application associated extension files from Windows. So when you double click a file from Windows it will execute my program, and I handle the file from there, something like:
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 0 to ParamCount -1 do
begin
if SameText(ExtractFileExt(ParamStr(i)), '.ext1') then
begin
// handle my file..
// break if needed
end else
if SameText(ExtractFileExt(ParamStr(i)), '.ext2') then
begin
// handle my file..
// break if needed
end else
end;
end;
That works pretty much how I want it to, but when I was testing I realised it does not consider using only one instance of my program.
So for example, if I selected several Files from Windows and opened them all at the same time, this will create the same number of instances of my program with the number of Files being opened.
What would be a good way to approach this, so that instead of several instances of my program being opened, any additional Files from Windows being opened will simply focus back to the one and only instance, and I handle the Files as normal?
Thanks
UPDATE
I found a good article here: http://www.delphidabbler.com/articles?article=13&part=2 which I think is what I need, and shows how to work with the Windows API as mentioned by rhooligan. I am going to read through it now..
Here is some simple example code that gets the job done. I hope it is self-explanatory.
program StartupProject;
uses
SysUtils,
Messages,
Windows,
Forms,
uMainForm in 'uMainForm.pas' {MainForm};
{$R *.res}
procedure Main;
var
i: Integer;
Arg: string;
Window: HWND;
CopyDataStruct: TCopyDataStruct;
begin
Window := FindWindow(SWindowClassName, nil);
if Window=0 then begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end else begin
FillChar(CopyDataStruct, Sizeof(CopyDataStruct), 0);
for i := 1 to ParamCount do begin
Arg := ParamStr(i);
CopyDataStruct.cbData := (Length(Arg)+1)*SizeOf(Char);
CopyDataStruct.lpData := PChar(Arg);
SendMessage(Window, WM_COPYDATA, 0, NativeInt(#CopyDataStruct));
end;
SetForegroundWindow(Window);
end;
end;
begin
Main;
end.
unit uMainForm;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms, StdCtrls;
type
TMainForm = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
public
procedure ProcessArgument(const Arg: string);
end;
var
MainForm: TMainForm;
const
SWindowClassName = 'VeryUniqueNameToAvoidUnexpectedCollisions';
implementation
{$R *.dfm}
{ TMainForm }
procedure TMainForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WinClassName := SWindowClassName;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 1 to ParamCount do begin
ProcessArgument(ParamStr(i));
end;
end;
procedure TMainForm.ProcessArgument(const Arg: string);
begin
ListBox1.Items.Add(Arg);
end;
procedure TMainForm.WMCopyData(var Message: TWMCopyData);
var
Arg: string;
begin
SetString(Arg, PChar(Message.CopyDataStruct.lpData), (Message.CopyDataStruct.cbData div SizeOf(Char))-1);
ProcessArgument(Arg);
Application.Restore;
Application.BringToFront;
end;
end.
The logic goes something like this. When you start your application, you iterate through the list of running processes and see if your application is already running. If it is running, you need to activate the window of that instance and then exit.
Everything you need to do this is in the Windows API. I found this sample code on CodeProject.com that deals with processes:
http://www.codeproject.com/KB/system/Win32Process.aspx
On finding and activating a window, the basic approach is to find the window of interest using the window class name then activate it.
http://www.vb6.us/tutorials/activate-window-api
Hopefully this gives you a good starting point.
There are many answers here that show how to implement this. I want to show why NOT to use the FindWindow approach.
I am using FindWindow (something similar with the one shown by David H) and I have seen it failed starting with Win10 - I don't know what they changed in Win10.
I think the gap between the time when the app starts and the time when we set the unique ID via CreateParams is too big so another instance has somehow time to run in this gap/interval.
Imagine two instances started at only 1ms distance (let's say that the user click the EXE file and then presses enter and keeps it pressed by accident for a short while). Both instances will check to see if a window with that unique ID exists, but none of them had the chance to set the flag/unique ID because creating the form is slow and the unique ID is set only when the form is constructed. So, both instances will run.
So, I would recommend the CreateSemaphore solution instead:
https://stackoverflow.com/a/460480/46207
Marjan V already proposed this solution but didn't explained why it is better/safer.
I'd use mutexes. You create one when your program starts.
When the creation fails it means another instance is already running. You then send this instance a message with your command line parameters and close. When your app receives a message with a command line, it can parse the parameters like you are already doing, check to see whether it already has the file(s) open and proceed accordingly.
Processing this app specific message ia also the place to get your app to the front if it isn't already. Please do this politely (SetForegroundWindow) without trying to force your app in front of all others.
function CreateMutexes(const MutexName: String): boolean;
// Creates the two mutexes to see if the program is already running.
// One of the mutexes is created in the global name space (which makes it
// possible to access the mutex across user sessions in Windows XP); the other
// is created in the session name space (because versions of Windows NT prior
// to 4.0 TSE don't have a global name space and don't support the 'Global\'
// prefix).
var
SecurityDesc: TSecurityDescriptor;
SecurityAttr: TSecurityAttributes;
begin
// By default on Windows NT, created mutexes are accessible only by the user
// running the process. We need our mutexes to be accessible to all users, so
// that the mutex detection can work across user sessions in Windows XP. To
// do this we use a security descriptor with a null DACL.
InitializeSecurityDescriptor(#SecurityDesc, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(#SecurityDesc, True, nil, False);
SecurityAttr.nLength := SizeOf(SecurityAttr);
SecurityAttr.lpSecurityDescriptor := #SecurityDesc;
SecurityAttr.bInheritHandle := False;
if (CreateMutex(#SecurityAttr, False, PChar(MutexName)) <> 0 )
and (CreateMutex(#SecurityAttr, False, PChar('Global\' + MutexName)) <> 0 ) then
Result := True
else
Result := False;
end;
initialization
if not CreateMutexes('MyAppNameIsRunningMutex') then
//Find and SendMessage to running instance
;
end.
Note: above code is adapted from an example on the InnoSetup site. InnoSetup creates installer applications and uses this approach in the installer to check whether (a previous version of) the application being installed is already running.
Finding the other instance and sending it a message, I'll leave for another question (or you can use the WM_COPYDATA approach from David's answer). Actually, there is a StackOverflow question that deals exactly with this: How to get the process thread that owns a mutex Getting the process/thread that owns the mutex may be a bit of a challenge, but the answers to this question do address ways to get the information from one instance to the other.
Windows has different ways to handle file associations to executable.
The "command line" approach is only the simplest one, but also the most limited one.
It also supports DDE (it still works although officially deprecated) and COM (see http://msdn.microsoft.com/en-us/library/windows/desktop/cc144171(v=vs.85).aspx).
If I recall correctly both DDE and COM will let your application receive the whole list of selected files.
I used window/message approach by myself with addition of events for tracking if the other instance is running:
Try to create event "Global\MyAppCode" (the "Global" namespace is used for handling various user sessions as I needed single instance system-wide; in your case you'll probably prefer "Local" namespace which is set by default)
If CreateEvent returned error and GetLastError = ERROR_ALREADY_EXISTS then the instance is running already.
FindWindow/WM_COPYDATA to transfer data to that instance.
But the drawbacks with messages/windows are more than significant:
You must always keep your window's Caption constant. Otherwise you'll have to list all the windows in the system and loop through them for partial occurrence of some constant part. Moreover the window's caption could be easily changed by a user or 3rd part app so the search would fail.
Method requires a window to be created so no console/service apps, or they must create a window and perform message loop especially for handling the single instance.
I'm not sure FindWindow could find a window that is opened in another user session
For me, WM_COPYDATA is rather awkward method.
So currently I'm a fan of named pipe approach (haven't implemented it yet though).
On launch, app tries to connect to "Global\MyAppPipe". If successed, other instance is running. If failed, it creates this pipe and finishes instance check.
2nd instance writes the required data to pipe and exits.
1st instance receives data and does some stuff.
It works through all user sessions (with namespace "Global") or just a current session; it doesn't depend on strings used by UI (no localization and modification issues); it works with console and service apps (you'll need to implement pipe reading in a separate thread/message loop though).
Recently, I looked into using OnGuard as a way to help honest people abide by their principles. I agree with the views expressed by many here that no licensing system is going to protect you from someone who wants to use your software and does not want to pay for it. On the other hand, I would like to avoid making it too easy for someone else to create valid keys for my program.
After studying the manual and examples, I added the following to my main form's code:
const
TheKey: TKey = ($4A,$62,$F3,$2B,$9C,$D2,$84,$BF,$CB,$04,$0A,$C3,$3D,$11,$47,$1A);
function TfrmMain1.MakeCode(sName, sNumber: String; dtExpiration: TDate): String;
var Key: TKey;
Code: TCode;
sCode: String;
begin
Key := TheKey;
InitRegCode(Key, sName + ' - ' + sNumber, dtExpiration, Code);
sCode := BufferToHex(Code, SizeOf(Code));
Insert('-', sCode, 13);
Insert('-', sCode, 09);
Insert('-', sCode, 05);
Result := sCode
end;
function TfrmMain1.TestCode(sName, sNumber, sTestCode: String; dtExpiration: TDate): Boolean;
var Key: TKey;
Code: TCode;
sCode: String;
begin
sCode := MakeCode(sName, sNumber, dtExpiration);
Result := SameText(sCode, sTestCode);
end;
This brings up some questions:
Does seem like the correct way to use this? I would rather not add their components to my form.
Since the OnGuard source is available, couldn't a hacker reverse engineer the Key I will choose and produce valid release codes? Should I therefore add some additional obfuscation to the code or might I just weaken the system.
The Key is set as a constant here. Won't it show up in the code as contiguous bytes and be easy to copy?
My program will require (at least) annual updates and my plan is to license it with an annual subscription. Would it be stronger to add the year as a constant in my program and test dated user entries against that year in a few places.
There are 4 questions here that are closely related and pretty specific. It seemed like it would be more awkward to ask those in four separate entries and have to add references for context but I will be glad to do that if that would be preferable. Thank you for your help.
Jack
constructor TLincenceManager.Create;
begin
FSpecialCode := TOgSpecialCode.Create(nil);
FSpecialCode.OnGetModifier := OgNetCodeGetModifier;
FSpecialCode.OnChecked := OgNetCodeChecked;
FSpecialCode.OnGetCode := OgNetCodeGetCode;
FSpecialCode.OnGetKey := OgNetCodeGetKey;
FSpecialCode.AutoCheck := False;
end;
function TLincenceManager.InitializeLicenceCode: Boolean;
begin
Result := FSpecialCode.CheckCode(True) = ogValidCode;
end;
procedure TLincenceManager.OgNetCodeChecked(Sender: TObject; Status: TCodeStatus);
begin
case Status of
ogValidCode : FMaxUsers := FSpecialCode.GetValue;
ogInvalidCode : FMaxUsers := 0;
ogPastEndDate : FMaxUsers := 0;
ogDayCountUsed : FMaxUsers := 0;
ogRunCountUsed : FMaxUsers := 0;
ogNetCountUsed : FMaxUsers := 0;
ogCodeExpired : FMaxUsers := 0;
else
FMaxUsers := 0;
end;
end;
procedure TLincenceManager.OgNetCodeGetCode(Sender: TObject; var Code: TCode);
var
FileName: string;
SerialData: string;
LicenceData: TStringList;
begin
FileName := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
FileName := FileName + cLicenseFileName;
SerialData := '';
LicenceData := TStringList.Create;
try
if FileExists(FileName) then
begin
LicenceData.LoadFromFile(FileName);
SerialData := LicenceData.Values['Serial'];
end;
{convert to proper form}
HexToBuffer(SerialData, Code, SizeOf(Code));
finally
LicenceData.Free;
end;
end;
procedure TLincenceManager.OgNetCodeGetKey(Sender: TObject; var Key: TKey);
const
CKey : TKey = ($4A,$62,$F3,$2B,$9C,$D2,$84,$BF,$CB,$04,$0A,$C3,$3D,$11,$47,$1A);
begin
Key := CKey;
end;
procedure TLincenceManager.OgNetCodeGetModifier(Sender: TObject; var Value: Integer);
begin
Value := GenerateMachineModifierPrim;
end;
I have posted the way I do it. I do not use "visual" components. The way I do it is the way to go, in your case you just apply the date modifier (i have machine modifier)
No in theory no. The key with which you generate you licence is of essence. If you have the key you can crack the licences. But with only the code you cant. This is just like encryption algorithms. You can know how the algorithm works but if you don't have the key you cant crack it. Look at XTEA. It is very simple yet hard to crack.
Yes the key can be extracted from binary if one knows what is doing. You could use some sort of obfuscation here. But I would not bother. For most people such protection is enough, so if you are not making the next MS Office I would not bother. People are way to paranaoid about their products. Sell it first and think about this later. Oh and since it is not a string it is a little harder to find anyway.
Just look at the time trial demos that come with onGuard to know how to do the time limited licence. However be aware that just simple manipulation of the computer clock will be enough to fool it. In my opinion best trial software is such, that is lack some vital functionality (save button...). Good time trials are very hard to make in my opinion.
I use the lower level OnGuard APIs rather than the classes as suggested by Runner. Either will work fine, the classes end up calling the lower level APIs anyway. Here are my wrapper utility functions for these lower level API methods.
{ Used by you to generate the unlock code you send to your customer, do not include in the customer software }
function GenerateReleaseCode(const inAppKey : string; inExpiryDate : TDateTime; inRegCode : string) : string;
(* inAppKey is the byte sequence key you already have
inRegCode can be anything, typically customers name
Returns the release code for the customer to type into the software *)
var
releaseCode : TCode;
key : TKey;
begin
HexToBuffer(inAppKey, key, SizeOf(TKey));
InitRegCode(key, inRegCode, inExpiryDate, releaseCode);
Result := BufferToHex(unlockCode, SizeOf(releaseCode));
end;
{ Used in your program to authenticate if the release code is valid - does not check for expiry }
function AuthenticateReleaseCode(const inReleaseCodeHexString : string; const inAppKey : TKey) : Boolean;
var
releaseCode : TCode;
begin
HexToBuffer(inReleaseCodeHexString, releaseCode, SizeOf(releaseCode));
Result := IsRegCodeValid(inAppKey, releaseCode);
end;
{ Used in your program to test if the license has expired }
function UnlockCodeExpiryDate(const inReleaseCodeHexString : string; const inAppKey : TKey) : TDateTime;
var
releaseCode : TCode;
begin
HexToBuffer(inReleaseCodeHexString, releaseCode, SizeOf(releaseCode));
Result := GetExpirationDate(inAppKey, releaseCode);
end;
I do use OnGuard extensively but only for enterprise software where piracy isn't such an issue. If you're selling consumer software and your worried about piracy I'd recommend a stronger solution such as a commercial copy protection library that encrypts the exe.
Even then you can slow the crackers down, but you can't stop them.
3) You should "scatter" the key around and maybe having part of it computed someway. The simpler to identify the key, of course the simpler to bypass protection. But even a complex key is useless if a simple JMP instruction in the proper place will bypass the whole protection check. Those checks should be also more than one, and again, scattered around.
4) Be careful with such kind of licenses - usually users don't like them unless the annual fee means also some perceived value (i.e. an antivirus gives you update signatures, or a GPS application updated maps). Just forcing users to pay annually may look good for you, but not for users, even if you add new features they may not regard as useful. If the application stop working is even worse. That's one of the issue that killed many Unix applications when Windows application with the same features but without yearly fees became available. I know that many companies are thinking to return to that model - if it will be successful is yet to be seen.