Returning a string from a BPL function - delphi

have a function, simplified below, that is exported from a BPL
function DoA(amount: currency; var Info: string): Currency; stdcall;
begin
result := amount * 19;
Info:= 'Some Text about the result';
end;
its loaded from the main program with LoadPackage, and GetProcAddress which works fine for the other functions.
but this one brings up many errors when its called;
BPL Is used with (simplified)
bplhandle: HModule;
BPLDoA: function (amount: currency; var Info: string): Currency; stdcall;
intoStr : string;
.
begin
bplhandle:=LoadPackage('test.bpl');
if bplhandle <> 0 then
begin
#BPLDoA:=GetProcAddress(bplhandle,'DoA');
if assigned(BPLDoA) then
result := BPLDoA(123, intoStr);
end;
end;
the exception that seems to happen at the end of the Procedure,
but the corrected text is returned into intoStr (viewed with a break point)
would the error have anything to do with the Info param being a var and/or a string?
The Error message is
Project Project1.exe raised exception class EInvalidPointer with message 'Invalid pointer operation'
thanks
more info>
another function from the same bpl/unit works fine
function DoB(amount: currency): Currency; stdcall;
result := amount * 19;
end;
Mad Except>
exception class : EInvalidPointer
exception message : Invalid pointer operation.
main thread ($1b7c):
0040276f +013 Project1.exe System #FreeMem
00404650 +01c Project1.exe System #LStrClr
00483814 +15c Project1.exe Unit1 97 +11 TForm1.Button3Click
00462430 +064 Project1.exe Controls TControl.Click
0045a870 +01c Project1.exe StdCtrls TButton.Click

You haven't configured your EXE project to "build with run-time packages." Find that in the "packages" section of your project options. (Documentation)
The EInvalidPointer exception comes when a memory manager tries to free something that it didn't allocate. That suggests you have two different memory managers active. Your BPL is using the one from the RTL package, which appears on your package's "requires" list. Your EXE, on the other hand, is using the memory manager compiled into the EXE module.
Fix that by telling your EXE to use run-time packages, and then make sure the RTL package is on the list of required packages.

Does your import declaration exactly match the exported function's signature?
Must be like this:
DoAProc: function (amount: currency; var Info: string): Currency; stdcall;

Another option if you don't want to be required to ship additional BPLs (which you will now that your main exe is using runtime BPLs), is to include the ShareMem unit in your project. Check out the "Sharing Memory" topic in the Delphi help file.
ms-help://embarcadero.rs2010/rad/Sharing_Memory.html

Related

Annoying assembly codes in Delphi XE4 after method calls

What option should I check out to disable the assembly code tracing after every method call in Delphi XE4?
If you trace (F7) the call of TMyClass.foo, the set of the result cause an assembly tracing.
type
TMyClass = class
public
function foo : string;
end;
function TMyClass.foo : string;
begin
result := 'x';
end;
It sounds like you should set the checkbox "use debug DCUs" on the RHS of the Debugging section of Delphi's Project Options pop-up, under Delphi Compiler | Compiling.

Querying Active Directory (AD) without linking in "ActiveDs_TLB.pas"

I want to query Active Directory in an app developed with Delphi (7 and up), but do not want to include "ActiveDs_TLB" in the "uses" clause to keep the EXE size down. When querying WMI it is possible to use the IBindCtx and IMoniker interfaces to avoid linking in the type library (see How do I use WMI with Delphi without drastically increasing the application's file size? for a solution).
Is it possible to do the same when performing AD queries? I my case I want to retrieve "IADsUser" and "IADsComputer". I am aware that I can decrease the EXE size by manually copying only the required definitions from "ActiveDs_TLB" into my program or to use an LDAP query, but I would prefer a solution similar to the one described for WMI.
I'm no Active Directory expert, but I just created two D7 console applications, one accessing the WnNTSystemInfo object using the ActiveDS_TLB.Pas type library and the other using late binding do do the same thing, namely get the ComputerName from AD.
First, the late binding one:
program ActiveDSLBConsole;
{$APPTYPE CONSOLE}
uses
SysUtils, ActiveX, ComObj;
var
SI : OleVariant;
S : String;
begin
CoInitialize(Nil);
SI := CreateOleObject('WinNTSystemInfo');
S := SI.ComputerName;
writeln(S);
readln;
end.
(what took me longest writing the above was checking the registry for the name of
the object to create)
Anyway, I hope that shows that, yes, you can query AD via late binding and that this minimal example will get you started querying AD that way.
The equivalent AD console application using ActiveDS_Tlb is
program ActiveDSConsole;
{$APPTYPE CONSOLE}
uses
SysUtils, ActiveX, ActiveDS_Tlb;
var
SI : IADsWinNTSystemInfo;
S : String;
begin
CoInitialize(Nil);
SI := CoWinNTSystemInfo.Create;
S := SI.ComputerName;
writeln(S);
readln;
end.
These have .Exe sizes of
ActiveDSConsole : 390144 bytes
ActiveDSLBConsole : 87552 bytes (late bound)
So there's evidently quite a bit of code pulled in to support the use
of the tlb objects, but neither is huge.
FWIW, the above re-written as Button1Click handlers of a minimalist VCL app gives Exe sizes
of
using ActiveDS_TLB : 396288 bytes
late bound : 392704 bytes
the difference between these two seems fairly marginal to me, but there's a clear
size advantage to late binding in a minimal D7 console application. Your mileage may vary,
so probably best to "suck it and see", if you'll pardon the mixed metaphors.
Btw, late binding has the advantage that you don't always have to supply arguments for each of the parameters in an interface method. And you can call a method with this special syntax that the compiler was enhanced to allow (when automation support was added, in D2) for variants that it knows contain late-bound automation objects:
(from an MS Word late binding example)
Table := MSWord.ActiveDocument.Tables.Add(Range:= MSWord.Selection.Range, NumRows:= Rows, NumColumns:= Columns, DefaultTableBehavior:= wdWord9TableBehavior, AutoFitBehavior:= wdAutoFitFixed);
Martyn's answer filled in the missing pieces. Here's an example on how to query IADsUser using late binding:
program GetUserObjectPath;
{$APPTYPE CONSOLE}
uses SysUtils, ActiveX, ComObj;
function GetObject (const Name: WideString) : IDispatch;
var
Moniker : IMoniker;
Eaten : Integer;
BindContext : IBindCtx;
begin
OleCheck (CreateBindCtx (0, BindContext));
OleCheck (MkParseDisplayName (BindContext, PWideChar (Name), Eaten,
Moniker));
OleCheck (Moniker.BindToObject (BindContext, NIL, IDispatch, Result));
end; { GetObject }
procedure Query_AD (const sQuery: String);
var
vUser : OleVariant;
begin
vUser := GetObject (sQuery); // = IADsUser
WriteLn ('Name = ' + vUser.FullName);
end; { Query_AD }
var
sQuery, sDomain, sUserName : String;
begin
sDomain := GetEnvironmentVariable ('USERDNSDOMAIN');
sUserName := GetEnvironmentVariable ('USERNAME');
sQuery := Format ('WinNT://%s/%s,user', [sDomain, sUserName]);
CoInitialize (NIL);
try
Query_AD (sQuery);
finally
// Causes Access Violation if AD query does not happen in subroutine
CoUninitialize;
end; { try / finally }
WriteLn;
Write ('Press [Enter] to continue ...');
ReadLn;
end.
The actual AD query should happen in a subroutine (here "Query_AD"), otherwise calling "CoUninitialize" is going to lead to an access violation (see Why does CoUninitialize cause an error on exit? for an explanation).

Point to a DLL using a dynamic path

My problem is not exporting a function, but importing it. I know for sure that both the function and DLL both work because I have used a hard-coded path to point to the DLL.
This is what is currently working:
function RoamingAppDataPath: String; external 'C:\Users\Peter\AppData\Roaming\ss\Application\ss.dll';
However I need to point to the DLL with a dynamic value so what I tried to do is
Declare a global variable (DLLPath: String)
Assign DLLPath the value - RoamingAppDataPath+'\ss\Application\ss.dll'
Note: RoamingAppDataPath is a function that outputs the path to the roaming app data folder.
The code I am trying to run is:
function RoamingAppDataPath: String; external DLLPath;
When I compile the code, Delphi is telling me that it is expecting a constant expression:
E2026 Constant expression expected
What is the work around for this?
You have to bind at runtime and that means you need to use LoadLibrary and GetProcAddress:
var
lib: HMODULE;
RoamingAppDataPath: function: string;
lib := LoadLibrary(dllfilename);
if lib=0 then
RaiseLastOSError;
Pointer(RoamingAppDataPath) := GetProcAddress(lib, 'RoamingAppDataPath');
And then you can call it:
radp := RoamingAppDataPath;
Some comments:
I don't know why you write this function when it exists in standard system libraries.
Using string across DLL boundaries is liable to fail. You need to be using ShareMem and make sure that all code is built with the same Delphi version. Better to allocate the buffer in the calling code.
Even if you would be able to use a Variable, you would nowhere be able to set a value to DLLPATH, since already initalization would not be used if a static DLL can not be used.
You will have to use dynamic loadingif you want to define the path for the DLL.
procedure Test;external 'Notexists.DLL';
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
test;
end;
initialization
Showmessage('Hallo'); // will never be seen if test is used.

BPL File needs Run-Time Packages !

I have created a Package and i want to use the BPL File of my Package ...
My Package have VCL.dcp and RTL.dcp as Required libraries , i load this Package in my application without any errors but when i want to unload it , an Access Violation shown !
If i Build my Application with Run-Time Packages ( "vcl" and "rtl" ) , Access Violation not shown !
What is this mean ?! My Application need VCL and RTL Libraries to Load BPLs ?! I want to Load my Package like a DLL File , is there any solution ?
I`m using Delphi 2010
thanks a lot ...
Your BPL requires the RTL and VCL packages. If your Application doesn't require them, then that means the RTL and VCL units are compiled into your EXE file. When your EXE loads your BPL, you now have two copies of the RTL and VCL units — one set of copies comes from within the EXE, and the second copies come from the RTL and VCL packages that your package implicitly causes to be loaded.
Delphi isn't intended to accommodate that situation. It's possible that you have memory that was allocated by one RTL and attempted to get freed by the other RTL. Or there might be function pointers in the EXE that refer to functions that were in the VCL package.
I see three options for you:
Compile your EXE to use packages. Specifically, it should require the same RTL and VCL packages that your BPL requires.
Make your BPL not require any other packages. If it doesn't require RTL and VCL, then any RTL and VCL units that your package uses will get compiled into your BPL. You'll end up with two separate copies again, but it should work better since neither copy will think it's supposed to be shared.
Load your package like a real DLL instead of like a package. You said you wanted to use it like a DLL, so do that. Use LoadLibrary, and then use GetProcAddress to get whatever functions you want to call. If you go this route, it's probably better to not make your code be a package at all. Make it a DLL, and export functions that only use parameter types that you'd expect to find in other DLLs, like integers, character pointers, and record pointers, not strings or objects.
It should be clear that the first option is the easiest. The second could probably work, and it sounds like that's the way you'd prefer, but I expect it will generate more headaches before it finally works. The third option is best if you'll ever have to use other development environments during the lifetime of this project.
What have your package inside?
What work do you do with it?
How do you charge and discharge? What's in it?
What do you do with the package before unload it?
When you Unload it, all the objects/forms/components/... that yo've used is released?
ADDED: I Think that you are using anything of the package when you try to Onload. This is the reason of AV.
In an EXE compiled without runtime package, I load the package:
OutputDebugString(PChar('Loading the package'));
hand := LoadPackage('r:\rrrrrrr\Package1.bpl');
I Unload the package with this code:
OutputDebugString(PChar('Ready to Unload Package'));
UnloadPackage(hand);
OutputDebugString(PChar('Unloaded'));
The package has a unit with a form (form1) and a unit Init.pas, for initialization like this:
unit Init;
interface
// prototipos
procedure Start_P;
procedure Finish_P;
implementation
uses
Unit1, Windows;
procedure Finish_P();
begin
OutputDebugString(PChar('Finish_P form free'));
Form1.Free;
end;
procedure Start_P();
begin
OutputDebugString(PChar('Start_P Creating form'));
Form1 := TForm1.Create(nil);
Form1.Show;
end;
Initialization;
Start_P();
Finalization;
Finish_P();
end.
The package is loaded and the form visualized without problems, and the same with the operation of Close and Unload. The project is compiled with "Build with rutime packages" unchecked.
Can you post any code.
The result of OutputDebugString is this (no AV error):
[2644] Loading the package
[2644] Start_P Creating form
[2644] Ready to Unload Package
[2644] Finish_P form free
[2644] Unloaded
Regards.
Thanks for your helps ...
I put an example of my package and my Application here to Find what is the problem !
We have a package without requiring to Run-Time Packages like VCL and RTL , in other words i removed all libraries from the Requires section in my package :
my package contains a form with code below :
unit MyUnit;
interface
uses
Windows, Forms, StdCtrls, Buttons, Controls, Classes, Dialogs;
type
TMyForm = class(TForm)
MyLabel: TLabel;
MyEdit: TEdit;
PostBtn: TBitBtn;
procedure PostBtnClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MyForm: TMyForm;
implementation
{$R *.dfm}
function ShowForm(FCaption, LCaption : String) : String;
var
F : TMyForm;
begin
F := TMyForm.Create(nil);
try
F.Caption := FCaption;
F.MyLabel.Caption := LCaption;
F.ShowModal;
finally
Result := F.MyEdit.Text;
F.Free;
end;
end;
procedure TMyForm.PostBtnClick(Sender: TObject);
begin
if MyEdit.Text <> '' then
Close
else
ShowMessage('Please Enter Value !');
end;
exports
ShowForm;
end.
I Load this Package and Call ShowForm Function and then Unload package :
var
ShowF : function(FCaption, LCaption : String) : String;
MyPkg : HMODULE;
FC, LC : String;
begin
MyPkg := LoadPackage(ExtractFilePath(Application.ExeName)+'MyPackage.bpl');
FC := 'Enter Value ... ';
LC := 'Value : ';
if MyPkg <> 0 then
begin
try
#ShowF := GetProcAddress(MyPkg, 'ShowForm');
if Assigned(ShowF) then
Edit1.Text := ShowF(FC, LC)
else
ShowMessage('Function not found !');
finally
UnloadPackage(MyPkg);
end;
end;
end;
After the Procedure above done , the AV Shows !
#Neftalí : If I just do loading and unloading the Package , no AV Shows , but i think that is because i don`t call some routines or objects or ... that they need VCL or RTL Libraries , if i use objects and functions and ... of this package , after using them i will get an AV ...
is it true ?!
If I Build my application with Run-Time package ( VCL and RTL ) no AV will shown !
I`m confusing !! , I want to use an BPL package without any Run-Time package needed ...
thanks a lot ...
Yes, if you want to use runtime packages in your application you have to build it with runtime packages, and then it requires them (links statically with them).
The solution to your problem depends on what the problem actually is (which is unclear at the moment).
Ohhhhh, great oversight/neglect (mine).
With the code that you have posted, made a simple change a test it (use PChar).
function ShowForm(FCaption, LCaption : String) : PChar;
...
Result := PChar(F.MyEdit.Text);
...
The same when you define the sitaxis of the function:
ShowF : function(FCaption, LCaption : String):PChar;
Test it and say the result.
Regards.

delphi exe and dll without build with runtime packages

For my last project i was using many frames in my delphi application ,so i dicided to create dlls and put them inside the dlls(ALL created in Delphi)
i have gone through many websites and came up with the code that works but for that example i have to compile both apps and dlls with build with runtime packages which means i have to distribute the bpls also. and if dont check build with runtime packages error is coming
this is the code i found
in exe
procedure TForm1.Button1Click(Sender: TObject);
type
TGetTheFrame =Function( Owner: TComponent; TheParent: TWinControl ): TFrame; stdcall ;
var
GetTheFrame : TGetTheFrame;
begin
try
GetTheFrame(application,TabSheet1).Free ;
except
end;
frm := GetTheFrame(application,TabSheet1) ;
dllHandle := LoadLibrary('project1.dll') ;
if dllHandle <> 0 then
begin
GetTheFrame := GetProcAddress(dllHandle, 'GetTheFrame') ;
frm := GetTheFrame(application,TabSheet1) //call the function
{ ShowMessage('error function not found') ;
FreeLibrary(dllHandle) ; }
end
else
begin
ShowMessage('xxxx.dll not found / not loaded') ;
end
in dll
Function GetTheFrame( Owner: TComponent; TheParent: TWinControl ): TFrame; stdcall;
Begin
Result := TFrame2.Create( Owner );
Result.Parent := TheParent;
End;
thats all but i want this code to work without runtime packages
Too bad. That code won't work without run-time packages. (And with run-time packages, you should use LoadPackage instead of LoadLibrary.)
Without packages, each module of your program (the EXE and each DLL) has its own copy of the definition of all the standard classes, including TFrame, TWinControl, and even TObject. A TWinControl class from the EXE doesn't look like a TWinControl to the DLL.
Since you're sharing classes between modules, you need to make sure they all have the same definitions of those classes, and run-time packages is how you do that.
If you really won't use run-time packages, then you need to change the interface of your DLL so it doesn't share any Delphi object types. Instead of the TWinControl parent, pass the control's Handle property, or any other HWnd value to serve as the parent window. The DLL code will not be able to assume that there is a Delphi object for the parent anymore, and the EXE will not be able to assume that the control it receives is a Delphi object; they will be restricted to using the Windows API to manipulate window handles and send messages.

Resources