I'm trying to implement a record pointer in Inno Setup (Unicode) to match a Delphi DLL's specifications...
type
PUnzipFile = ^TUnzipFile;
TUnzipFile = record
Caption: WideString;
Src: WideString;
Dest: WideString;
Status: Integer;
Size: Integer;
ErrCode: Integer;
ErrMsg: WideString;
end;
TUnzipFiles = array of PUnzipFile;
function UnzipFiles(var Files: TUnzipFiles; const Silent: Bool): Bool;
external 'UnzipFiles#files:Unzipper.dll stdcall';
The problem is that the compiler fails on the line PUnzipFile = ^TUnzipFile; because apparently Inno Setup doesn't support pointers as Delphi does. This record pointer works perfect when implemented in Delphi...
function UnzipFiles(var Files: TUnzipFiles; const Silent: Bool): Bool; stdcall;
external 'Unzipper.dll';
How can I work with this DLL if Inno Setup doesn't support record pointers?
There is no need for pointers.
Inno Setup Pascal Script does not support pointers.
The statement:
function UnzipFiles(var Files: TUnzipFiles; const Silent: BOOL): BOOL;
external 'UnzipFiles#files:Unzipper.dll stdcall';
Passes Files as a var parameter, which means that what's really passed is a pointer to TUnzipFiles. There is no need to make TUnzipFiles array of pointers.
Just make it a normal array and everything will work.
The solution is to just use an array of the record in question:
TUnzipFiles = array of TUnzipFile;
Now it will work.
Because a var parameter passes a pointer internally your call will not be any slower (or faster).
That's the beauty of Delphi. It hides the complexity of pointers in almost all cases where you'd need it in C.
All objects references and var parameters are really pointers, but you needn't worry about that.
Related
I am trying to port some code that works in Delphi XE8 to Delphi 10 Seattle. This code calls the GetPath function in Winapi.Windows.
The new Win32 API function signature is:
function GetPath(DC: HDC; Points: PPointL; Types: PByte; nSize: Integer): Integer; stdcall;
In XE8, previously the function had "var Points,Types" which is known commonly as a "var untyped" parameter.
Fixing the code to work with Delphi 10 Seattle means "unifying" the arbitrary types in the application code to use exactly the types declared in the unit itself. However what is confusing me is that there are two types, PPointL, and TPoint, and when I get the GetPath function working, the data it populates is populated into an array of _POINTL records, declared thus in Winapi.Windows:
type
_POINTL = record { ptl }
x: Longint;
y: Longint;
end;
{$EXTERNALSYM _POINTL}
PPointL = ^TPointL;
TPointL = _POINTL;
However, there is also another type TPoint, declared in System.Types:
TPoint = record
X: FixedInt;
Y: FixedInt;
public
Elsewhere, FixedInt is aliased to Longint for both 32 bit and 64 bit Windows, and so TPoint and _POINTL are equivalent, as far as I can tell, on the Windows platform at least.
If existing application component code is all using a type named TPoint, like this:
procedure AddPoint(const P:TPoint);
... What sense am I to make of the situation on the ground inside the RTL sources in Delphi 10? What should my approach to fixing this be? Alias TPoint to _POINTL at the unit level?
How do I fix this and proceed? Since this code is a commercial component, I'm thinking I'll wait until the vendor fixes this, but then, I think that understanding the _POINTL and TPoint in the RTL, and why these structures are redundantly/duplicated in definition, would help others porting low level Win32 Code from Delphi XE8 to Delphi 10 Seattle.
Update: As a workaround, I find I can re-declare an import of the function GetPath, and have it remain as var untyped in my own private unit implementation area import, and continue:
{$ifdef D23}
{$POINTERMATH ON}
// Delphi 10 Seattle: function GetPath(DC: HDC; Points: PPointL; Types: PByte; nSize: Integer): Integer; stdcall;
// previously had "var Points,Types" untyped,
const
gdi32 = 'gdi32.dll';
{$EXTERNALSYM GetPath}
function GetPath(DC: HDC; var Points, Types; nSize: Integer): Integer; stdcall; external gdi32 name 'GetPath';
{$endif}
There's not much to be said about this, beyond the fact that the change to Winapi.Windows.GetPath in DX Seattle is wrong. I mean, technically it will work, but it leaves any code that uses GetPath in an isolated silo.
This TPointL type is not new, but it is the wrong type for GetPath. The Win32 API function is:
int GetPath(
_In_ HDC hdc,
_Out_ LPPOINT lpPoints,
_Out_ LPBYTE lpTypes,
_In_ int nSize
);
And LPPOINT is POINT* and POINT maps to TPoint. There are some Win32 API functions that use POINTL, but the majority use POINT. Of course, Microsoft are not helping by having declared two identical types when one would suffice.
Very hard to see how the Embarcadero developer has managed to come up with POINTL in the new GetPath, but there you go. In my view you should submit a QP report and request that the declaration is changed from PPointL to PPoint.
In the meantime a simple cast will suffice because these two types are binary compatible. You wish to pass a PPoint, but the compiler wants PPointL. So pass PPointL(...) where ... is the expression that yields a PPoint.
I need to wrap some legacy code in Delphi 7 for use within Delphi XE2. My question seems simple, but I tried ALL the examples I could find and they all fail. Basically, I need to be able to pass strings between D7 and DXE2, and as far as I can figure out, the safest approach is to use pchar (since I do not want to ship the borlandmm dll).
So DLL written in D7, to be called by Delphi XE2
My interface needs to be
IN My DLL:
function d7zipFile(pFatFile,pThinFile : PChar) : integer; stdCall;
function d7unzipfile(pThinFile,pFatFile : PChar) : integer; stdCall;
I need to pass BACK the pFatFile name in the unzipfile function.
In My calling code:
function d7zipFile(pFatFile,pThinFile : PChar) : integer; external 'd7b64zip.dll';
function d7unzipfile(pThinFile,pFatFile : PChar) : integer; external 'd7b64zip.dll';
Could someone please assist with the best way to implement these?
Obviously I am not looking for the actual zip/unzip code - I have that working fine within D7. I want to know how to declare and work with the string / pchar params, since the various types I tried (PWideChar, WideString, ShortString etc) all give errors.
So I would be happy to simply be able to do a showMessage in the d7zipFile function for both filenames.
And then be able to do a showMessage in delphiXE2 on the pFatFile variable, which means the strings went both ways OK?
By far the easiest way to do this is to use WideString. This is the Delphi wrapper around the COM BSTR type. Dynamic allocation of the string payload is done using the shared COM allocator. Since the Delphi RTL manages that, it is transparent to you.
In the Delphi 7 code you declare your functions like this:
function d7zipFile(const FatFile, ThinFile: WideString): integer; stdcall;
function d7unzipfile(const ThinFile: WideString; var FatFile: WideString):
integer; stdcall;
In your calling code you declare the functions like this:
function d7zipFile(const FatFile, ThinFile: WideString): integer; stdcall;
external 'd7b64zip.dll';
function d7unzipfile(const ThinFile: WideString; var FatFile: WideString):
integer; stdcall; external 'd7b64zip.dll';
The alternative to this approach is to use PAnsiChar or PWideChar. Note that you cannot use PChar because that alias refers to different types depending on which version of Delphi you use. In Delphi 7 PChar is an alias for PAnsiChar, and in XE2 it is an alias for PWideChar.
The big downside of using PAnsiChar, say, is that the caller needs to allocate the string which is returned from the DLL. But typically the caller does not know how large that string needs to be. There are a variety of solutions to the problem but the neatest approach is always to use a shared allocator. You state that you do not want to rely on borlandmm.dll and so the next most obvious common allocator is the COM allocator. And that's why WideString is attractive.
Im trying to use a function from Windows API on delphi, functions for Windows Wlanapi.dll (Native WIFI API)
WlanOpenHandle
DWORD WINAPI WlanOpenHandle(
__in DWORD dwClientVersion,
__reserved PVOID pReserved,
__out PDWORD pdwNegotiatedVersion,
__out PHANDLE phClientHandle
);
WlanHostedNetworkQueryProperty
DWORD WINAPI WlanHostedNetworkQueryProperty(
__in HANDLE hClientHandle,
__in WLAN_HOSTED_NETWORK_OPCODE OpCode,
__out PDWORD pdwDataSize,
__out PVOID *ppvData,
__out PWLAN_OPCODE_VALUE_TYPE *pWlanOpcodeValueType,
__reserved PVOID pvReserved
);
I trying to use this functions and others for hours, reading the MSDN references and others sites, but I just can't get this working.
My attempt
type
TWlanOpenHandle = function( dwClientVersion:DWORD;
pReserved:Pointer;
pdwNegotiatedVersion:PDWORD;
phClientHandle:PHANDLE
):DWORD; stdcall;
function apiWlanOpenHandle( dwClientVersion:DWORD;
pReserved:Pointer;
pdwNegotiatedVersion:PDWORD;
phClientHandle:PHANDLE
):DWORD;
implementation
function apiWlanOpenHandle ( dwClientVersion:DWORD; pReserved:Pointer; pdwNegotiatedVersion:PDWORD; phClientHandle:PHANDLE ):DWORD;
var
WlanOpenHandle: TWlanOpenHandle;
DLL: Cardinal;
begin
DLL:=LoadLibrary('Wlanapi.dll');
WlanOpenHandle := GetProcAddress(DLL, 'WlanOpenHandle');
if Assigned(WlanOpenHandle) then
begin
WlanOpenHandle(dwClientVersion, pReserved, pdwNegotiatedVersion, phClientHandle);
end
else begin
ShowMessage('Function not found');
end;
end;
I'm trying to translate this API, seems a lot of work, and I'm just a beginner in delphi, I read a lot of stuff on the web, how do I deal with this OpCode parameter, seems a C Struct with constants, and PWLAN_OPCODE_VALUE_TYPE?
http://msdn.microsoft.com/en-us/library/windows/desktop/dd439502(v=vs.85).aspx
You didn't actually show how you called apiWlanOpenHandle which would, I think, explain what the problem is. However, there's one very common mistake that is most likely what is confusing you.
Consider the C declaration of the API:
DWORD WINAPI WlanOpenHandle(
__in DWORD dwClientVersion,
__reserved PVOID pReserved,
__out PDWORD pdwNegotiatedVersion,
__out PHANDLE phClientHandle
);
The parameters that I suspect are causing you problems are the final two. Let us consider pdwNegotiatedVersion. This is a pointer to a DWORD. Because this is an out parameter you must supply a pointer to valid memory. I suspect you are just declaring a variable of type PDWORD and passing that.
var
NegotiatedVersionPtr: PDWORD;
begin
WlanOpenHandle(...., NegotiatedVersionPtr, ...);
The function WlanOpenHandle then de-references that pointer and tries to write to the memory. If you have not given a valid pointer then this will fail.
The naive solution is to change the calling code to look like this:
var
NegotiatedVersion: DWORD;
NegotiatedVersionPtr: PDWORD;
begin
NegotiatedVersionPtr := #NegotiatedVersion;
WlanOpenHandle(...., NegotiatedVersionPtr, ...);
This will work but there is a much cleaner way. Declare the API import like this:
function WlanOpenHandle(
dwClientVersion: DWORD;
pReserved: Pointer;
out NegotiatedVersion: DWORD;
out ClientHandle: THandle
): DWORD; stdcall; external 'Wlanapi.dll';
An out parameter of type DWORD is actually passed as a pointer to the DWORD that you supply as the argument to the function call. You can then change your calling code to look like this:
var
ReturnValue: DWORD;
NegotiatedVersion: DWORD;
ClientHandle: THandle;
begin
ReturnValue := WlanOpenHandle(2, nil, NegotiatedVersion, ClientHandle);
if ReturnValue<>ERROR_SUCCESS then
//respond to error
Note that I have also added some error checking which you really ought to be doing.
The reason that the Windows API function is declared using pointers is that the C language only supports parameter passing by value. It simply does not have pass-by-reference, i.e. out or var in Delphi terms. Languages that do support pass-by-reference should make use of them when they can.
Some Windows API functions have optional parameters declared as pointers. When this is the case passing NULL as the pointer is the way to signal that you do not wish to pass a parameter. Translating those APIs to Delphi is more complex. You need to implement a version using pointers to allow callers to opt-out of supplying the parameter. But it can be helpful to supply an overloaded version that uses out or var for convenience to the caller. The Delphi Windows unit contains many such examples.
As for WlanHostedNetworkQueryProperty, I would declare it like this:
const
// WLAN_HOSTED_NETWORK_OPCODE constants
wlan_hosted_network_opcode_connection_settings = 0;
wlan_hosted_network_opcode_security_settings = 1;
wlan_hosted_network_opcode_station_profile = 2;
wlan_hosted_network_opcode_enable = 3;
// WLAN_OPCODE_VALUE_TYPE constants
wlan_opcode_value_type_query_only = 0;
wlan_opcode_value_type_set_by_group_policy = 1;
wlan_opcode_value_type_set_by_user = 2;
wlan_opcode_value_type_invalid = 3;
function WlanHostedNetworkQueryProperty(
hClientHandle: THandle;
OpCode: Integer;
out DataSize: DWORD;
out Data: Pointer;
out WlanOpcodeValueType: Integer;
Reserved: Pointer
): DWORD; external 'Wlanapi.dll' delayed;
I have used the delayed facility because this is a Windows 7 and up API. You will presumably want your program to run on older versions of Windows and so delay loading is needed. For more information on delay loading in Delphi, see this answer, and particularly the onward links.
Note that the documentation in the MSDN topic to which you link is incorrect. The pWlanOpcodeValueType parameter is declared incorrectly in the MSDN topic. The correct definition, the one to be found in wlanpi.h is this:
__out PWLAN_OPCODE_VALUE_TYPE pWlanOpcodeValueType,
I want to use HttpOpenRequest to download a file from internet using GET. I don't know how to declare the AcceptType parameter. The MS documentations says that it is an array of strings. So I declare it like this:
CONST
AcceptType: packed array[0..1] of LPWSTR = (PChar('*/*'), nil);
I have done something wrong? LPWSTR is a pointer to a string, however, the documentation says that I need a string. How do I declare a matrix of strings that are compatible with C++ ?
procedure THTTPGetThread.Execute;
CONST
AcceptType: packed array[0..1] of LPWSTR = (PChar('*/*'), nil); // Originally was: AcceptType:= PWideChar('Accept: ' + FTAcceptTypes);
VAR
hConnect: hInternet;
FileName: String;
Data: Array[0..1024] of Char;
TempStr: PAnsiChar;
RequestMethod: PChar;
InternetFlag: DWord;
begin
...
hRequest:= HttpOpenRequest(hConnect, RequestMethod, PChar(FileName), PChar('HTTP/1.0'), PChar(FTReferer), #AcceptType, InternetFlag, 0);
...
end;
I use Delphi XE.
The MS documentation is here: http://msdn.microsoft.com/en-us/library/windows/desktop/aa384233(v=vs.85).aspx
My function is multi-threaded so it won't block the program while it downloads. Single threaded functions won't work for me.
You are almost there. You just need to pass a pointer to the first element of the array:
#AcceptType[0]
In fact, as #Serg points out, this is equivalent to your existing code. So it seems that, as you have commented below, the issue you are facing is unrelated to the passing of this parameter.
As an aside, I think I would use PWideChar rather than LPWSTR, but that's not the issue here since they are equivalent.
I have a dll library. I have excluded memory unit for delphi types.
In that way, what would be the appropriate Boolean type for function declaration?
Is it BOOL or something else?
The problem is that in the method signature:
function Test(Param1: BOOL; Param2: BOOL; docContent: PCharArray): Integer;
I get AV when program leaves that function.
I assume that it is the problem with the data type of these two first parameters.
BOOL is fine for Boolean types. It's a Windows type, so it's what you'll see in all the functions in Windows.pas.
Access violations upon return from a DLL function often indicate that you have the calling convention wrong — the default calling convention is register, but you probably need stdcall or cdecl. Add it at the end of the declaration:
function Test(Param1: BOOL; Param2: BOOL; docContent: PCharArray): Integer; stdcall;