Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

C++ const char* -> Delphi's String - showing only part of the string

694 views
Skip to first unread message

Dan

unread,
Jan 4, 2008, 6:38:39 PM1/4/08
to
I have a program which us a DLL, I want this dll to be able to report back
errors to Delphi. When an error is found it saves it to a variable and then
a flat C++ function returns the value of that variable to Delphi when
GetError was called.

Now that I have the variable in Delphi I try to display it and I get only
the last part of the string displaying correctly what the C++ string is:
"Could not find taxon named Gorilla among stored taxon labels"

What Delphi gives:
"p O p O t find taxon named Gorilla among stored taxon labels"

Any ideas as to what my problem is? What is it about "Could not find " that
causes it to mess up like this?

- Dan

Rudy Velthuis [TeamB]

unread,
Jan 4, 2008, 7:12:19 PM1/4/08
to
Dan wrote:

> I have a program which us a DLL, I want this dll to be able to report
> back errors to Delphi. When an error is found it saves it to a
> variable and then a flat C++ function returns the value of that
> variable to Delphi when GetError was called.

Returns? What exactly do you mean? Does the function return a PChar
(char *) as function result to a string in your memory, or does it fill
a buffer provided by the caller?

I'd like to see the C++ prototpye of the DLL function and how it
actually returns text to the caller. I guess you are passing a pointer
to internal data which is then overwritten after the call. But I'd have
to see the code first.

--
Rudy Velthuis [TeamB] http://www.teamb.com

"All things are possible, except skiing through a revolving door."

Dan

unread,
Jan 4, 2008, 7:24:35 PM1/4/08
to
"Rudy Velthuis [TeamB]" <newsg...@rvelthuis.de> wrote in message
news:xn0fktabt...@newsgroups.borland.com...

Ok, I am calling let's say the function GetErrorMsg form Delphi, this in the
dll returns a const char*. In Delphi this looks like a PChar, so I simply
assign it to a string and it turns into a regular string, except that I'm
still getting this error.

- Dan

Here's the code on the DLL's (C++) side:

//---------------------------------------------------------------------------

#include <windows.h>
//---------------------------------------------------------------------------
// Important note about DLL memory management when your DLL uses the
// static version of the RunTime Library:
//
// If your DLL exports any functions that pass String objects (or structs/
// classes containing nested Strings) as parameter or function results,
// you will need to add the library MEMMGR.LIB to both the DLL project and
// any other projects that use the DLL. You will also need to use
MEMMGR.LIB
// if any other projects which use the DLL will be performing new or
delete
// operations on any non-TObject-derived classes which are exported from
the
// DLL. Adding MEMMGR.LIB to your project will change the DLL and its
calling
// EXE's to use the BORLNDMM.DLL as their memory manager. In these cases,
// the file BORLNDMM.DLL should be deployed along with your DLL.
//
// To avoid using BORLNDMM.DLL, pass string information using "char *" or
// ShortString parameters.
//
// If your DLL uses the dynamic version of the RTL, you do not need to
// explicitly add MEMMGR.LIB as this will be done implicitly for you
//---------------------------------------------------------------------------

#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*
lpReserved)
{
return 1;
}
//---------------------------------------------------------------------------
/*
This is the core of the dll, all the functions that delphi accesses are
defined here
*/
#include "TNeXus.h"
#include "string.h"

#ifdef __cplusplus
extern "C" {
#endif

__declspec(dllexport) bool NeXus_Exist( void );
__declspec(dllexport) void NeXus_Create( const char* );
__declspec(dllexport) void NeXus_Destroy( void );
__declspec(dllexport) void NeXus_Parse( void );
__declspec(dllexport) const char* NeXus_GetTaxonName( int num );
__declspec(dllexport) const char* NeXus_GetErrorMsg( void );
__declspec(dllexport) int NeXus_GetErrorLine( void );

#ifdef __cplusplus
}
#endif

bool NeXus_Exist( void )
{
return (globalInterfaceInstance);
}

void NeXus_Create( const char* NeXusFilePath)
{
if (globalInterfaceInstance) delete globalInterfaceInstance;
globalInterfaceInstance = new _TNeXus (NeXusFilePath);
}

void NeXus_Destroy( void ) // Destorys the globalInterface
{
if (!globalInterfaceInstance) return;
delete globalInterfaceInstance;
globalInterfaceInstance = nil;
}

void NeXus_Parse( void )
{
globalInterfaceInstance->ParseFile();

}

const char* NeXus_GetTaxonName(int num)
{
return globalInterfaceInstance->GetTaxonName(num);

}

const char* NeXus_GetErrorMsg( void )
{
return globalInterfaceInstance->ErrorMsg;

}

int NeXus_GetErrorLine( void )
{
return globalInterfaceInstance->ErrorLine;

}


Then on the Delphi side I'm using this code:

unit MNeXus;

// IMPORTANT NOTICE:
// This is a MNeXus.dll wrapper for the use of NeXus from Delphi codes.
// Since only one instance of NeXus is allowed in the MNeXus.dll,
// do not create any TNeXus object instancejust but just call the
// CreateNeXusGlobalInstance function.
//
// MNeXus.dll must be present in the same directry with the execute file,
// which is Delphi6/Bin in the design time.

interface

uses
Windows, Messages, Graphics, SysUtils, Classes, Controls, Forms, StrUtils;

type

TNeXus = class
private
function GetActivated: boolean;
public
property Activated: boolean read GetActivated;
procedure Activate(ADirPath: string);
procedure Clear;
destructor Destroy; override;
procedure Parse;
function GetTaxonName(num: Integer): String;
function GetErrorMsg(): String;
function GetErrorLine(): Integer;
end;


function CreateNeXusGlobalInstance: TNeXus; forward;
procedure DestroyNeXusGlobalInstance;


implementation

procedure _NeXus_Create(baseDirPath: PChar); cdecl; external 'MNeXus.dll';
procedure _NeXus_Destroy; cdecl; external 'MNeXus.dll';

function _NeXus_Exist: boolean; cdecl; external 'MNeXus.dll';
procedure _NeXus_Parse; cdecl; external 'MNeXus.dll';
function _NeXus_GetTaxonName(num: Integer): PChar; cdecl; external
'MNeXus.dll';
function _NeXus_GetErrorMsg(): PChar; cdecl; external 'MNeXus.dll';
function _NeXus_GetErrorLine(): Integer; cdecl; external 'MNeXus.dll';

var
NeXus: TNeXus = nil;

function CreateNeXusGlobalInstance: TNeXus;
begin
result := NeXus;
if assigned(NeXus) then exit;

NeXus := TNeXus.Create;

result := NeXus;
end;

procedure DestroyNeXusGlobalInstance;
begin
if not assigned(NeXus) then exit;
NeXus.Free;
NeXus := nil;
end;

//==========================
// TNeXus
//==========================

destructor TNeXus.Destroy;
begin
Self.Clear;
inherited;
end;

procedure TNeXus.Activate(ADirPath: string);
var
baseDirPath: PChar;
begin
GetMem(baseDirPath, length(ADirPath)+1); // Changed form FDirPath to
ADirPath
StrPCopy(baseDirPath, ADirPath); // Changed from FDirPath to ADirPath
_NeXus_Create(baseDirPath);
end;

function TNeXus.GetActivated: boolean;
begin
result := _NeXus_Exist;
end;

procedure TNeXus.Clear;
begin
if not _NeXus_Exist then exit;
_NeXus_Destroy;
end;

procedure TNeXus.Parse;
begin
_NeXus_Parse;
end;

function TNeXus.GetTaxonName(num: Integer): String;
begin
result := _NeXus_GetTaxonName(num);
end;

function TNeXus.GetErrorMsg(): String;
begin
result := _NeXus_GetErrorMsg;
end;

function TNeXus.GetErrorLine(): Integer;
begin
result := _NeXus_GetErrorLine;
end;

end.

Rudy Velthuis [TeamB]

unread,
Jan 4, 2008, 7:41:22 PM1/4/08
to
Dan wrote:

> const char* NeXus_GetErrorMsg( void )
> {
> return globalInterfaceInstance->ErrorMsg;
>
> }


As I expected. You return a pointer to an internal string. Don't do it
like that, since the string may (partly) be overwritten by something
else. I guess that is the junk you are seeing.

Do this instead:

void NeXus_GetErrorMsg(char * const buffer, int len)
{
strncpy(buffer, globalInterfaceInstance->ErrorMsg, len);
}

Now, you only copy the text of the string to a buffer provided by the
user. This means the user (who may have a totally different memeory
manager) doesn't have to take care of a pointer handed out by the DLL.

FWIW, use the same principle for the other functions as well.

--
Rudy Velthuis [TeamB] http://www.teamb.com

"I have come to believe that the whole world is an enigma, a
harmless enigma that is made terrible by our own mad attempt to
interpret it as though it had an underlying truth."
-- Umberto Eco

Rudy Velthuis [TeamB]

unread,
Jan 4, 2008, 7:40:29 PM1/4/08
to
Dan wrote:

> bool NeXus_Exist( void )
> {
> return (globalInterfaceInstance);
> }

Ouch. I'm sure it works, but that's unnecessary terseness, and not very
readable, IMO. I'd write:

return (globalInterfaceInstance != NULL);


> void NeXus_Destroy( void ) // Destorys the globalInterface
> {
> if (!globalInterfaceInstance) return;
> delete globalInterfaceInstance;
> globalInterfaceInstance = nil;
> }

You defined nil?

> const char* NeXus_GetTaxonName(int num)
> {
> return globalInterfaceInstance->GetTaxonName(num);
>
> }
>
> const char* NeXus_GetErrorMsg( void )
> {
> return globalInterfaceInstance->ErrorMsg;
>
> }

I would redo these two.

--
Rudy Velthuis [TeamB] http://www.teamb.com

"What I am against is quotas. I am against hard quotas, quotas
they basically delineate based upon whatever. However they
delineate, quotas, I think, vulcanize society. So I don't know
how that fits into what everybody else is saying, their relative
positions, but that's my position." -- George W. Bush

Dan

unread,
Jan 7, 2008, 11:36:26 AM1/7/08
to
"Rudy Velthuis [TeamB]" <newsg...@rvelthuis.de> wrote in message
news:xn0fktb0u...@newsgroups.borland.com...

> Dan wrote:
>
>> bool NeXus_Exist( void )
>> {
>> return (globalInterfaceInstance);
>> }
>
> Ouch. I'm sure it works, but that's unnecessary terseness, and not very
> readable, IMO. I'd write:
>
> return (globalInterfaceInstance != NULL);
>
>
>> void NeXus_Destroy( void ) // Destorys the globalInterface
>> {
>> if (!globalInterfaceInstance) return;
>> delete globalInterfaceInstance;
>> globalInterfaceInstance = nil;
>> }
>
> You defined nil?
>
>> const char* NeXus_GetTaxonName(int num)
>> {
>> return globalInterfaceInstance->GetTaxonName(num);
>>
>> }
>>
>> const char* NeXus_GetErrorMsg( void )
>> {
>> return globalInterfaceInstance->ErrorMsg;
>>
>> }
>
> I would redo these two.
>
> --
> Rudy Velthuis [TeamB] http://www.teamb.com
>

I didn't define nil, the guy who I'm working with did. What do you mean you
would redo the taxon and error msg lines? All they are doing is calling
functions of a class of which globalInterfaceInsance is an instance of. I'm
simply writing functions to flatten classes.

- Dan

Dan

unread,
Jan 7, 2008, 11:45:45 AM1/7/08
to
"Rudy Velthuis [TeamB]" <newsg...@rvelthuis.de> wrote in message
news:xn0fktb1n...@newsgroups.borland.com...

> Dan wrote:
>
>> const char* NeXus_GetErrorMsg( void )
>> {
>> return globalInterfaceInstance->ErrorMsg;
>>
>> }
>
>
> As I expected. You return a pointer to an internal string. Don't do it
> like that, since the string may (partly) be overwritten by something
> else. I guess that is the junk you are seeing.
>
> Do this instead:
>
> void NeXus_GetErrorMsg(char * const buffer, int len)
> {
> strncpy(buffer, globalInterfaceInstance->ErrorMsg, len);
> }
>
> Now, you only copy the text of the string to a buffer provided by the
> user. This means the user (who may have a totally different memeory
> manager) doesn't have to take care of a pointer handed out by the DLL.
>
> FWIW, use the same principle for the other functions as well.
>
> --
> Rudy Velthuis [TeamB] http://www.teamb.com
>

I remember that now. I was going to use that for these functions, except I
didn't know how long the string was going to be, so there is really no way I
can allocate the right buffer size in Delphi.

Or are you suggesting that I just allocate a buffer that's really long (as
long as I would ever expect any returned string to be) and pass that length?
Once I allocate a large buffer and get a response, how do I take the buffer
and turn it into a string? Do I just treat it as if it's a PChar and set a
string := buffer? I'm not too experienced with dynamic allocation of memory
in Delphi.

Thank you
- Dan

Rudy Velthuis [TeamB]

unread,
Jan 7, 2008, 1:15:04 PM1/7/08
to
Dan wrote:

> > You defined nil?
> >
> > > const char* NeXus_GetTaxonName(int num)
> > > {
> > > return globalInterfaceInstance->GetTaxonName(num);
> > >
> > > }
> > >
> > > const char* NeXus_GetErrorMsg( void )
> > > {
> > > return globalInterfaceInstance->ErrorMsg;
> > >
> > > }
> >
> > I would redo these two.
> >
> > -- Rudy Velthuis [TeamB] http://www.teamb.com
> >
>
> I didn't define nil, the guy who I'm working with did.

Tsk, tsk, tsk. Did he also define the following? <g>

#define begin {
#define end }

> What do you
> mean you would redo the taxon and error msg lines?

I would not redo the messages, I would not code the FUNCTIONS like
that, returning a PChar/char *, IOW, I would redo them. Returning a
PChar means you return a pointer to your DLL's internal memory. Not
only is that a little bit insecure, but who will get rid of it? The
user can't, actually, since he or she has no access to your memory
manager. And if the user could, how would you ever notice, except when
you got an AV accessing it for a second time?

The best thing is to let the user provide a buffer you fill with data,
i.e. you simply copy your internal message into the buffer provided by
the user. This avoids all kinds of memory problems.


--
Rudy Velthuis [TeamB] http://www.teamb.com

"USA Today has come out with a new survey: Apparently three out
of four people make up 75 percent of the population."
-- David Letterman.

Rudy Velthuis [TeamB]

unread,
Jan 7, 2008, 1:27:02 PM1/7/08
to
Dan wrote:

> "Rudy Velthuis [TeamB]" <newsg...@rvelthuis.de> wrote in message
> news:xn0fktb1n...@newsgroups.borland.com...
> > Dan wrote:
> >
> > > const char* NeXus_GetErrorMsg( void )
> > > {
> > > return globalInterfaceInstance->ErrorMsg;
> > >
> > > }
> >
> >
> > As I expected. You return a pointer to an internal string. Don't do
> > it like that, since the string may (partly) be overwritten by
> > something else. I guess that is the junk you are seeing.
> >
> > Do this instead:
> >
> > void NeXus_GetErrorMsg(char * const buffer, int len)
> > {
> > strncpy(buffer, globalInterfaceInstance->ErrorMsg, len);
> > }
> >
> > Now, you only copy the text of the string to a buffer provided by
> > the user. This means the user (who may have a totally different
> > memeory manager) doesn't have to take care of a pointer handed out
> > by the DLL.
> >
> > FWIW, use the same principle for the other functions as well.
> >
> > -- Rudy Velthuis [TeamB] http://www.teamb.com
> >
>
> I remember that now. I was going to use that for these functions,
> except I didn't know how long the string was going to be, so there is
> really no way I can allocate the right buffer size in Delphi.

Why not? You can specify a maximum length of, say 2048 characters. Or
you let the user specify the size of the buffer. If the buffer is too
small, you return either what you can cram into the buffer, or you
return the size you need. This requires that your function has a return
value of some integral type. You simply always return the size of the
error message. If the buffer was too small, the user can retry with
that size.

>
> Or are you suggesting that I just allocate a buffer that's really
> long (as long as I would ever expect any returned string to be) and
> pass that length?

OK, last version:

int Nexus_GetErrorMsg(char * const buffer, int len)
{
int required_len = strlen(globalInterfaceInstance->ErrorMsg);
if (len >= required_len)
strcpy(buffer, globalInterfaceInstance->ErrorMsg);
return required_len;
}

Now, you can do this:

Len := Nexus_GetErrorMsg(nil, 0); // get required size
SetLength(Result, Len); // allocate required size
Nexus_GetErrorMsg(PChar(Result), Length(Result));

Of course, if you are sure your strings will never be > 2048
characters, you can also do:

var
buf: array[0..2048] of Char;
begin
Nexus_GetErrorMsg(buf, High(buf));
Result := buf;


--
Rudy Velthuis [TeamB] http://www.teamb.com

"I'd stop eating chocolate, but I'm no quitter." -- Unknown

Dan

unread,
Jan 7, 2008, 2:26:04 PM1/7/08
to
"Dan" <froze...@gmail.com> wrote in message
news:4782...@newsgroups.borland.com...

Well, I just did the test and here's the output: "p 1 p 1 t find taxon named
Gorilla among stored taxon labels" My problem is somewhere in the DLL with
handing the error string arround. Anyway, I figured that I shouldn't really
be calling GetErrorMsg, I should just pass in a buffer when I'm doing the
parsing in the Parse function. Is 50000000 too large or too little space to
be allocating for a few sentences. I'm not really sure what exactly I'm
allocating either, bits, bytes, etc?

function TNeXus.Parse(): String;
var
Buffer: PChar;
len: Integer;
ErrorStr: String;
begin
len := 50000000;
GetMem(Buffer, len);
try
_NeXus_Parse(Buffer,len);
ErrorStr := PChar(Buffer);
result := ErrorStr;
finally
FreeMem(Buffer);
end;
end;


0 new messages