Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

DLL Problem: PChar as result returning unassigned

Status
Not open for further replies.

djjd47130

Programmer
Nov 1, 2010
480
US
I have a function in a DLL which returns PChar as a result. I debugged the DLL and verified it in fact does assign the Result. I even tested hard-coding the result. However, when the DLL function is returned back to the app, the Result is unassigned. Other functions made the exact same only returning Bool work perfect, just returning PChar has a problem...

DLL Code:
Code:
function svc_GetServiceName(Filename: PChar): PChar; StdCall;
var
  Reg: TRegistry;
  L: TStringList;
  Key, N, NKey, Res: String;
  X: Integer;
  Found: Bool;
begin
  Found:= False;
  Result:= PChar('');
  Reg:= TRegistry.Create(KEY_READ or KEY_WRITE);
  L:= TStringList.Create;
  try
    Reg.RootKey:= HKEY_LOCAL_MACHINE;
    Key:= 'System\CurrentControlSet\Services\';
    Reg.OpenKey(Key, False);
      Reg.GetKeyNames(L);
    Reg.CloseKey;
    for X:= 0 to L.Count - 1 do begin
      N:= L[X];
      NKey:= Key + N;
      if Reg.OpenKey(NKey, False) then begin
        if Reg.ValueExists('ImagePath') then begin
          if UpperCase(Filename) = UpperCase(Reg.ReadString('ImagePath')) then begin
            //ShowMessage('Found!');
            Res:= N;
            Found:= True;
          end;
        end;
        Reg.CloseKey;
      end;
      if Found then Break;
    end;
  finally
    Reg.Free;
    L.Free;  
    Result:= PChar(Res);
  end;
end;

exports
  svc_GetServiceName;

App Code:

Code:
  var
    SvcName: PChar;

  function svc_GetServiceName(Filename: PChar): PChar; StdCall;
    External 'WinSvc32Lib.dll';

  /////////////////////////////////////////////////////////////////////////////

  SvcName:= PChar(svc_GetServiceName(PChar(dlgOpen.FileName)));

In the end, when I try to pass the result PChar to a String, I get an access violation.



JD Solutions
 
PChar is a pointer to a character.

That said, your DLL function as you wrote it knows nothing of what it can legitimately pass data to.

Code:
function svc_GetServiceName(Filename: PChar): PChar; StdCall;
var
  ...
  Res: String;
begin
 ...
  Result:= PChar(Res);
end;

To look at your code, you are passing a pointer address to a local variable of the DLL where you have your data allocated. Now this local variable (well any variable of a DLL) is unstable and will not be available. This makes it invalid and will cause your AV.

We can take a few lessons from how Microsoft laid out their API functions and this is one of them. Most of the functions that involve strings or record data have pointers to where to put the data and then a buffer length. You will have to supply a pointer to your function that indicates where the data is to be put (at minimum).

A simple example. Defining the library function as a function is pretty silly since the result directly stems from the input value, but it illustrates how to handle PChars between DLL and main program.

DLL
Code:
library testdll; uses sysutils;

function AddA(inp: PChar): PChar; stdcall;
// adds character "A" to end of PChar passed.
begin
  // result returned is directly related to input and as main
  // is called, result goes back to the input pointer
  Result := PChar(String(inp) + 'A');
end;

exports
  AddA;

begin
end.

Main
Code:
$APPTYPE CONSOLE}
program testmain; uses sysutils;

function AddA(inp: PChar): PChar; stdcall; external 'testdll.dll';

var
  Teststring: string;
begin
  teststring := 'This is a test string';
  writeln('Test string before: ', teststring);
  teststring := AddA(PChar(teststring));
  writeln('Test string after: ', teststring);
  readln;
end.

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Unfortunately, didn't help :(

I tried your example separately, and it worked.

Should I try something more like this?

Code:
  function svc_GetServiceName(Filename: PChar; var ServiceName: PChar): bool;

Or would that give the same issues?


JD Solutions
 
Actually I did try and it did give the same issues.

JD Solutions
 
On the other hand, you did help me figure out how to make a console application :p

JD Solutions
 
Actually the first example is a little bad for general use, since the DLL code is making some assumptions that *Might* not hold up if other languages call it. What I'm saying more or less is that DLLs are discrete, which means you can not use any other variables outside of the variables in the function parms to communicate with the calling application. The code you posted breaks this rule when it comes to the string I pointed out.

Here's another example that is a little more involved. While the method in the first post for handling PChar might work, this one is probably the more compatible way.

DLL
Code:
library testdll; uses sysutils, registry, windows;

function AddA(inp: PChar): PChar; stdcall;
// adds character "A" to end of PChar passed.
begin
  Result := PChar(String(inp) + 'A');
end;

function GetWallPaperPath(wallpaper: PChar; var buflen: Word): Boolean; stdcall;
var
  regobj: TRegistry;
  wpresult: string;
begin
  Result := false;
  regobj := TRegistry.Create;
  try
    regobj.rootkey := HKEY_CURRENT_USER;
    if regobj.OpenKey('Control Panel\Desktop', false) then
      begin
        wpresult := regobj.ReadString('Wallpaper');
        // I can't use wpresult as a direct basis for data return.
        // So I have to copy it if I go that way (Method 1.
        StrPCopy(wallpaper, wpresult);
        buflen := Length(wpresult);
        Result := true;
      end
  finally
    regobj.CloseKey;
    regobj.free;
  end;
end;

exports
  AddA,
  GetWallPaperPath;

begin
end.

MAIN
{$APPTYPE CONSOLE}
program testmain; uses sysutils;

function AddA(inp: PChar): PChar; stdcall; external 'testdll.dll';
function GetWallPaperPath(wallpaper: PChar; var buflen: Word): Boolean;
stdcall; external 'testdll.dll';

var
Teststring: string;
teststringlen: Word;
begin
teststring := 'This is a test string';
writeln('Test string before: ', teststring);
teststring := AddA(PChar(teststring));
writeln('Test string after: ', teststring);

{ this is how I usually call Windows API functions that involve strings. You have to have a big enough buffer to receive whatever data the DLL function wants to provide or it will truncate - Delphi seems very good about buffer overruns. You can do a check for whether you require more buffer space than
what is passed, too. }
teststringlen := 255;
SetLength(teststring, teststringlen);
GetWallPaperPath(PChar(teststring), teststringlen);
SetLength(teststring, teststringlen);
writeln('WallPaper path for current user is: ', teststring, '*'); // writing the "*" to show where the end of the string is
readln;
end.
[/code]

Hope that clears up things a little more?

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Actually, can you make the appropriate changes to my code below please? That's the best way for me to see what you mean.

DLL Function:
Code:
function svc_GetServiceName(Filename: PChar; var ServiceName: PChar): PChar; StdCall;
var
  Reg: TRegistry;
  L: TStringList;
  Key, N: String;
  X: Integer;
  Found: Bool;
begin
  Found:= False;
  Result:= PChar('');    //Assigned blank value first
  Reg:= TRegistry.Create(KEY_READ or KEY_WRITE);
  L:= TStringList.Create;
  try
    Reg.RootKey:= HKEY_LOCAL_MACHINE;
    Key:= 'System\CurrentControlSet\Services\';
    Reg.OpenKey(Key, False);
      Reg.GetKeyNames(L);
    Reg.CloseKey;
    for X:= 0 to L.Count - 1 do begin
      N:= L[X];
      if Reg.OpenKey(Key+N, False) then begin
        if Reg.ValueExists('ImagePath') then begin
          if UpperCase(Filename) = UpperCase(Reg.ReadString('ImagePath')) then begin
            Found:= True;
            Result:= PChar(String(N));    //This is where it's assigned
          end;
        end;
        Reg.CloseKey;
      end;
      if Found then Break;
    end;
  finally
    Reg.Free;
    L.Free;
  end;
end;

EXE Usage:
Code:
  function svc_GetServiceName(Filename: PChar): PChar;
    StdCall; External 'WinSvc32Lib.dll';
  function GetServiceName(Filename: String): String;

implementation

function GetServiceName(Filename: String): String;
begin
  Result:= svc_GetServiceName(PChar(Filename));
end;


JD Solutions
 
I didn't get your post before I posted this last one...

JD Solutions
 
PS - Speaking of cross-language usage, you need to use bool instead of boolean.

Code:
  function GetWallPaperPath(wallpaper: PChar; var buflen: Word): Bool; stdcall;


JD Solutions
 
That sample sparked my curiosity about something else - Obviously I can set the wallpaper path, but how can I force Windows to refresh the wallpaper? There has to be a message I could send...


JD Solutions
 
Obviously I can set the wallpaper path, but how can I force Windows to refresh the wallpaper?

Code:
 if SystemParametersInfo(SPI_SETDESKWALLPAPER,
                           0,
                           PChar(instr),
                           SPIF_SENDWININICHANGE) then

Where instr is the full path to the BMP you want.

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
I posted the second example (cleaned up some) as a FAQ.

faq102-7472

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top