Reference parameters in Delphi to C++ DLL - delphi

I'm creating a DLL in Delphi that must have the following C++ structure.
DWORD Load(char* &Test);
So the test must be a reference parameter. I tried 'var' and 'out' in Deplhi, but I get an error in my C++ application that uses the DLL.

A literal translation of that code is this:
function Load(var Test: PAnsiChar): DWord; cdecl;
Notice the calling convention. If you're missing that, then Delphi places the first parameter in a register, but the C++ code expects it on the top of the stack.

Like 'Rob Kennedy' stated, it must have 'cdecl'. I fixed the problem by using that. Here is the fixed code
function Load(out Test : PAsniChar) : Integer; cdecl ; export;
begin
Test := 'Test String';
end;
Thanks for the help!

Related

Convert variable arguments in C stdcall interface method to Delphi

I am converting the ICallFrame interface to Delphi and I am not sure how to handle the ICallFrame::Invoke method.
This is the definition:
HRESULT Invoke(
void *pvReceiver,
...
);
Per documentation the varargs directive works only with external routines and only with the cdecl calling convention.
It's a bit strange as Invoke is declared with STDMETHODCALLTYPE thus __stdcall, which is a callee-clean convention. But a variadic function cannot be callee-clean since the callee does not know how many parameters were passed.
Is there some kind of compiler magic if you do this with Visual Studio?
Even if compiler magic uses cdecl, the Delphi compiler doesn't accept this (because it's not external) E2070 Unknown directive: 'varargs'
ICallFrame = interface(IUnknown)
['{D573B4B0-894E-11d2-B8B6-00C04FB9618A}']
// other methods left out
function Invoke(pvReceiver: PVOID): HRESULT; cdecl; varargs;
end;
Based on Rudy's answer it seems this could work:
type
TfnInvoke = function(this: Pointer; pvReceiver: Pointer): HRESULT; cdecl varargs;
implementation
function GetInterfaceMethod(const intf; methodIndex: dword) : pointer;
begin
Result := Pointer(pointer(dword_ptr(pointer(intf)^) + methodIndex * SizeOf(Pointer))^);
end;
...
var
Invoker: TfnInvoke;
...
// Don't forget IUnknown methods when counting!
Invoker := GetInterfaceMethod(myIntf, 21);
Invoker(myIntf, FObject, 11, 17.3);
I will test with this and report back...
EDIT: tested and works.
Stdcall cannot use variable arguments. Any C or C++ function taking variable arguments must be __cdecl, because only in that calling convention, the caller (code), which knows how many parameters were passed, cleans up the stack. Even if the default calling convention is stdcall, var args functions will be cdecl.
Delphi can not generate functions like that, but it can consume existing external C functions (in a DLL or .obj file) using the varargs directive.
So a C (or C++) function like
HRESULT Invoke(void *pvReceiver, ...);
should be converted as:
function Invoke(pvReceiver: Pointer); cdecl; varargs;
Note that you leave out the ... part in the Delphi declaration. It is assumed when you use the varargs directive.
And that allows you to use it in Delphi like in C or C++:
Invoke(someReceiver, 17, 1.456);
Another example: for instance the well known C function printf():
int printf(const char *format, ...);
is declared as
function printf(format: PAnsiChar {args}): Integer; cdecl; varargs;
in System.Win.Crtl.pas.
Update
This seems to be a method of an interface.
Not sure if that works, but I would try:
type
TInvoke = function(Intf: IInterface; pvReceiver: Pointer): HRESULT; cdecl varargs;
// No semicolon between cdecl and varargs.
and use it as:
MyResult := TInvoke(#myIntf.Invoke)(myIntf, someReceiver, 11, 17.3);
This method may have been declared as __stdcall (through the STDMETHODCALLTYPE macro), but even then, if it is variadic, the (VC++) compiler will generate __cdecl, as Remy Lebeau found in Raymond Chen's blog.

How to adjust the level of compression with TZCompressionStream?

When I create a TZCompressionStream object with:
var
cs: TZCompressionStream;
dest: TStream;
level: TZCompressionLevel;
...
cs := TZCompressionStream.Create(level, dest);
I get this compiler error:
E2250 There is no overloaded version of 'Create' that can be called with these arguments
But my code is according to the constructor declaration:
Create(compressionLevel: TZCompressionLevel; dest: TStream); overload;
When I used XE, everything was OK. But now with XE5 there is this error. Why?
Update:
Working code: cs := TZCompressionStream.Create(dest);
Faliing code: cs := TZCompressionStream.Create(clMax, dest);
I also tried to change the order of the arguments, which was unsuccessful.
I'm assuming that your code is as stated in your edit:
cs:=TZCompressionStream.Create(clMax, dest);
The obvious explanation is that clMax is not what you think it is. There is probably another unit that defines clMax and that unit appears after ZLib in your list of uses. Solve the problem by either:
fully qualifying the enumerated value: ZLib.clMax, or
change the order of the uses so that ZLib appears after the unit which defines the other clMax.

Point to a DLL using a dynamic path

My problem is not exporting a function, but importing it. I know for sure that both the function and DLL both work because I have used a hard-coded path to point to the DLL.
This is what is currently working:
function RoamingAppDataPath: String; external 'C:\Users\Peter\AppData\Roaming\ss\Application\ss.dll';
However I need to point to the DLL with a dynamic value so what I tried to do is
Declare a global variable (DLLPath: String)
Assign DLLPath the value - RoamingAppDataPath+'\ss\Application\ss.dll'
Note: RoamingAppDataPath is a function that outputs the path to the roaming app data folder.
The code I am trying to run is:
function RoamingAppDataPath: String; external DLLPath;
When I compile the code, Delphi is telling me that it is expecting a constant expression:
E2026 Constant expression expected
What is the work around for this?
You have to bind at runtime and that means you need to use LoadLibrary and GetProcAddress:
var
lib: HMODULE;
RoamingAppDataPath: function: string;
lib := LoadLibrary(dllfilename);
if lib=0 then
RaiseLastOSError;
Pointer(RoamingAppDataPath) := GetProcAddress(lib, 'RoamingAppDataPath');
And then you can call it:
radp := RoamingAppDataPath;
Some comments:
I don't know why you write this function when it exists in standard system libraries.
Using string across DLL boundaries is liable to fail. You need to be using ShareMem and make sure that all code is built with the same Delphi version. Better to allocate the buffer in the calling code.
Even if you would be able to use a Variable, you would nowhere be able to set a value to DLLPATH, since already initalization would not be used if a static DLL can not be used.
You will have to use dynamic loadingif you want to define the path for the DLL.
procedure Test;external 'Notexists.DLL';
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
test;
end;
initialization
Showmessage('Hallo'); // will never be seen if test is used.

Delphi 7 DLL to be called from Delphi XE

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.

Is it safe to pass Delphi const string params across memory manager boundaries?

Subj. I'd like to use strings instead of PChar because that spares me much casting, but if I just do
procedure SomeExternalProc(s: string); external SOMEDLL_DLL;
and then implement it in some other project with non-shared memory manager:
library SeparateDll;
procedure SomeExternalProc(s: string);
begin
//a bla bla bla
//code here code here
end;
I have (formally) no guarantee Delphi does not decide for whatever reason to alter the string, modify its reference counter, duplicate or unique it, or whatever else. For example
var InternalString: string;
procedure SomeExternalProc(s: string);
begin
InternalString := s;
end;
Delphi increments refcounter and copies a pointer, that's it. I'd like Delphi to copy the data. Does declaring the parameter as "const" make it safe for that reason? If not, is there a way to do it? Declaring parameter as PChar doesn't seem to be a solution because you need to cast it every time:
procedure SomeExternalProc(s: Pchar); forward;
procedure LocalProc;
var local_s: string;
begin
SomeExternalProc(local_s); //<<--- incompatible types: 'string' and 'PAnsiChar'
end;
That would probably work, as long as you only ever use your DLL from code compiled in the same version of Delphi. The internal format of string has been known to change between releases, and you have no formal guarantee that it won't change again.
If you want to avoid having to cast everywhere you use it, try wrapping the function, like this:
procedure SomeExternalProc(s: Pchar); external dllname;
procedure MyExternalProc(s: string); inline;
begin
SomeExternalProc(PChar(local_s));
end;
Then in your code, you call MyExternalProc instead of SomeExternalProc, and everyone's happy.
If both the app and the DLL are written in the same Delphi release, just use shared memory manager (more details here).
If one side is written in a different language than there's no other way but to use PChar or WideString (WideStrings are managed by the COM memory manager).
Or you can write a wrapper function:
procedure MyExternalProc(const s: string);
begin
SomeExternalProc(PChar(s));
end;
Just to add a single fact:
Delphi allows you to simply assign PChar to a string so on the DLL side you don't need any typecast:
function MyDllFunction(_s: PChar): integer;
var
s: string;
begin
s := _s; // implicit conversion to string
// now work with s instead of the _s parameter
end;
This also applies for passing PChar as a parameter to a function that expects a (by value) string.
I recommend to use an alternative memory manager such as RecyclerMM or FastMM. They doesn't require any external shared MM dll's and allows you to pass strings to the dlls safely. As a bonus, you may get a nice performance improvement in whole application.
FastMM is used as a default memory manager in Delphi 2006 and above. Also it's a good tool to search the memory-leaks.

Resources