Get a handle to IFileDialog from a TFileDialogEvent to get the filename - delphi

I am building a TFileSaveDialog descendent component. The descendent has a PushButton who's event is handled by:
function TFileDialogEvent.OnButtonClicked(const pfdc: IFileDialogCustomize;
dwIDCtl: DWORD): HResult; stdcall;
var
iImageEnIO: TImageEnIO;
iFilename: string;
iName: PChar;
pfd: IFileDialog;
begin
if dwIDCtl = dwVisualGroup8ID then
begin
iImageEnIO := TImageEnIO.Create(nil);
try
FileDialog.QueryInterface(
StringToGUID('{8016B7B3-3D49-4504-A0AA-2A37494E606F}'),
pfd);
// How to get correct valid handle to IFileDialog?
pfd.GetFileName(iName);
iFilename := string(iName);
if FileExists(iFilename) then
begin
The component also displays image information in various control labels correctly. The component sucessfully returns the selected filename and allows changing folders, but the getting the filename from pfd.GetFileName(iName) in the OnButtonClicked event is returning an invalid filename. I think the problem is caused by not getting the correct handle for pfd: IFileDialog.
UPDATE:
I solved this by defining
FileDialog: IFileDialog as a var at the component level then I called
function TFileDialogEvent.OnButtonClicked(const pfdc: IFileDialogCustomize;
dwIDCtl: DWORD): HResult; stdcall;
var
iImageEnIO: TImageEnIO;
iFilename: string;
pFolder: PWideChar;
iFolder: string;
iName: PChar;
pfd: IFileDialog;
hr: HRESULT;
aShellItem: IShellItem;
begin
if dwIDCtl = dwVisualGroup8ID then
begin
iImageEnIO := TImageEnIO.Create(nil);
try
FileDialog.QueryInterface(IFileDialog, pfd);
pfd.GetFileName(iName);
// Get the ShellItem
hr := SHCreateItemFromParsingName(iName, nil,
StringToGUID(SID_IShellItem), aShellItem);
// Get the folder
pfd.GetFolder(aShellItem);
// Get the folder displayname
aShellItem.GetDisplayName(SIGDN_FILESYSPATH, pFolder);
iFolder := string(pFolder);
if DirectoryExists( iFolder) then
iFilename := IncludeTrailingPathDelimiter( iFolder) + string(iName);
if FileExists(iFilename) then
begin
Thank-you all... Thank-you Rob... your post was helpful.

You're querying the object for an interface matching the GUID {8016B7B3-3D49-4504-A0AA-2A37494E606F}, and storing the result in an IFileDialog reference. The problem is that {8016B7B3-3D49-4504-A0AA-2A37494E606F} is the GUID for the IFileDialogCustomize interface, not IFileDialog. You attempt to call GetFileName, which is the sixth method of the IFileDialog interface, but since the variable actually holds an IFileDialogCustomize interface, control ends up being transferred to the sixth function of that interface instead. The compiler cannot catch the type mismatch for you, partly because you're constructing the GUID dynamically at run time (so it doesn't know the value at compile time), and partly because the second parameter to QueryInterface is untyped (so it can't know that the type of the variable is supposed to match the first parameter).
There's an easier way than computing the GUID at run time. Interface types are automatically useable as their associated GUIDs (when they have GUIDs). To request the IFileDialog interface, just pass that identifier directly to QueryInterface:
FileDialog.QueryInterface(IFileDialog, pfd);
You don't even have to call QueryInterface if you use the as operator:
pfd := FileDialog as IFileDialog;
When you call QueryInterface directly, you need to make sure you check the result for error codes. If you use the as operator, an unsupported interface will raise an exception. If you just want pass-fail without too much error checking, use the Supports function instead:
if Supports(FileDialog, IFileDialog, pfd) then ...

TSaveFileDialog has a public Dialog property of type IFileDialog, so you don't need to hunt for it manually, you already have direct access to it, eg:
function TFileDialogEvent.OnButtonClicked(const pfdc: IFileDialogCustomize; dwIDCtl: DWORD): HResult; stdcall;
var
iImageEnIO: TImageEnIO;
iFilename: string;
iName: PWideChar;
begin
if dwIDCtl <> dwVisualGroup8ID then
Result := E_NOTIMPL
else
begin
Result := S_OK;
if FAILED(Self.Dialog.GetFileName(iName)) then Exit;
try
iFilename := string(iName);
finally
CoTaskMemFree(iName);
end;
iImageEnIO := TImageEnIO.Create(nil);
try
if FileExists(iFilename) then
begin
...
end;
finally
iImageEnIO.Free;
end;
end;
end;

Related

Creating and connecting DirectShow filter: how to implement CreateInstance()?

I want to write my own DirectShow filter to pull out packets of information for my own purposes. To do this, I used the guide to creating filters.
I did steps 1 to 5, and am stuck at step 6: failed to implement CreateInstance(). Can't instantiate the class because the MSDN example doesn't pass parameters, but code in Pascal requires (ObjectName: string; unk: IUnKnown; const clsid: TGUID). I used regsvr32, unfortunately I don’t know how to connect my DLL and I can’t think of it. The DSFMgr program also does not see my filter.
I read how filters are connected, tried to implement various searches, it's useless. Tried to connect manually via CLSID. Everything is useless. I know the answer is somewhere on the surface, but I don't see it. I can't figure out how DirectShow should see my library if it didn't exist in the first place. It's not logical. I've been trying to implement this for a very long time, but it doesn't work, I'm stuck.
Please don't recommend FFmpeg and the like. I don't want to use third party libraries. In DirectX, as far as I know it's built-in.
Step 6 example:
CUnknown * WINAPI CRleFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)
{
CRleFilter *pFilter = new CRleFilter();
if (pFilter== NULL)
{
*pHr = E_OUTOFMEMORY;
}
return pFilter;
}
I Implemented/converted it like this, but it doesn't work. Errors:
no variables sent
function TCRleFilter.CreateInstance(pUnk: PPUnknown; pHr: HRESULT): PUnknown;
var
pFilter: TCRleFilter;
begin
pFilter:= TCRleFilter.Create();
if pFilter = nil then pHr:= E_OUTOFMEMORY;
Result:= pFilter;
end;
I think at least a logical explanation should suffice.
The whole class:
unit Unit1;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
Windows, ActiveX, Classes, ComObj, DirectShow9, BaseClass, Dialogs;
type
TCRleFilter = class(TBCTransformFilter)
public
function CheckInputType(mtIn: PAMMediaType): HRESULT;
function GetMediaType (IPosition: Integer; pMediaType: PAMMediaType): HRESULT;
function CheckTransform(mtln: PAMMediaType; mt0ut: PAMMediaType): HRESULT;
function DecideBufferSize(pAlloc: IMemAllocator; pProp: PAllocatorProperties): HRESULT;
function Transform(pSource, pDest: IMediaSample): HRESULT;
function CreateInstance(pUnk: PPUnknown; pHr: HRESULT): PUnknown;
end;
const
CLSID_CRleFilter: TGUID = '{FBA9B97F-505B-49C7-A6C2-D1EFC34B2C0D}';
implementation
uses ComServ;
{ TCRleFilter }
function TCRleFilter.CheckInputType(mtIn: PAMMediaType): HRESULT;
begin
Result := S_OK;
ShowMessage('CheckInputType: âåðíóë "S_OK"');
end;
function TCRleFilter.CheckTransform(mtln, mt0ut: PAMMediaType): HRESULT;
begin
Result := S_OK;
ShowMessage('CheckTransform: âåðíóë "S_OK"');
end;
function TCRleFilter.CreateInstance(pUnk: PPUnknown;
pHr: HRESULT): PUnknown;
var
pFilter: TCRleFilter;
begin
try
pFilter:= TCRleFilter.Create('');
Result := pFilter;
except
pHr:= E_OUTOFMEMORY;
Result:= nil;
end;
end;
function TCRleFilter.DecideBufferSize(pAlloc: IMemAllocator; pProp: PAllocatorProperties): HRESULT;
begin
Result := S_OK;
ShowMessage('DecideBufferSize: âåðíóë "S_OK"');
end;
function TCRleFilter.GetMediaType(IPosition: Integer; pMediaType: PAMMediaType): HRESULT;
begin
Result := S_OK;
ShowMessage('GetMediaType: âåðíóë "S_OK"');
end;
function TCRleFilter.Transform(pSource, pDest: IMediaSample): HRESULT;
begin
Result := S_OK;
ShowMessage('Transform: âåðíóë "S_OK"');
end;
initialization
{.Create(ComServer, TCRleFilter, Class_CRleFilter, 'CRleFilter', 'CRle_Filter', ciMultiInstance, tmApartment); }
TBCClassFactory.CreateFilter(TCRleFilter,'CRle_Filter', CLSID_CRleFilter, CLSID_LegacyAmFilterCategory, MERIT_DO_NOT_USE, 0, nil );
end.
Your class inherites from TBCTransformFilter and the needed parameters are defined as:
constructor TBCTransformFilter.Create(ObjectName: string; unk: IUnKnown; const clsid: TGUID);
Untested, but it should be much more correct than your attempt:
function TCRleFilter.CreateInstance
( pUnk: IUnknown // LPUNKNOWN
; var pHr: HRESULT // Pointer to variable = VAR
): PUnknown; // Pointer
var
oFilter: TCRleFilter; // Object, not pointer
begin
try // Failing constructors throw exceptions
oFilter:= TCRleFilter.Create( 'my RLE encoder', pUnk, CLSID_CRleFilter );
result:= oFilter; // In doubt cast via "PUnknown(oFilter)"
except // Constructor failed, oFilter is undefined
pHr:= E_OUTOFMEMORY;
result:= nil;
end;
end;
The var parameter ensures that assigned values inside the function also live on outside the function - otherwise you'd only have a local variable. Which is also the point (haha) of pointers in C++ parameters.

Check if windows explorer already opened on given path

How can I see if windows explorer is already opened with certain path ? I don't want my application opens many duplicated windows. I was unable to do it with this way :
var
H: hwnd;
begin
if FileExists(edt8.Text) then
begin
H := FindWindow(0, PChar(ExtractFilePath(edt8.Text)));
if H <> 0 then
ShowMessage('explorer already opened')//explorer bring to front
else
ShellExecute(Application.Handle, nil, 'explorer.exe',
PChar(ExtractFilePath(edt8.Text)), nil, SW_NORMAL);
end;
end;
IShellWindows::FindWindowSW method
There is a nice method FindWindowSW that should find an existing Shell window, which includes Windows Explorer windows as well, I'd say. So, in a hope I'll find an existing window easily I wrote this code:
uses
ActiveX, ShlObj, SHDocVw, ComObj;
function IDListFromPath(const Path: WideString): PItemIDList;
var
Count: ULONG;
Attributes: ULONG;
ShellFolder: IShellFolder;
begin
OleCheck(SHGetDesktopFolder(ShellFolder));
OleCheck(ShellFolder.ParseDisplayName(0, nil, PWideChar(Path), Count, Result, Attributes));
end;
function GetExplorerHandle(const Path: WideString): HWND;
var
IDList: PItemIDList;
Unused: OleVariant;
Location: OleVariant;
ShellWindows: IShellWindows;
begin
OleCheck(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_LOCAL_SERVER, IID_IShellWindows, ShellWindows));
Unused := Unassigned;
IDList := IDListFromPath(Path);
PVariantArg(#Location).vt := VT_VARIANT or VT_BYREF;
PVariantArg(#Location).pvarVal := PVariant(IDList);
ShellWindows.FindWindowSW(Location, Unused, SWC_EXPLORER, Integer(Result), SWFO_INCLUDEPENDING);
end;
But it never finds the Windows Explorer window with the given folder path (it always returns 0). I've used SWC_EXPLORER class to search only for Windows Explorer windows, build the absolute ID list, used a proper VT_VARIANT | VT_BYREF variant for location (at least I hope so, if not, please let me know). And I also tried to return IDispatch by including the SWFO_NEEDDISPATCH option (method always returned nil reference). So I gave up on this method (haven't found any example).
IShellWindows enumeration
The following code was inspired by this article and this example. Here is a scheme:
1. IShellWindows.Item(n)
2. ⤷ IDispatch.QueryInterface(IWebBrowserApp)
3. ⤷ IWebBrowserApp.QueryInterface(IServiceProvider)
4. ⤷ IServiceProvider.QueryService(STopLevelBrowser, IShellBrowser)
5. ⤷ IShellBrowser.QueryActiveShellView
6. ⤷ IShellView.QueryInterface(IFolderView)
7. ⤷ IFolderView.GetFolder(IPersistFolder2)
8. ⤷ IPersistFolder2.GetCurFolder
9. ⤷ ITEMIDLIST
And some description:
As first you obtain the IShellWindows interface reference and iterate its items.
For each item, the IShellWindows interface returns window's IDispatch interface which you then query for an IWebBrowserApp interface reference.
The obtained IWebBrowserApp interface (for documentation refer to IWebBrowser2, as it's their implementation) provides except others also the information about the host window, like handle which can be later used for bringing the window to foreground. We need to go deeper though. So let's query this interface reference for the IServiceProvider interface (which is an accessor for getting interfaces for the given service).
Now from the top-most browser implementation service query its IShellBrowser interface. A reference of this interface is still not interesting for our aim.
The obtained IShellBrowser query for the displayed Shell view object.
Now we can finally say, if the iterated Shell window is not an Internet Explorer window. So far they were having common interfaces implemented. Now if we query the obtained IShellView for the IFolderView interface and it succeeds, it is not Internet Explorer and we can continue.
Query the obtained IFolderView reference for the IPersistFolder2 interface for the currently displayed folder object.
If we succeeded even there and we got IPersistFolder2 reference, let's get the ITEMIDLIST for the current folder object.
And if we succeeded even with this last step, we have ITEMIDLIST of the currently displayed folder of a Windows Explorer instance (or the same interface implementor) and we can finally check if the obtained ITEMIDLIST equals to the one we parsed for the input path. If so, bring that window to foreground, if not, continue to the next iteration.
And here is a Delphi code. I don't know how much do you need for your Delphi version; this was a bare minimum I've needed for D2009 (manually translated from Windows SDK 10.0.15063.0). It's not a best example; in real code you may prefer wrapping this into a class and have more flexible interface, but that's upon your design preference. And finally, if you have Delphi newer than 2009, you may not need the imported prototypes, if older, you might be missing some:
uses
ActiveX, ShlObj, SHDocVw, ComObj;
{ because of Win32Check }
{$WARN SYMBOL_PLATFORM OFF}
const
IID_IFolderView: TGUID = '{CDE725B0-CCC9-4519-917E-325D72FAB4CE}';
IID_IPersistFolder2: TGUID = '{1AC3D9F0-175C-11D1-95BE-00609797EA4F}';
IID_IServiceProvider: TGUID = '{6D5140C1-7436-11CE-8034-00AA006009FA}';
SID_STopLevelBrowser: TGUID = '{4C96BE40-915C-11CF-99D3-00AA004AE837}';
type
IFolderView = interface(IUnknown)
['{CDE725B0-CCC9-4519-917E-325D72FAB4CE}']
function GetCurrentViewMode(out pViewMode: UINT): HRESULT; stdcall;
function SetCurrentViewMode(ViewMode: UINT): HRESULT; stdcall;
function GetFolder(const riid: TIID; out ppv): HRESULT; stdcall;
function Item(iItemIndex: Integer; out ppidl: PItemIDList): HRESULT; stdcall;
function ItemCount(uFlags: UINT; out pcItems: Integer): HRESULT; stdcall;
function Items(uFlags: UINT; const riid: TIID; out ppv): HRESULT; stdcall;
function GetSelectionMarkedItem(out piItem: Integer): HRESULT; stdcall;
function GetFocusedItem(out piItem: Integer): HRESULT; stdcall;
function GetItemPosition(pidl: PItemIDList; out ppt: TPoint): HRESULT; stdcall;
function GetSpacing(var ppt: TPoint): HRESULT; stdcall;
function GetDefaultSpacing(out ppt: TPoint): HRESULT; stdcall;
function GetAutoArrange: HRESULT; stdcall;
function SelectItem(iItem: Integer; dwFlags: DWORD): HRESULT; stdcall;
function SelectAndPositionItems(cidl: UINT; var apidl: PItemIDList; var apt: TPoint; dwFlags: DWORD): HRESULT; stdcall;
end;
EShObjectNotFolder = class(Exception);
function ILGetSize(pidl: PItemIDList): UINT; stdcall;
external 'shell32.dll' name 'ILGetSize';
function ILIsEqual(pidl1: PItemIDList; pidl2: PItemIDList): BOOL; stdcall;
external 'shell32.dll' name 'ILIsEqual';
function InitVariantFromBuffer(pv: Pointer; cb: UINT; out pvar: OleVariant): HRESULT; stdcall;
external 'propsys.dll' name 'InitVariantFromBuffer';
function CoAllowSetForegroundWindow(pUnk: IUnknown; lpvReserved: Pointer): HRESULT; stdcall;
external 'ole32.dll' name 'CoAllowSetForegroundWindow';
resourcestring
rsObjectNotFolder = 'Object "%s" is not a folder.';
{ this parses the input folder path and creates ITEMIDLIST structure if the given
folder path is a valid absolute path to an existing folder }
function GetFolderIDList(const Folder: string): PItemIDList;
const
SFGAO_STREAM = $00400000;
var
Count: ULONG;
Attributes: ULONG;
ShellFolder: IShellFolder;
begin
OleCheck(SHGetDesktopFolder(ShellFolder));
Attributes := SFGAO_FOLDER or SFGAO_STREAM;
OleCheck(ShellFolder.ParseDisplayName(0, nil, PWideChar(WideString(Folder)), Count, Result, Attributes));
if not ((Attributes and SFGAO_FOLDER = SFGAO_FOLDER) and (Attributes and SFGAO_STREAM <> SFGAO_STREAM)) then
begin
CoTaskMemFree(Result);
raise EShObjectNotFolder.CreateFmt(rsObjectNotFolder, [Folder]);
end;
end;
{ translated from the link mentioned in this comment; D2009 does not allow me to
create an OleVariant of type VT_ARRAY|VT_UI1 which is needed for the Navigate2
method so I've imported and used the InitVariantFromBuffer function here
https://msdn.microsoft.com/en-us/library/windows/desktop/gg314982(v=vs.85).aspx }
procedure OpenNewExplorer(IDList: PItemIDList);
var
Location: OleVariant;
WebBrowser: IWebBrowser2;
begin
OleCheck(CoCreateInstance(CLASS_ShellBrowserWindow, nil, CLSCTX_LOCAL_SERVER, IID_IWebBrowser2, WebBrowser));
OleCheck(CoAllowSetForegroundWindow(WebBrowser, nil));
OleCheck(InitVariantFromBuffer(IDList, ILGetSize(IDList), Location));
try
WebBrowser.Navigate2(Location, Unassigned, Unassigned, Unassigned, Unassigned);
finally
VariantClear(Location);
end;
WebBrowser.Visible := True;
end;
{ translated from the link mentioned in this comment
https://blogs.msdn.microsoft.com/oldnewthing/20040720-00/?p=38393 }
procedure BrowseInExplorer(const Folder: string);
var
I: Integer;
WndIface: IDispatch;
ShellView: IShellView;
FolderView: IFolderView;
SrcFolderID: PItemIDList;
CurFolderID: PItemIDList;
ShellBrowser: IShellBrowser;
ShellWindows: IShellWindows;
WebBrowserApp: IWebBrowserApp;
PersistFolder: IPersistFolder2;
ServiceProvider: IServiceProvider;
begin
SrcFolderID := GetFolderIDList(Folder);
try
OleCheck(CoCreateInstance(CLASS_ShellWindows, nil, CLSCTX_LOCAL_SERVER, IID_IShellWindows, ShellWindows));
{ iterate all Shell windows }
for I := 0 to ShellWindows.Count - 1 do
begin
WndIface := ShellWindows.Item(VarAsType(I, VT_I4));
{ do not use OleCheck here; windows like Internet Explorer do not implement
all the interfaces; it is the way to distinguish Windows Explorer windows
actually; so let's get all the references and if we succeed, check if the
obtained folder equals to the passed one; if so, bring that window to top
and exit this procedure }
if Assigned(WndIface) and
Succeeded(WndIface.QueryInterface(IID_IWebBrowserApp, WebBrowserApp)) and
Succeeded(WebBrowserApp.QueryInterface(IID_IServiceProvider, ServiceProvider)) and
Succeeded(ServiceProvider.QueryService(SID_STopLevelBrowser, IID_IShellBrowser, ShellBrowser)) and
Succeeded(ShellBrowser.QueryActiveShellView(ShellView)) and
Succeeded(ShellView.QueryInterface(IID_IFolderView, FolderView)) and
Succeeded(FolderView.GetFolder(IID_IPersistFolder2, PersistFolder)) and
Succeeded(PersistFolder.GetCurFolder(CurFolderID)) and
ILIsEqual(SrcFolderID, CurFolderID) then
begin
{ restore the window if minimized, try to bring it to front and exit this
procedure }
if IsIconic(WebBrowserApp.HWnd) then
Win32Check(ShowWindow(WebBrowserApp.HWnd, SW_RESTORE));
{$IFNDEF IBelieveThatIWebBrowserAppVisiblePropertyBringsWindowToFront}
Win32Check(SetForegroundWindow(WebBrowserApp.HWnd));
{$ELSE}
OleCheck(CoAllowSetForegroundWindow(WebBrowserApp, nil));
WebBrowserApp.Visible := True;
{$ENDIF}
Exit;
end;
end;
{ the procedure was not exited, hence an existing window was not found, so go
and open the new one }
OpenNewExplorer(SrcFolderID);
finally
CoTaskMemFree(SrcFolderID);
end;
end;
{$WARN SYMBOL_PLATFORM ON}
Possible usage:
BrowseInExplorer('C:\MyFolder');

How can I modify my EnumWindowNamesProc to work with Lazarus and use a List as a parameter?

Overview
I am trying to enumerate a list of all visible window names and then populate a list passed as a parameter.
In Delphi I was able to get this to work with the following:
function EnumWindowNamesProc(wHandle: HWND; List: TStrings): BOOL; stdcall;
var
Title: array[0..255] of Char;
begin
Result := False;
GetWindowText(wHandle, Title, 255);
if IsWindowVisible(wHandle) then
begin
if Title <> '' then
begin
List.Add(string(Title));
end;
end;
Result := True;
end;
Then I could call the above like so:
EnumWindows(#EnumWindowNamesProc, LPARAM(FWindowNames));
Note: FWindowNames is a stringlist created inside a custom class.
Problem
I am also trying to make the function compatible with Lazarus/FPC but it wont accept the List: TStrings parameter.
The compiler error in Lazarus complains about a type mismatch (I have highlighted the important parts):
Error: Incompatible type for arg no. 1: Got " (address of
function(LongWord;TStrings):LongBool;StdCall) ", expected " (procedure
variable type of function(LongWord;LongInt):LongBool;StdCall) "
I can stop the compiler complaining by changing the function declaration like so:
{$IFDEF FPC}
function EnumWindowNamesProc(wHandle: HWND; Param: LPARAM): BOOL; stdcall;
{$ELSE}
function EnumWindowNamesProc(wHandle: HWND; List: TStrings): BOOL; stdcall;
{$ENDIF}
var
Title: array[0..255] of Char;
begin
Result := False;
GetWindowText(wHandle, Title, 255);
if IsWindowVisible(wHandle) then
begin
if Title <> '' then
begin
{$IFDEF FPC}
// List no longer available
{$ELSE}
List.Add(string(Title));
{$ENDIF}
end;
end;
Result := True;
end;
But then I lose my List parameter.
I know I could modify the code and use a Listbox1 for example directly inside the function but I was hoping to create a reusable function that does not need to know about VCL/LCL controls, instead I was hoping for a more elegant solution and simply pass a TStrings based parameter and add to this instead.
Question:
So my question is, in Delphi I am able to pass a TStrings based parameter to my EnumWindowNamesProc but in Lazarus it wont accept it. Is it possible, and if so, how can I modify the code so Lazarus accepts the List: TStrings parameter?
You can. You don't have to lose your List.
Just use correct parameters and typecast
function EnumWindowNamesProc(wHandle: HWND; List: LPARAM): BOOL; stdcall;
var
Title: array[0..255] of Char;
begin
Result := False;
GetWindowText(wHandle, Title, 255);
if IsWindowVisible(wHandle) then
begin
if Title <> '' then
begin
TStringList(List).Add(string(Title));
end;
end;
Result := True;
end;
EnumWindows(#EnumWindowNamesProc, LPARAM(list));
To complete my answer. There is one more option - define the function by yourself with pointer (like in Delphi). Then you can use it the same way.
function EnumWindows(lpEnumFunc:Pointer; lParam:LPARAM):WINBOOL; external 'user32' name 'EnumWindows';

Using interfaces to pass data from exe to dll in Delphi

I'm trying to pass an array of strings from host EXE to DLL in a loop. The host EXE is an external app which supports scripts (compiled and executed by a delphi-like compiler with certain limitations). The DLL is written by me in Delphi 2010.
So the problem is - I can't get rid of access violations when I pass a Widestring array to DLL. Whatever I tried - I got AVs sooner or later. And even if not, my statically loaded DLL was locked by host app, I can't rename or delete the DLL, so something is wrong.
There's an example of code which describes how to do it using interfaces (new for me which I liked a lot). Works like a charm and the DLL file is not locked.
General definitions (DLL + EXE)
type
// Your "structure"
TSomething = record
A: Integer;
S: WideString;
end;
// Your "structure list"
TSomethingArray = array of TSomething;
// The DLL and the EXE exchange data via Interface
IArray = interface
['{EE7F1553-D21F-4E0E-A9DA-C08B01011DBE}'] // press Ctrl+Shift+G to generate id
// protected
function GetCount: Integer; safecall;
function GetItem(const AIndex: Integer): TSomething; safecall;
// public
property Count: Integer read GetCount;
property Items[const AIndex: Integer]: TSomething read GetItem; default;
end;
DLL
// DLL, auxilary code:
type
// IArray implementation, which will be passed from DLL to EXE
TBaseArray = class(TInterfacedObject, IArray)
protected
FArray: TSomethingArray;
function GetCount: Integer; safecall;
function GetItem(const AIndex: Integer): TSomething; safecall;
end;
// Copying an array - for static and open arrays
TArray = class(TBaseArray)
public
constructor Create(const AArray: array of TSomething);
end;
// We don't have to copy an array for dynamic arrays
TArrayRef = class(TBaseArray)
public
constructor Create(const AArray: TSomethingArray);
end;
// We could create 1 class instead of 3 (TArray),
// but with 2 constructors (CreateByArr, CreateByDynArr).
// Or even with 1 constructor, if you work with 1 type of array.
// But it doesn't really matter, it's a matter of convenience.
// What really matters is a class which implements an interface.
{ TBaseArray }
function TBaseArray.GetCount: Integer;
begin
Result := Length(FArray);
end;
function TBaseArray.GetItem(const AIndex: Integer): TSomething;
begin
Result := FArray[AIndex];
end;
{ TArray }
constructor TArray.Create(const AArray: array of TSomething);
var
ArrIndex: Integer;
begin
inherited Create;
SetLength(FArray, Length(AArray));
for ArrIndex := 0 to High(AArray) do
FArray[ArrIndex] := AArray[ArrIndex];
end;
{ TArrayRef }
constructor TArrayRef.Create(const AArray: TSomethingArray);
begin
inherited Create;
FArray := AArray;
end;
// DLL, applied code:
function DoSomething1: IArray; stdcall;
var
A: array[0..2] of TSomething;
begin
// Some operations with array...
A[0].A := 1;
A[0].S := 'S1';
A[1].A := 2;
A[1].S := 'S2';
A[2].A := 3;
A[2].S := 'S3';
// Returning result
Result := TArray.Create(A); // <- An array is copied here
end;
function DoSomething2: IArray; stdcall;
var
A: TSomethingArray;
begin
// Some operations with array...
SetLength(A, 3);
A[0].A := 1;
A[0].S := 'S1';
A[1].A := 2;
A[1].S := 'S2';
A[2].A := 3;
A[2].S := 'S3';
// Returning result
Result := TArrayRef.Create(A); // An array isn't copied here, only reference counting
// We could also write:
// Result := TArray.Create(A);
// but the array would be copied in this case
end;
exports
DoSomething1, DoSomething2;
EXE
function DoSomething1: IArray; stdcall; external 'Project2.dll';
function DoSomething2: IArray; stdcall; external 'Project2.dll';
procedure TForm1.Button1Click(Sender: TObject);
var
A: IArray;
X: Integer;
begin
A := DoSomething1; // or DoSomething2
for X := 0 to A.Count - 1 do
OutputDebugString(PChar(IntToStr(A[X].A) + ' ' + A[X].S));
end;
This code works fine with my host app (when all OOP logic is in DLL). But I need the data to be passed from EXE to DLL.
So I swapped the code and the EXE became the 'heavier' part, everything was fine too, but only if both DLL and EXE are written in Delphi 2010.
If I use my host app and a static array, the compiler in my host app reports 'incompatible types' error at string in DoSomething1:
Result := TArray.Create(A);
When I write
Result := TArray.Create(A) as IArray;
it compiles but the app crashes.
If I use dynamic array, the compiler reports "Access violation at address 0D92062B. Read of address FFFFFFF8" here:
function TBaseArray.GetItem(const AIndex: Integer): TSomething;
begin
Result := FArray[AIndex];
end;
The author of this code said that if we want to pass data from EXE to DLL we need to use callbacks. And I just swapped the code. Maybe this is the problem? If so, how should I use callbacks here?

How do you get the piFileType in a TFileDialog.OnTypeChange event

How to get the FileDialog's FileTypeIndex in the FileDialog OnTypeChange Event?
function TFileDialogEvent.OnTypeChange(const pfd: IFileDialog):
HResult; stdcall;
var
iCaption: string;
iFilename: PWideChar;
begin
{Get the current filename}
pfd.GetFileName(iFilename);
{Get the classname of the dialog to set the caption}
if FClassName = 'TIEWin7FileOpenDialog' then
iCaption := 'Open- ' + iFilename
else
iCaption := 'Save As- ' + iFilename;
pfd.SetTitle(PWideChar(iCaption));
FileTypeIndex := pfd.GetFileTypeIndex(???);
end;
The documentation to IFileDialog::GetFileTypeIndex contains the answer. The C++ signature of that method is:
HRESULT GetFileTypeIndex(
[out] UINT *piFileType
);
That translates to Delphi as:
function GetFileTypeIndex(out FileType: UINT): HRESULT;
That said, the Delphi translation in ShlObj declares the parameter to be var which is semantically incorrect. As it happens it doesn't really matter.
Put it all together and you code should read like this:
OleCheck(pfd.GetFileTypeIndex(FileTypeIndex));
Note that I have added some error checking. You should too. The code in your question calls three different COM methods and in each case fails to check for errors.
You can use the FileTypeIndex property.

Resources