views:

164

answers:

4

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;
+10  A: 

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.

Mason Wheeler
Heh, beat me by less than a minute :)
gabr
+1 because of the versioning detail. Good.
Marco van de Voort
I've been doing that for some time, but writing all these stubs... (*edit: checked generated code for inline version, no addrefs after all*)
himself
Oh, and it's not always possible, take interfaces for example - you can't extend them.
himself
@himself: Good point. Too bad you can't declare an interface helper, like you can for classes and records. They'd really come in handy for this scenario.
Mason Wheeler
Found a solution though, just now: declare TWO interfaces with the same guid and different declarations: one with "const string", the other "pchar". Implement the one with pchar, access through the one with strings. Dirty hack, but better than nothing.
himself
+5  A: 

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;
gabr
A: 

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.

Andrew
Thanks, though I know all of that. I'm not using, or more precisely, not *relying* on shared mm because I want interoperability, and I also think relying on shared mm is bad style - makes you forget about proper memory management.
himself
-1. This is not true. FastMM does require you to share the memory manager in order to pass strings around safely. It comes with a unit called SimpleShareMem to accomplish this. There's simply no way to share string management safely without making both sources use the same heap.
Mason Wheeler
Mason, where did I say something like that? I was writing about the external DLL (borlandmm.dll). Yes, SimpleShareMem is needed if you wish to share the memory between application that don't use FastMM and libraries that use FastMM. But I was talking about using FastMM in both application and dll.
Andrew
Even when you use FastMM for both the application and the DLL, if you don't put SimpleShareMem in, the app and the DLL will end up with their own heaps instead of sharing. It's true that it doesn't require an external shared MM DLL, but you still need to explicitly set up shared memory within both the EXE and the DLL or it won't work.
Mason Wheeler
AFAIK the full FastMM is able to share the MM without SimpleShareMem - just enable the proper defines in the .inc file
ldsandon
True. Here is the quote from the FastMM FAQ:Q: How do I get my DLL and main application to share FastMM so I can safely pass long strings and dynamic arrays between them?A: The easiest way is to define ShareMM, ShareMMIfLibrary and AttemptToUseSharedMM in FastMM4.pas and add FastMM4.pas to the top of the uses section of the .dpr for both the main application and the DLL.
Andrew
A: 

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.

dummzeuch