I'm trying to get a game to integrate with the Windows Games Explorer. So far I have embedded a Game Definition Format XML file as a resource in my binary and my actual Inno Setup script to integrate the the DLL to the installer looks like this:
This is what i've managed to do by now:
[Files]
Source: GameuxInstallHelper.dll; DestDir: "{tmp}"; Flags: dontcopy
[Code]
const
GIS_NOT_INSTALLED = 1;
GIS_CURRENT_USER = 2;
GIS_ALL_USERS = 3;
// Given a game instance GUID and path to GDF binary, registers game with Game Explorer
procedure AddToGameExplorer( GDFBinPath: PChar; GameInstallPath: PChar; InstallScope : integer; GameGUID : GUID );
external 'AddToGameExplorerA#files:GameuxInstallHelper.dll stdcall';
// Given a game instance GUID, unregisters a game from Game Explorer
function RemoveFromGameExplorer( GameGUID : GUID ) : boolean;
external 'RemoveFromGameExplorer#files:GameuxInstallHelper.dll stdcall';
// Given a path to a GDF binary that has already been registered, returns a game instance GUID
function RetrieveGUIDForApplication( GDFBinPath: PChar; var GameGUID : GUID ) : boolean;
external 'RetrieveGUIDForApplicationA#files:GameuxInstallHelper.dll stdcall';
// Creates a unique game instance GUID
function GenerateGUID( var GameGUID : GUID ) : boolean;
external 'GenerateGUID#files:GameuxInstallHelper.dll stdcall';
// Register with Media Center using the data from a GDF binary
procedure RegisterWithMediaCenter( GDFBinPath : PChar; GameInstallPath : PChar; InstallScope : integer; ExePath : PChar; CommandLineArgs : PChar; UseRegisterMCEApp : boolean );
external 'RegisterWithMediaCenterA#files:GameuxInstallHelper.dll stdcall';
// Unregister with Media Center
procedure UnRegisterWithMediaCenter( GameInstallPath : PChar; InstallScope : integer; strExePath : PChar; UseRegisterMCEApp : boolean );
external 'UnRegisterWithMediaCenterA#files:GameuxInstallHelper.dll stdcall';
// Given a a game instance GUID, creates a task shortcut in the proper location
procedure CreateTask( InstallScope : integer; GameInstanceGUID : GUID; SupportTask : boolean; TaskID : integer; TaskName :PChar; LaunchPath : PChar; CommandLineArgs : PChar );
external 'CreateTaskA#files:GameuxInstallHelper.dll stdcall';
// This removes all the tasks associated with a game instance GUID
// Pass in a valid GameInstance GUID that was passed to AddGame()
procedure RemoveTasks( GameInstanceGUID : GUID );
external 'RemoveTasks#files:GameuxInstallHelper.dll stdcall';
// Creates the registry keys to enable rich saved games. The game still needs to use
// the rich saved game header as defined in the documentation and support loading a
// saved game from the command line.
procedure SetupRichSavedGames( SavedGameExtension : PChar; LaunchPath : PChar; CommandLineToLaunchSavedGame : PChar);
external 'SetupRichSavedGamesA#files:GameuxInstallHelper.dll stdcall';
// Removes the registry keys to enable rich saved games.
procedure RemoveRichSavedGames( SavedGameExtension : PChar);
external 'RemoveRichSavedGamesA#files:GameuxInstallHelper.dll stdcall';
However, i have two errors and i can't get past them:
Unknown type 'GUID'
and
Cannot import dll:C:\Users\my user\AppData\Local\Temp\a random name folder\GameuxInstallHelper.dll
Any ideas how to solve the errors or modify the code?
As far as the issue with importing the DLL into the User folder - are you checking for administrative privileges?
Section: PrivilegesRequired
Related
Currently to access LocalLow I use this:
{%USERPROFILE}\AppData\LocalLow
But I would like to know if there's a constant for that in Inno Setup, since both Roaming and Local have one.
There's no constant for AppData\LocalLow.
You may use Pascal Scripting to resolve it.
To resolve the "LocalLow", one has to use SHGetKnownFolderPath.
See also Detect the location of AppData\LocalLow.
The implementation involves few hacks, due to a lack of (wide) PChar type in Unicode Inno Setup.
const
MAX_PATH = 260;
AppDataLocalLowGUID = '{A520A1A4-1780-4FF6-BD18-167343C5AF16}';
// There's no PChar in Unicode Inno Setup,
// pretend the function returns a pointer to an Integer
function SHGetKnownFolderPath(rfid: TGUID; dwFlags: DWORD; hToken: THandle;
var ppszPath: Integer): Integer;
external 'SHGetKnownFolderPath#Shell32.dll stdcall';
// And allow the Integer to be copied to string
function StrCpy(Dest: string; Source: Integer): Integer;
external 'StrCpyW#Shlwapi.dll stdcall';
// And allow the Integer pointer to be released
procedure CoTaskMemFreeAsInteger(pv: Integer);
external 'CoTaskMemFree#Ole32.dll stdcall';
function GetAppDataLocalLow: string;
var
Path: Integer;
I: Integer;
begin
if SHGetKnownFolderPath(StringToGUID(AppDataLocalLowGUID), 0, 0, Path) = 0 then
begin
// The path should not be longer than MAX_PATH
SetLength(Result, MAX_PATH);
StrCpy(Result, Path);
CoTaskMemFreeAsInteger(Path);
// Look for NUL character and adjust the length accordingly
SetLength(Result, Pos(#0, Result) - 1);
end;
end;
If you need to use the path in non-Code section (outside of the Pascal Script), you can use a scripted constant:
[Files]
Source: myfile.txt; DestDir: {code:GetAppDataLocalLow}
And you need to change the function signature to take a dummy parameter:
function GetAppDataLocalLow(Param: string): string;
For example, to delete file on uninstall from LocalLow with INNO:
[UninstallDelete]
Type: filesandordirs; Name: "{userappdata}\..\LocalLow\MyFile.txt"
I'm trying to figure out how to use WinAPI functions from Pascal Script/Inno Setup. I didn't find much code examples how to do it and I'm not a Pascal programmer. Here's what I did so far:
Importing the function
function PathCombine (
pszPathOut : PChar;
pszPathIn : PChar;
pszMore : PChar
) : PChar;
external 'PathCombineA#Shlwapi.dll stdcall';
and using it like this:
function InitializeSetup(): Boolean;
var
a, b,c : PChar;
s : string;
begin
SetLength(s, 256); { soon it gets working I'll switch to use MAX_PATH instead of }
a := 'C:';
b := 'one\two';
c := PathCombine(s, a, b);
MsgBox(s, mbInformation, MB_OK);
end;
The output is this:
The expected output is:
C:\one\two
I'm pretty sure I'm accessing garbage values in memory but I don't know why, how do I fix this?
You didn't specify if you are using Ansi or Unicode version of Inno Setup.
But this should work in either version:
function PathCombine(
pszPathOut : PAnsiChar;
pszPathIn : PAnsiChar;
pszMore : PAnsiChar
) : PAnsiChar; external 'PathCombineA#Shlwapi.dll stdcall';
function InitializeSetup(): Boolean;
var
a, b, c: AnsiString;
begin
SetLength(c, 256); { soon it gets working I'll switch to use MAX_PATH instead of }
a := 'C:';
b := 'one\two';
PathCombine(c, a, b);
MsgBox(c, mbInformation, MB_OK);
Result := True;
end;
Though I strongly encourage you to use Unicode version of Inno Setup and PathCombineW instead.
function PathCombine(
pszPathOut : string;
pszPathIn : string;
pszMore : string
) : Cardinal; external 'PathCombineW#Shlwapi.dll stdcall';
function InitializeSetup(): Boolean;
var
a, b, c: string;
begin
SetLength(c, 256); { soon it gets working I'll switch to use MAX_PATH instead of }
a := 'C:';
b := 'one\two';
PathCombine(c, a, b);
MsgBox(c, mbInformation, MB_OK);
Result := True;
end;
Note that Inno Setup lacks PWideChar type. While it can marshal string to LPTSTR (PWideChar) function arguments, it cannot marshal LPTSTR return value. So I've used Cardinal for return type. It has the same size as pointer (to char), so a stack will match. And we do not actually need the returned value.
I think (although I haven't worked with Pascal/Delphi for a while) that the problem is that C "strings" (char *) are 0 index based, while Pascal strings are 1 index based (byte 0 is used to store the length).
So, if you declare your s variable as:
s: array[0..255] of Char; //Don't forget to change it to MAX_PATH afterwards
it should work. Also use the PathCombine function like this:
PathCombine(s, a, b);
There's no need to assign its result (which is the same as s) to another variable (that you aren't going to use anyway).
I am trying to add NSStatusItem in a Delphi application for OSX. Searched for sample code to help me with that but got stuck when defining an interface:
Here is the code:
// Source: https://forums.embarcadero.com/thread.jspa?threadID=108449
unit Unit2;
interface
uses Macapi.ObjectiveC, Macapi.CocoaTypes, Macapi.Foundation, Macapi.AppKit,
Macapi.Helpers, Macapi.ObjcRuntime, System.TypInfo, FMX.Platform, FMX.Platform.Mac;
type
TFMXTrayItem = class(TOCLocal)
private
NSStatItem : NSStatusItem;
public
constructor Create;
destructor Destroy; override;
function GetObjectiveCClass: PTypeInfo; override;
procedure call_mymethod; cdecl;
end;
implementation
constructor TFMXTrayItem.Create;
var
NSContMenu : NSMenu;
NSContItem : NSMenuItem;
NSStatBar : NSStatusBar;
NSImg : NSImage;
AppBundle : NSBundle;
NSpImg: Pointer;
Path: String;
begin
inherited Create;
NSStatBar := TNSStatusBar.Create;
NSStatBar := TNSStatusBar.Wrap(TNSStatusBar.OCClass.systemStatusBar);
NSStatItem:= NSStatBar.statusItemWithLength(NSVariableStatusItemLength);
NSStatItem.setTarget(GetObjectID);
// Create context menu
NSContMenu := TNSMenu.Create;
NSContMenu := TNSMenu.Wrap(NSContMenu.initWithTitle(StrToNSStr('The caption')));
NSContItem:=TNSMenuItem.Create;
NSContItem:=TNSMenuItem.Wrap(NSContItem.initWithTitle(StrToNSStr('1. menuitem'),sel_getUid(PAnsiChar('call_mymethod')),StrToNSStr('')));
NSContItem.setTarget(GetObjectID);
NSContMenu.addItem(NSContItem);
NSContItem.release;
// Add menu
NSStatItem.retain;
NSStatItem.setHighlightMode(true);
NSStatItem.setMenu(NSContMenu);
NSContMenu.release;
// Get path to dir
AppBundle := TNSBundle.Wrap(TNSBundle.OCClass.mainBundle);
Path:=AppBundle.bundlePath.UTF8String+'/Contents/yourimage16x16.png';
NSpImg := TNSImage.Alloc.initWithContentsOfFile(StrToNSStr(Path));
// Create Icon
NSImg := TNSImage.Create;
NSImg := TNSImage.Wrap(NSpImg);
NSStatItem.setImage(NSImg);
NSImg.release;
end;
destructor TFMXTrayItem.Destroy;
begin
NSStatItem.release;
inherited;
end;
function TFMXTrayItem.GetObjectiveCClass: PTypeInfo;
begin
Result :=TypeInfo(IFMXTrayItem);
end;
procedure TFMXTrayItem.call_properties;
begin
// your event code of the menu item
end;
end.
Does anyone have any idea on how to declare the IFMXTrayItem interface?
Got it to work like this:
type
IFMXTrayItem = interface(NSObject)
['{7d2e4b38-61d9-4cf4-b78b-5f7c4188e9c0}']
procedure call_mymethod; cdecl;
end;
later edit:
Added a GUID to the interface after reading this:
This GUID is used by the compiler to identify uniquely this interface.
Strictly speaking, you can use an interface without the GUID, but you
can’t get very far using them as much of the RTL and most frameworks
that take advantage of interfaces will require that they have a GUID.
So that is a random GUID I generated but if you use this in your code you should generate your own GUID.
I'm trying to implement the IFileIsUse COM interface in my Delphi program so that Windows Explorer can show more details about my application when it locks a file.
I based my code on the FileIsUse sample from Microsoft (http://msdn.microsoft.com/en-us/library/ee330722%28VS.85%29.aspx) and am up to this:
type
TFileIsInUseImpl = class(TInterfacedObject, IUnknown, IFileIsInUse)
protected
function GetAppName(out ppszName: LPWSTR) : HRESULT; stdcall;
function GetUsage(out pfut : FILE_USAGE_TYPE) : HRESULT; stdcall;
function GetCapabilities(out pdwCapFlags : DWORD) : HRESULT; stdcall;
function GetSwitchToHWND(out phwnd : HWND) : HRESULT; stdcall;
function CloseFile() : HRESULT; stdcall;
public
constructor Create(const AFileName: string);
end;
procedure RegisterFileIsInUse(const AFileName: string);
var
Cookie: Longint;
rot: IRunningObjectTable;
hr: HRESULT;
mk: IMoniker;
FileIsInUse: IFileIsInUse;
begin
hr := GetRunningObjectTable(0, rot);
if SUCCEEDED(hr) then
begin
hr := CreateFileMoniker(PChar(AFileName), mk);
if SUCCEEDED(hr) then
begin
FileIsInUse := TFileIsInUseImpl.Create(AFileName);
hr := rot.Register(ROTFLAGS_REGISTRATIONKEEPSALIVE or ROTFLAGS_ALLOWANYCLIENT, FileIsInUse, mk, Cookie);
if hr = CO_E_WRONG_SERVER_IDENTITY then
hr := rot.Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, FileIsInUse, mk, Cookie);
if SUCCEEDED(hr) then
FRegisteredFiles.Add(AFileName, TRegisteredFile.Create(Cookie, FileIsInUse));
end;
end;
end;
I added registry infos in both HKEY_CLASSES_ROOT\AppID\MyApp.exe and HKEY_CLASSES_ROOT\AppID\{MyGUID} to indicate RunAs=Interactive user so that the first call to rot.Register succeeds with ROTFLAGS_ALLOWANYCLIENT
However, none of my TFileIsInUseImpl methods are ever called.
Overriding QueryInterface for it, I discovered that it does get called but only for marshaling related interfaces, never for IFileIsInUse.
Looking around I came to the conclusion that something is not initialized the way it should be in my application, but I can't figure out why.
I already tried this:
Call CoInitializeEx(nil, COINIT_MULTITHREADED) instead of the default CoInitialize(nil)
Add HKEY_CLASSES_ROOT\CLSID\{MyGuid}\InProcServer32\ThreadingModel=Both
but nothing helped. Considering that Microsoft's sample is a standalone application, I should be able to replicate what it does.
Can any of you tell me what I'm doing wrong here?
If the code was build similar to the MSDN example, then it works as expected. The sample code can be downloaded from the code gallery:
Defect link: http://archive.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=shellapplication&DownloadId=6773
New link: https://msdn.microsoft.com/en-us/library/windows/desktop/ee330722(v=vs.85).aspx
Download from Windows 7 SDK: https://www.microsoft.com/en-us/download/details.aspx?id=8279
I need to get the volume serial number for a drive letter during an installation created with Inno Setup. I know that DLL functions can be imported into Inno, but I'm fairly new to it and having some problems getting it to work. I know that the GetVolumeInformation function in kernel32 can do what I need. Could someone show me how to import and use that functionality in an Inno script to retrieve the volume serial number?
Thanks!
Inno-Setup code::
[Code]
function GetVolumeInformation(
lpRootPathName: PChar;
lpVolumeNameBuffer: PChar;
nVolumeNameSize: DWORD;
var lpVolumeSerialNumber: DWORD;
var lpMaximumComponentLength: DWORD;
var lpFileSystemFlags: DWORD;
lpFileSystemNameBuffer: PChar;
nFileSystemNameSize: DWORD
): BOOL;
external 'GetVolumeInformationA#kernel32.dll stdcall';
function LoWord(dw: DWORD): WORD;
begin
Result := WORD(dw);
end;
function HiWord(dw: DWORD): WORD;
begin
Result := WORD((dw shr 16) and $FFFF);
end;
function WordToHex(w: WORD): string;
begin
Result := Format('%.4x', [w]);
end;
function FindVolumeSerial(const Drive: string): string;
var
FileSystemFlags: DWORD;
VolumeSerialNumber: DWORD;
MaximumComponentLength: DWORD;
begin
Result := '';
// Note on passing PChars using RemObjects Pascal Script:
// '' pass a nil PChar
// #0 pass an empty PChar
if GetVolumeInformation(
PChar(Drive),
'', // nil
0,
VolumeSerialNumber,
MaximumComponentLength,
FileSystemFlags,
'', // nil
0)
then
Result := WordToHex(HiWord(VolumeSerialNumber)) + '-' + WordToHex(LoWord(VolumeSerialNumber));
end;
function InitializeSetup(): Boolean;
begin
MsgBox(FindVolumeSerial('c:\'), mbInformation, mb_Ok);
end;
Tested with Inno-setup version 5.2.3
In Unicode versions of Inno-Setup replace PChar with PAnsiChar
Since the InnoSetup doesn't support pointers you will have to create the external library for the call of the GetVolumeInformation function. The following code samples should work for all combinations of the Delphi and InnoSetup (from the Unicode support point of view).
Here's the Delphi library code:
library VolumeInformation;
uses
Types, Classes, SysUtils, Windows;
var
SerialNumber: AnsiString;
function GetVolumeSerial(Drive: PAnsiChar): PAnsiChar; stdcall;
var
FileSystemFlags: DWORD;
VolumeSerialNumber: DWORD;
MaximumComponentLength: DWORD;
begin
SerialNumber := '';
GetVolumeInformationA(Drive, nil, 0, #VolumeSerialNumber,
MaximumComponentLength, FileSystemFlags, nil, 0);
SerialNumber := IntToHex(HiWord(VolumeSerialNumber), 4) + ' - ' +
IntToHex(LoWord(VolumeSerialNumber), 4);
Result := PAnsiChar(SerialNumber);
end;
exports
GetVolumeSerial;
end.
And here's the InnoSetup code:
[Files]
Source: "VolumeInformation.dll"; Flags: dontcopy
[Code]
function GetVolumeSerial(Drive: PAnsiChar): PAnsiChar;
external 'GetVolumeSerial#files:VolumeInformation.dll stdcall setuponly';
procedure ButtonOnClick(Sender: TObject);
var
S: string;
begin
S := GetVolumeSerial('c:\');
MsgBox(S, mbInformation, mb_Ok);
end;