How can I pass a Delphi string to a Prism DLL? - delphi

We try to pass a string from a native Delphi program to a Delphi Prism DLL.
We have no problem passing integers, but strings are mismatched in the DLL.
We saw Robert Love's code snippet in response to another question, but there is no code for the native Delphi program.
How can we pass strings from Delphi to a Delphi Prism DLL?

The best way would be to use WideString.
For several reasons.
It is Unicode and works before D2009
It's memory is managed in ole32.dll, so no dependency on either Delphi's memory manager or the CLR GC.
You do not have to directly deal with pointers
In Oxygene, you could write it like so:
type
Sample = static class
private
[UnmanagedExport]
method StringTest([MarshalAs(UnmanagedType.BStr)]input : String;
[MarshalAs(UnmanagedType.BStr)]out output : String);
end;
implementation
method Sample.StringTest(input : String; out output : String);
begin
output := input + "ä ~ î 暗";
end;
"MarshalAs" tells the CLR how to marshal strings back and forth. Without it, strings are passed as Ansi (PAnsiChar), which is probably NOT what you would want to do.
This is how to use it from Delphi:
procedure StringTest(const input : WideString; out output : WideString);
stdcall; external 'OxygeneLib';
var
input, output : WideString;
begin
input := 'A b c';
StringTest(input, output);
Writeln(output);
end.
Also, never ever use types, that are not clearly defined, for external interfaces.
You must not use PChar for DLL imports or exports. Because if you do, you will run into exceptions when you compile it with D7 or D2009 (depending on what the original dev system was)

Strings in Delphi Win32 are managed differently from strings in .Net, so you can not pass a .Net string to Delphi Win32 or vice versa.
To exchange strings values you'd better use PChar type which is supported by both compilers. That is the same way you send string values to Windows API functions.
Regards
P.S. I am NOT Robert ;-)

Related

vb6 dll In Delphi

I have written a VB6 dll with this code
Public Function RegGetStr(ByRef FullLocation_Name As String) As String
Dim oReg As Object
Set oReg = CreateObject("WScript.Shell")
RegGetStr = oReg.RegRead(FullLocation_Name, "REG_SZ")
Set oReg = Nothing
End Function
In Delphi I have library working
type
TRegGetStr = Function(Const FullLocation_Name: String): String; StdCall;
var
aRegGetStr: TRegGetStr;
and
#aRegGetStr := Windows.GetProcAddress(LibHandle, 'RegGetStr');
I have a crash
Is it because of the types of strings I'm using? or something else?
Your question asked about calling a VB DLL from Delphi.
VB6, out of the box, creates ActiveX dll's (created as an ActiveX DLL project)
How can I create a standard DLL in VB6? Look at the answer there describing ActiveX DLL.
While you can create standard DLL's in VB6 you have to go spelunking. It does not support that out of the box and is not recommended.
You would consume that VB ActiveX DLL just as you would any ActiveX dll in Delphi.
Deplhi Import Component - Type Library vs ActiveX
=====
IF, on the other hand, you are trying to call an external DLL (e.g. written in Delphi) FROM a VB application, you would create the Delphi function with pAnsiChar parameters for strings. Ex: in MyDelProcs.dll:
procedure MyDelProc(pStr1: pAnsiChar, MyInt: integer);stdcall;
.......................
exports
MyDelProc;
On the VB side you would then declare the procedure as
Declare Sub MyDelProc Lib "MyDelProcs.dll" (ByVal sStr1 as String, MyLong As Long)
Then call the procedure with
Call MyDelProc(sStr1, MyLong)
Some notes:
sStr1 is Unicode internal to VB, but on the call is converted to AnsiString. The ansistring has a chr(0) added to the end and moved to a buffer which is the length of the ansistring plus the chr(0).
Take care with other parameter types. Default Integer in VB is 2 bytes, default integer in Delphi is 4, which is equivalent to a Long type in VB.
If your Delphi function needs to know the length, pass it as another (long/integer) parameter.
The string buffer is fixed in size. If you are returning a string of longer length then either pad the original string with enough room, or pass a longer dummy string with enough length for the return.
Upon return the string is treated as ansistring. It will be converted back to Unicode to be placed into a VB internal string.

Delphi 7 calling DelphiXE2 dll getting corrupt widestrings

I have a Delphi 7 application that needs to call a SOAP API that is much too new for the available SOAP importers. I have satisfied myself that D7 can't call the SOAP API without too much effort to be worth while. But I also have Delphi XE2, and that can import the SOAP and call it quite happily. So I have written a simple dll wrapper in XE2 that exposes the necessary parts of the soap interface. I can call the dll from an XE program.
In Delphi7 I took the SOAP API import file from XE, stripped out the {$SCOPED_ENUMS ON} defines and the initialization section that calls unavailable SOAP wrappers, plus changed string to widestring throughout. That compiles. I'm using FastMM with ShareMM enabled to make string passing work and avoid making everything stdcall.
The reason I'm trying to do it this way is that if it works it will make the SOAP shim very easy to code and maintain, since 90% of the code is generated by the XE2 SOAP importer, and it will mean that when we move the D7 app to a modern Delphi the code will remain largely unchanged.
But when I run it, I get weird strings (and consequent access violations). I've got simple functions that don't use the SOAP code to make the problem more obvious.
Passing a widestring from Delphi7 exe into DelphiXE2 dll the string length is doubled (according to the Length() function), but there's no matching data conversion. So a widestring "123" in D7 becomes "1234...." in XE2, where the .... is whatever garbage happens to be on the stack. Viewed as byte arrays both have half zero bytes as expect.
Passing a widestring back from XE2 dll to D7 I get the mirror effect - the string length is halved and strings are simply truncated ("1234" becomes "12").
I'm pasting code in because I know you will ask for it.
In Delphi XE2 I'm exporting these functions:
// testing
function GetString(s:string):string; export;
function AddToString(s:string):string; export;
implementation
function GetString(s:string):string;
begin
Result := '0987654321';
end;
function AddToString(s:string):string;
begin
Result := s + '| ' + IntToStr(length(s)) + ' there is more';
end;
In Delphi 7:
function GetString(s:widestring):widestring; external 'SMSShim.dll';
function AddToString(s:widestring):widestring; external 'SMSShim.dll';
procedure TForm1.btnTestGetClick(Sender: TObject);
var
s: widestring;
begin
s := widestring('1234');
Memo1.Lines.Add(' GetString: ' + GetString(s));
end;
procedure TForm1.btnTestAddClick(Sender: TObject);
var
s: widestring;
begin
s := widestring('1234567890');
Memo1.Lines.Add(' AddToString: ' + AddToString('1234567890'));
end;
I can run from either side, using the D7 executable as the host app to debug the dll. Inspecting the parameters and return values in the debugger gives the results above.
Annoyingly, if I declare the imports in delphi7 as strings I get the correct length but invalid data. Declaring as shown I get valid data, wrong lengths, and access violations when I try to return.
Making it all stdcall doesn't change the behaviour.
The obvious solution is the just write simple wrapper functions that expose exactly the functionality I need right now. I can do that, but I'd prefer the above cunning way.
The DLL in question exports functions that expect to receive UnicodeString parameters. (As you know, the string type became an alias for UnicodeString in Delphi 2009.) A Delphi 7 application cannot consume that DLL; the run-time library doesn't not know how to operate on that type because it didn't exist back in 2002 when Delphi 7 was published.
Although the character size for UnicodeString is compatible with WideString, they are not the same types. UnicodeString is structured like the new AnsiString, so it has a length field, a reference count, a character size, and a code page. WideString has a length field, but any other metadata it carries is undocumented. WideString is simply Delphi's way of exposing the COM BSTR type.
A general rule to live by is to never export DLL functions that couldn't be consumed by C.1 In particular, this means using only C-compatible types for any function parameters and return types, so string is out, but WideString is safe because of its BSTR roots.
Change the DLL to use WideString for its parameters instead of string.
1 Maintaining C compatibility also means using calling conventions that C supports. Delphi's default register calling convention is not supported in Microsoft C, so use cdecl or stdcall instead, just like you've seen in every Windows DLL you've ever used.
There's not way to disable the UNICODE in Delphi XE2 (or any version greater than 2009) , however there are many resources that can help you to migrate your application.
White Paper: Delphi and Unicode (from Marco Cantù)
Delphi Conversion Unicode Issues
"Globalizing your Delphi applications" - Delphi Unicode Resources
Compilation of resources for migrate to Delphi 2009/2010 Unicode

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.

Passing parameters from Delphi 5 to Delphi DLL XE

I have a Delphi 5 application in the application code calls a function in the DLL, passing integer and string parameters, this works well when the DLL is called in a static way, when I try to dynamically change does not work.
which is the correct way to pass parameters to function dynamically?
the code is as follows
main application
function Modulo_Pptos_Operacion(No_Orden : Integer; pathBD : string; PathBDConf : String) : Integer ; stdcall;
external 'LIB_Pptos_Oper.dll';
Modulo_Pptos_Operacion(DmDatos.OrdenesNO_Orden.AsInteger,
DmDatos.CiasPATHA.AsString, 'Alguna String');
DLL
Modulo_Pptos_Operacion function (No_Orden: Integer; PathDB: AnsiString; PathDBConfig: AnsiString): Integer; StdCall;
DYNAMIC CRASH
main application
type
TDLLPpto = function(No_Orden : Integer; PathDB : AnsiString; PathDBConfig : AnsiString) : Integer;
var
DLLHandle: THandle;
: TDLLPpto;
PROCEDURE CALL
DLLHandle := LoadLibrary('LIB_Pptos_Oper.dll');
DLLHandle <> 0 then
begin
#DLLPpto := GetProcAddress(DLLHandle, 'Modulo_Pptos_Operacion');
end;
;
which is the right way?
The problem is probably that you are mixing different runtimes and probably different heaps. Delphi strings are not valid interop types because their implementations vary from version to version.
In this case you can simply switch to using null-terminated strings, PAnsiChar.
In the case of dynamically loaded dll you omitted stdcall; calling convention directive in the declaration of TDLLPpto. Still it is advisable to use PAnsiChar type to pass strings across executable boundaries.
The layout of ansistring has changed with Delphi XE: now there is also a codepage field at negative offset and D5 does not have that. EG: strings from D5 and DXE are utterly incompatible. Thus you should use PAnsiChar or PWideChar in your interface, either zero terminated (Delphi strings are always zero terminated) of introduce an extra parameter with the length if the string might contain #$00 bytes.
Also: the different Delphi versions both have different memory managers. If a string is allocated by the main app and freed by the DLL (strings are reference counted) the pointer get's passed to the wrong memory manager which usually results in corrupted memory and thus nasty Access Violations etc.
Another solution is to use WideString; this is both in D5 en DXE equal to a COM BSTR stringtype and managed by the OS and not the Delphi memory manager. They are compatible. The only problem is: they are slow compared to the Delphi strings and are not ref counted.
In all: when using DLL interfaces, try to avoid string, use PAnsiChar or PWideChar, or WideString

Delphi - Problem using an existing dll

I have to use an existing dll, but got a problem with it.
Here's how I try to use it:
unit u_main;
...
implementation
procedure getUserData(var User, Pass: string); stdcall; external 'Common5.dll';
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
u, p: string;
begin
getUserData(u,p);
end;
...
end.
When I press the button the get the userData, I get an EInvalidPointer exception.
The dll is registerd and in some other projects it's in use and work. any ideas?
EDIT:
The DLL was created in Delphi7 and is now used in a Delphi 2009 project.
Maybe there's a problem with unicode strings or something like that?
You need to rebuild the Delphi 7 DLL, make it follow the WinApi standard of getting PChar and BufferLen parameters. You've got multiple problems with the current implementation:
string is platform-specific, it's implementation may change between delphi versions (and did change). You're not supposed to use string outside the platform!
You're passing the parameters as "var", suggesting the DLL might change the value of user and/or pass. String is an special, managed type, changing it requires allocating memory for the new string. This in turns requires you to share the memory manager between the DLL and the EXE (using sharemem.pas and BorlandMM.dll - or variants). The trouble is, sharing the memory manager between different versions of Delphi is an unsupported configuration! (gotton from embarcadero forums)
The Delphi 7 is hoping to receive an simple AnsiString (1 byte chars), while the Delphi 2009 exe is sending Unicode strings (2 bytes per char).
Along with using PChar, be sure to pre-allocate the space before you call GetUserData. i.e. if you assign 'foo' into a pchar that's empty, you'll blow up. So either use static length PChar/PAnsiChar arrays, or use this technique:
var
s : AnsiString;
begin
setlength(s,256);
MyDLLProc(PAnsiChar(s));
end;

Resources