views:

1190

answers:

6

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?

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
+2  A: 
result := PWideString(s);

You are returning pointer to a local variable here. It immediately becomes invalid.

GSerg
A: 

I moved the declaration of the WideString outside of the function in which I had declared it, in the expectation that that would increase the lifetime of the variable to longer than just the lifetime of the foo function. It made no difference whatsoever.

Likewise I commented out of the VB6 the call to the foo function. That made no difference either. No matter what I do, VB6 IDE dies after the last line of code is executed.

Something apart from pointers to local variables is the cause. But what?

boost
From the symptom (IDE disappearing), it sounds like a stack overflow!
Hugh Allen
+1  A: 

To elaborate on GSerg's answer:

result := PWideString(s);

you'd think it would be ok because s was initialized with a string literal... but wide strings in Delphi are not reference counted like normal strings, so s actually holds a bit of dynamically allocated heap memory, and as soon as the function returns this memory can be reused :(

The following should be ok though:

function foo() : PWideString;
const s : WideString = 'My dog''s got fleas';
begin
  result := PWideString(s);
end;
Hugh Allen
I'm ticking this one because something needs to be ticked, and I can't tick my own solutions. Sometimes SO so sucks.
boost
+1  A: 

Creating DLLs on the delphi.wikia.com site has the answer I've been looking for. And the solution too.

For example, Delphi automatically allocates and frees the memory for storing your strings, it knows when they are no longer needed etc. The same applies for e.g. Visual Basic, but both do it in different ways. So, if you were to pass a string that was allocated by Visual Basic to a DLL written in Delphi you would end up in big trouble, because both would now try to manage the string and would get into each other's hair.

The solution is to use FastMM and it works brilliantly!! I now have a replacement BORLNDMM.DLL in with my project and everything just works.

boost
That goes for strings, not for WideStrings. Latter are allocated via COM - just because of the interoperability.
gabr
And FastMM, just as ShareMem, only works if everyone uses that memory manager. VB certranly does not.
Lars Truijens
A: 

I just got thoroughly straightened out on this one, thanks to Rob Kennedy on news:comp.lang.pascal.delphi.misc

He said, amongst other things that:

  1. This DLL does not need ShareMem, SysUtils, or Classes.
  2. 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.

So the revised code, which works fine without ShareMem (and SysUtils and Classes which were added by the DLL Wizard as it happens) is as follows:

library BOSLAD;
uses
  BOSLADCode in 'BOSLADCode.pas';
exports
  version,
  DMesg,
  foo;
{$R *.res}
begin
end.

BOSLADCode.pas:

unit BOSLADCode;

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.

boslad.odl:

// 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 );
} 
};

test.bas:

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=毅訜訝

All of that works perfectly. VB6's IDE happily runs the DLL and the MsgBoxs appear with everything as it should be.

boost
A: 

I think we can close this one. The code below seems to be enough to keep the folk on news:comp.lang.pascal.delphi.misc happy and I really need to move on from concept testing to actually doing something with it.

BOSLAD.bdsproj:

library BOSLAD;

uses
  BOSLADCode in 'BOSLADCode.pas';

exports
  version,
  DMesg,
  foo;
{$R *.res}

begin
end.

BOSLADCode.pas:

unit BOSLADCode;

interface
  function version() : Double; stdcall;
  procedure DMesg(const sText : WideString; const sHead : WideString ); stdcall;
  function foo() : PWideChar; stdcall;

implementation
  uses Windows, ActiveX;


  function version() : Double;
  begin
    result := 0.001;
  end;

  procedure DMesg( const sText : WideString; const 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 := SysAllocString(PWideChar(s));
  end;
end.

Now VB's happy and I don't get weird IDE crashes.

boost