I've had my first go at writing a DLL in Delphi. So far so good. By
using a typelib I've been able to pass Widestrings to and from the DLL
without difficulty.
What's curious at the moment is that I'm using VB6 as the testbed, and
every time I run a test within the IDE, the program runs and then the
IDE process suddenly disappears from memory - no error messages,
nothing. If I step through the code, everything works fine until I
execute the last line, then the IDE disappears.
By contrast, when I compile the test to an EXE the program runs to its
end, without error messages etc.
Has anyone had this problem before and is there an obvious solution
that's staring me in the face?
Kind regards,
Bruce.
P.S. Source code below, in case it matters:
-- project
library BOSLAD;
uses
ShareMem,
SysUtils,
Classes,
BOSLADCode in 'BOSLADCode.pas';
exports
version,
DMesg,
foo;
{$R *.res}
begin
end.
-- unit
unit BOSLADCode;
interface
function version() : Double; stdcall;
procedure DMesg(sText : WideString; sHead : WideString ); stdcall;
function foo() : PWideString; stdcall;
implementation
uses Windows;
function version() : Double;
var
s : String;
begin
result := 0.001;
end;
procedure DMesg( sText : WideString; sHead : WideString);
begin
Windows.MessageBoxW(0, PWideChar(sText), PWideChar(sHead), 0);
end;
function foo() : PWideString;
var s : WideString;
begin
s := 'My dog''s got fleas';
result := PWideString(s);
end;
end.
-- typelib
// This is the type library for BOSLAD.dll
[
// Use GUIDGEN.EXE to create the UUID that uniquely identifies
// this library on the user's system. NOTE: This must be done!!
uuid(0C55D7DA-0840-40c0-B77C-DC72BE9D109E),
// This helpstring defines how the library will appear in the
// References dialog of VB.
helpstring("BOSLAD TypeLib"),
// Assume standard English locale.
lcid(0x0409),
// Assign a version number to keep track of changes.
version(1.0)
]
library BOSLAD
{
// Now define the module that will "declare" your C functions.
[
helpstring("Functions in BOSLAD.DLL"),
version(1.0),
// Give the name of your DLL here.
dllname("BOSLAD.dll")
]
module BOSLADFunctions
{
[helpstring("version"), entry("version")] void __stdcall version(
[out,retval] double* res );
[helpstring("DMesg"), entry("DMesg")] void __stdcall DMesg( [in] BSTR
msg, [in] BSTR head );
[helpstring("foo"), entry("foo")] void __stdcall foo( [out,retval] BSTR*
msg );
} // End of Module
}; // End of Library
It might have been useful to include some of those lines that you
stepped through. It might also have been useful for you to eliminate
those lines until the problem went away, thus revealing which of the
lines is actually relevant to the problem.
> By contrast, when I compile the test to an EXE the program runs to its
> end, without error messages etc.
But since the program ran to its end without error messages anyway,
you've really learned nothing.
> Has anyone had this problem before and is there an obvious solution
> that's staring me in the face?
>
> Kind regards,
> Bruce.
>
> P.S. Source code below, in case it matters:
>
> -- project
> library BOSLAD;
>
> uses
> ShareMem,
> SysUtils,
> Classes,
> BOSLADCode in 'BOSLADCode.pas';
This DLL does not need ShareMem, SysUtils, or Classes.
> exports
> version,
> DMesg,
> foo;
> {$R *.res}
>
> begin
> end.
>
> -- unit
> unit BOSLADCode;
>
> interface
> function version() : Double; stdcall;
> procedure DMesg(sText : WideString; sHead : WideString ); stdcall;
> function foo() : PWideString; stdcall;
>
> implementation
> uses Windows;
>
> function version() : Double;
> var
> s : String;
> begin
> result := 0.001;
That value cannot be represented exactly as a Double. Are you sure
Double is the best choice?
Also, s is never used.
> end;
>
> procedure DMesg( sText : WideString; sHead : WideString);
> begin
> Windows.MessageBoxW(0, PWideChar(sText), PWideChar(sHead), 0);
> end;
>
> function foo() : PWideString;
> var s : WideString;
> begin
> s := 'My dog''s got fleas';
> result := PWideString(s);
> end;
You've taken a WideString and told the compiler that it's really a
pointer to a WideString. You're lying to the compiler. It doesn't care,
but the caller of this function probably does.
Next, when the variable s goes out of scope (just before the function
returns to its caller), the WideString value it holds is destroyed. From
then on, whatever value the function returns in an invalid pointer, no
matter what type it has.
--
Rob
I stand corrected. And apologies to you, as you sound a little annoyed
with my naivete. And what you have said works a treat. Thank you very
much for your patience. It is very much appreciated.
Kind regards,
Bruce.
---
library BOSLAD;
uses
BOSLADCode in 'BOSLADCode.pas';
exports
version,
DMesg,
foo;
{$R *.res}
begin
end.
---
interface
function version() : Double; stdcall;
procedure DMesg(sText : PWideChar; sHead : PWideChar ); stdcall;
function foo() : PWideChar; stdcall;
implementation
uses Windows;
var s : WideString;
function version() : Double;
begin
result := 0.001;
end;
procedure DMesg( sText : PWideChar; sHead : PWideChar);
begin
Windows.MessageBoxW(0, sText, sHead, 0);
end;
function foo() : PWideChar;
begin
s := 'My dog''s got fleas';
result := PWideChar(s);
end;
end.
---
// This is the type library for BOSLAD.dll
[
uuid(0C55D7DA-0840-40c0-B77C-DC72BE9D109E),
helpstring("BOSLAD TypeLib"),
lcid(0x0409),
version(1.0)
]
library BOSLAD
{
[
helpstring("Functions in BOSLAD.DLL"),
version(1.0),
dllname("BOSLAD.dll")
]
module BOSLADFunctions
{
[helpstring("version"), entry("version")]
void __stdcall version( [out,retval] double* res );
[helpstring("DMesg"), entry("DMesg")]
void __stdcall DMesg( [in] BSTR msg, [in] BSTR head );
[helpstring("foo"), entry("foo")]
void __stdcall foo( [out,retval] BSTR* msg );
}
};
---
Sub Main()
Dim cfg As New CFGProject.cfg
cfg.Load "test.cfg"
Dim s As String
s = cfg.Recall("msg")
DMesg s, "" & version
s = foo
DMesg s, "" & version
End Sub
---
#test.cfg
msg=毅訜訝
Although the type in foo is now accurate, there's still the problem that
s goes out of scope before the function returns, and thus the pointer is
not valid by the time it arrives at the caller.
But since the address that s held is probably still within your
process's address space, and COM probably hasn't re-allocated it to
anything else, it probably still contains the same characters it did a
moment ago when it was still a valid pointer, so the caller will
blithely read it and get what you were expecting, so it appears to work.
But a program that accidentally works most of the time is still broken.
> end.
>
> ---
>
> // This is the type library for BOSLAD.dll
> [
> uuid(0C55D7DA-0840-40c0-B77C-DC72BE9D109E),
> helpstring("BOSLAD TypeLib"),
> lcid(0x0409),
> version(1.0)
> ]
> library BOSLAD
> {
>
> [
> helpstring("Functions in BOSLAD.DLL"),
> version(1.0),
> dllname("BOSLAD.dll")
> ]
> module BOSLADFunctions
> {
> [helpstring("version"), entry("version")]
> void __stdcall version( [out,retval] double* res );
> [helpstring("DMesg"), entry("DMesg")]
> void __stdcall DMesg( [in] BSTR msg, [in] BSTR head );
> [helpstring("foo"), entry("foo")]
> void __stdcall foo( [out,retval] BSTR* msg );
A BSTR is not just a pointer to a wide character. A BSTR (a "binary
string") is a special type that is null-terminated, has a length
indicator, and has its memory managed by special COM API functions.
Delphi's declaration of the BSTR type is wrong, as I recall. It claims
it's just a PWideChar. That's particularly strange since Delphi actually
wraps all the special BSTR semantics into a built-in type, WideString. I
think if you change all your PWideChar types above to WideString,
everything will work just right. Change your DMesg implementation back
to what it was before.
WideStrings are not reference-counted like AnsiStrings are. That means
that every WideString assignment statement makes a new copy of the
string. Thus, your foo implementation would be non-optimal, as written:
var
s: WideString;
begin
s := 'bar'; // Creates a new WideString value
Result := s; // Makes a new copy; there are two now.
end; // s goes out of scope. Now back to one copy of "bar"
Avoid superfluous WideStrings. The compiler won't optimize them away
because the optimizer runs _after_ the built-in WideString code has been
expanded to call the API functions, and the optimizer never removes
external function calls (since it doesn't know what desirable side
effects they might have).
begin
Result := 'bar'; // Creates a new WideString value
end; // Compiler knows not to destroy Result's contents.
I don't know how VB deals with Unicode and non-Unicode strings, but I
assume that if you're giving it the type library above, VB will work out
whatever it needs to do to call the functions properly and receive their
return values.
> }
> };
>
> ---
>
> Sub Main()
> Dim cfg As New CFGProject.cfg
> cfg.Load "test.cfg"
> Dim s As String
> s = cfg.Recall("msg")
> DMesg s, "" & version
> s = foo
> DMesg s, "" & version
> End Sub
>
> ---
> #test.cfg
> msg=毅訜訝
>
--
Rob
>> You've taken a WideString and told the compiler that it's really a
>> pointer to a WideString. You're lying to the compiler. It doesn't
>> care, but the caller of this function probably does.
> I stand corrected. And apologies to you, as you sound a little annoyed
> with my naivete.
I don't think you should read that into it. If he were, you'd know.
'Lying to the compiler' is just a figure of speech - although, compilers
being involved, there is a case for taking things litterally. The
compiler always does. But generally, 'lying to the compiler' is just
a way to say that you did a typecast... and were wrong. Had you been
right, that would have been perfectly fine. Typecasts are a way of
telling the compiler that you know better than it, and that really is
possible. Just remember not to make mistakes.
> And what you have said works a treat.
Of course it does. This is Rob. He's one of those always-right types.
(It comes from only speaking when he knows what he's saying. Oh, and
being very, very good at stuff he speaks about.)
Groetjes,
Maarten Wiltink
> Although the type in foo is now accurate, there's still the problem that
> s goes out of scope before the function returns, and thus the pointer is
> not valid by the time it arrives at the caller.
>
> But since the address that s held is probably still within your
> process's address space, and COM probably hasn't re-allocated it to
> anything else, it probably still contains the same characters it did a
> moment ago when it was still a valid pointer, so the caller will
> blithely read it and get what you were expecting, so it appears to work.
> But a program that accidentally works most of the time is still broken.
Is this getting any closer?
...
uses Windows, ActiveX;
...
procedure DMesg( sText : WideString; sHead : WideString);
begin
Windows.MessageBoxW(0, PWideChar(sText), PWideChar(sHead), 0);
end;
function foo() : PWideChar;
var s : WideString;
begin
s := 'My dog''s got fleas';
result := ActiveX.SysAllocString(PWideChar(s));
end;
Kind regards,
Bruce.
Looks good. One last suggestion on this one is to declare the parameters
as const. That way, the function won't make local copies of the
parameters. Remember that copying a WideString involves allocating an
entire new buffer for the characters. Your function doesn't really need
that, and adding "const" tells the compiler not to add code to make
those copies.
> function foo() : PWideChar;
> var s : WideString;
> begin
> s := 'My dog''s got fleas';
> result := ActiveX.SysAllocString(PWideChar(s));
> end;
I think my suggestion was to change _all_ the PWideChar types to
WideString. That would include the return type of foo. Then the
implementation is very simple:
Result := 'My dog''s got fleas';
--
Rob
function foo() : WideString;
begin
result := 'My cat''s on fire';
end;
So I went back to this, which works nicely. But are we now reverting to
the "works but actually broken" situation?
function foo() : PWideChar;
begin
result := 'My cat''s on fire';
end;
If it matters, I'm using Turbo Delphi Explorer.
Kind regards,
Bruce.
how about this.
Dmesg(Var sText:WideString,sHead:WideString);
etc///