Getting a DLL class procedure address in Delphi - delphi

I have a DLL file from which I need the memory address of a class procedure. I am getting the handle to the DLL file, but when I use GetProcAddress, I can't get the address of the procedure. I have tried the following strings for the process name parameter:
"ProcName"
"ProcClass.ProcName"
"ProcClass::ProcName"
"ProcInterface::ProcName"
"ProcInterface.ProcName"
In none of the cases have I gotten the memory address of the procedure. I am mostly certain that the procedure is public.
What is the string format for doing this? Would it be easier to declare a function pointing to the external procedure and get the address later? Like this:
procedure ProcName(); stdcall; far; external 'Example.DLL';
ProcPointer := #ProcName;

GetProcAddress only gives you the address for exported functions. Your DLL surely doesn't export the methods of a class!
Use an PE explorer to look for the exported names. For example, use the PE explorer available in GExperts. I've got a "PE Information" menu entry under the GExperts menu.

You are into reverse engineering territory here.
I think that if I were you I would just step through in the CPU view of the debugger, following a call to the method of interest, and find the entry point address. I'd subtract it from the base address of the DLL and that would be the offset. Then to calculate the address at runtime you just add the offset it to the base address of the DLL in memory at that time. You can find out the base address with calls to LoadLibrary or GetModuleHandle.
Why hard code the offset? Well, since you can't modify your DLL it doesn't seem to be too limiting. If hard coding the offset is not viable then there are other means of locating entry points, but I must admit I'm not the world's greatest expert on that.
Finally, when you implement the replacement method, you will need to replace it with a global function/procedure with an extra parameter, the first parameter, which takes the place of Self.

I might be reading this wrong. But it seems to me you wrote the DLL.
You should write a function that is NOT a member of any class, and export it from your DLL. Inside that function, call your class method.
If you didn't write the DLL, you still need to find out what functions it exports, and it is very unlikely any of them were class methods, at least not in Pascal.
If someone wrote a dll in C++ and exported its methods, then you would have to investigate C++ name mangling rules.

Related

Delphi classes, shared memory, and varying DLL loading addresses

I'm working with an old and complex system that shares memory between dozens (sometimes hundreds) of Win32 processes. The code is mostly very old Pascal that has been ported to Delphi a few years ago.
(Almost) all of the code is in a single DLL, which all of the processes load. At the moment, we have forced a fixed loading address of that DLL. Image base is defined and ASLR is disabled in linker settings. Each process checks the DLL loading addresses at startup and the entire system refuses to work if the DLL cannot be loaded at the exact same address in all of the processes. This is of course a problematic solution. Sometimes customers have all sorts of 3rd party gadgets which affect the address space and prevents our product from having the address it wants for the DLL.
The reason for the fixed DLL loading address is below. I'm wondering if there is a way to work around this problem.
I've been trying to introduce object-oriented programming. The problem is, if I instantiate a Delphi class in the shared memory, that instance now seems to be dependent on the DLL loading address. For example, if another process tries to destroy that object, it will crash, unless the two processes happen to have the same DLL address. Delphi runtime seems to save function addresses in the object instance, assuming they will stay fixed for the lifetime of the object.
One possible solution might be to copy the DLL contents to the shared memory, then do some sort of magic trickery on DLL_PROCESS_ATTACH to make the process run that copy of the code instead of the loaded DLL address. The shared memory we have is always mapped at the same addresses. (Yes, this is also a problem sometimes but very rarely since the shared memory can be mapped at high (above 2 GB) addresses which are easily available.)
Or is there a way to tell Delphi compiler "please do not assume that the addresses of the functions related to this class are fixed"? I'm using Delphi 11.1.
I was able to figure out a a solution that seems to work well, so let me answer my own question.
The issue is that in order for dynamic dispatch to work, the object instance must be 'tagged' with type information. In the case of Delphi in Win32, this tag is in the first 32 bits of the object instance, and it is a memory address into the DLL where the the code of the class in question is.
If you shift this address to match the variable (process-specific) address of the DLL, the dynamically dispatched methods work fine. In order to do this, you need to compare this address to the loading address of the DLL (or any other reference address inside the DLL) and save the offset, when creating the object.
Then, before calling the object's methods in another process, "localize" the object by taking the actual address of the DLL, adding the offset, and writing this sum to the first 32 bits of the object.
Now you can use the object in any process, as long as you localize it first:
Obj.Localize;
Obj.Do_Something;
This can be neatly wrapped in a class.
Offset is simply a private 32-bit UInt32.
constructor Global_Object.Create;
begin
Self.Offset := PUInt32(Self)^ - Self.Reference_Address;
end;
procedure Global_Object.Localize;
begin
PUInt32(Self)^ := Self.Reference_Address + Self.Offset;
end;
destructor Global_Object.Destroy;
begin
inherited Destroy;
end;
function Global_Object.Reference_Address
: Cardinal;
begin
// Anything in the DLL can be used as a reference,
// such as the address of this function.
Result := Cardinal(#Global_Object.Reference_Address);
end;

Intercepting RegisterClass method in Classes.pas Delphi7

Is there a way of doing that? The list of registered classes is in the TRegGroups instance in Classes.pas unit, but problem is that instance is declared in the implementation section of unit. Is there a way of obtaining an address of RegisterClass procedure, or RegGroups.RegisterClass method?
Using KOLDetours.pas you can intercept calls to the method and then call the original method.
You can find it here: http://code.google.com/p/asmprofiler/source/browse/trunk/SRC/KOLDetours.pas
The file contains examples of how to use it.
To answer your specific question:
You may get the address of Classes.RegisterClass simply by using #Classes.RegisterClass as it is exposed in the interface section of Classes.pas.
The address TRegGroup.RegisterClass will be a bit tricky as it is not exposed in the interface section. Using the address of Classes.RegisterClass you could read the offset of TRegGroup.RegisterClass from the compiled code and then calculate the absolute address as a function of Classes.RegisterClass's address. Ultimately, this will be fragile across different version of the compiler.
As an alternative, if you are willing to make a small modification to each package, you could create a unit containing a function named RegisterClass and ensure that the unit is included in your registration unit before Classes.pas. Your unit would then link against your new RegisterClass function which could call some notification method before calling Classes.RegisterClass.
As you have indicated that you are statically linking to the packages, this is all somewhat moot because you will not have an opportunity to connect whatever notification routine you devise. To solve that issue you will want to dynamically load your packages after you have created your splash screen and are prepared to pump messages for it.
Alternatively, you could modify your package registration unit to use InitProc to delay registration until your TApplication instance is created. This would give you an opportunity to create some visual means of indicating registration progress before the registration actually take place.
In a comment you state:
I have 22 packages. Each package has (besides others) a unit with all the units in that package placed in interface section, and a procedure with simple RegisterClass(TSomeClass) for every class in that package.
In which case the answer is obvious. Define your own function, named MyRegisterClass for instance, and call that function instead.

The "local" directive in Delphi

I was sitting around debugging some code and I stumbled across this line in SysUtils.pas:
procedure ConvertError(ResString: PResStringRec); local;
What does the local keyword do exactly? It seems the ConvertError function is not declared in the interface section of the file, is this just a clarification that the function is indeed local, or is there a practical benefit to using this directive beyond that?
It dates back to the Linux compiler, Kylix. Here's what I can see in my Delphi 6 language guide, page 9-4:
The directive local, which marks routines as unavailable for export, is platform-specific and has no effect in Windows programming.
On Linux, the local directive provides a slight performance optimization for routines that are compiled into a library, but are not exported. The directive can be specified for standalone procedures and functions, but not for methods. A routine declared with local—for example.
function Contraband(I: Integer): Integer; local;
—does not refresh the EBX register and hence
cannot be exported from a library.
cannot be declared in the interface section of a unit.
cannot have its address take or be assigned to a procedural-type variable.
if it is a pure assembler routine, cannot be called from a another unit unless the caller sets up EBX.

how to get a component class in DLL

I'm making a mdi application with many child forms, one of which is the form to display the report.
on the report form I use dll files to display all the components on the form and look for value
in each component, I use the following code to do that.
// this code i write in dll or bpl file
procedure getReportParams(Form : Tform); stdcall;
var
i : integer;
str, cbstr : string;
b : boolean;
begin
for i:=0 to Form.ComponentCount-1 do
begin
str:=str+Form.Components[i].Name+' - '+Form.Components[i].ClassName+', ';
if (Form.Components[i] is TcxLookupComboBox) then
begin
showmessage('test 1');
// if i uncomment the code below, the program get error Einvalidcast
// cbstr:=(Form.Components[i] as TcxDBLookupComboBox).Text;
// if (Form.Components[i] as TcxDBLookUpCombobox).Parent=Form.FindComponent('pnledit') then
// showmessage((Form.Components[i] as TcxDBLookUpCombobox).Name);
end;
end;
showmessage(str);
// this showmessage work well in dll, bpl, or other unit
if b then
showmessage(cbstr+' true') else showmessage(cbstr+' false');
end;
simple question is how to write code cbstr:=(Form.Components[i] as TcxDBLookupComboBox).Text; with corecly without get EInvalidCast error?
Btw if i write this code in other unit, dll and bpl program get error but if i write that code in same unit (unit report) the code work well. thank for advance.
Your problem is that the classes in your DLL are different from the classes in your executable. You have two instances of these classes, even thought they are compiled from the same code. The compiler is accurate when it says that the object is not the class that you cast it to. You simply cannot share Delphi classes using DLLs.
The solution is either:
Compile all your code into a single executable.
Use runtime packages to share classes.
In your scenario it's not enough that you put your code in a package. The problem are the devexpresses classes. You need to link to those using runtime packages. Because you are not doing so you have multiple different versions of those classes.
You note that the results of the is operator appear to be at odds with the ClassName function. Well, that's because all the different versions of the class have the same name.
I also note that the issue you are encountering is the same as in your earlier question: How can I pass TForm to a DLL as parameter? The explanation and advice from the answer you accepted there apply equally here.
If you already used a (Foo is TSomething) type check, then you know that foo is a TSomething and you can use a static cast: TSomething(Foo)
If you are trying to link this code in another Executable or dll, you probably have not included the correct units IF IT FAILS TO COMPILE, AND IF it fails at runtime, you didn't turn the BPL link option on (Use Runtime PACKAGES, and make sure the list of package names is complete). Remember that checking "something is TSomething" you are comparing a class declaration with another live object's class. A class is not defined by the string name. It's actually type information linked into your application.
When you link a DLL (without runtime packages) you actually may have linked TSomething into your main EXE and into your DLL, and they are TWO DIFFERENT copies of the class with the same name and the name matters not one bit. When you compare for identity, there's no way to know at runtime that they were the same thing. SO they aren't.
You think about code the way you see it written on the screen. When it runs, it's been compiled into code, and the types are simply data in the exe or DLL. So TSomething-in-myexe.exe is not the same class as TSomething-in-mydll.dll.
If you want them to be the same, turn on Use Runtime Packages (BPLs) for all places where you want to compare type information between different compiled parts. In particular passing pointers or references to VCL types between non-bpl-enabled linked targets is not going to work the way you thought it would.
You should also make sure that the list of runtime packages contains the package that defines that class you're using. (TcxSomething is probably a developer express component, go find what package BPL it is defined in.)

Delphi, can't use a function in an external .pas file

I am using functions from an external .pas file. I can use some of the functions, but not others. As far as i can see the functions are declared the same way, I would like to post some of the file, but don't know ho to post large amounts of code.
You can use the functions that are declared in the interface section, that is the section of code before the implementation section.
You are probably trying to call functions that are defined only in the implementation section, that is that code that appears after the implementation keyword.
These different sections are how Delphi implements public and private visibility at the unit level.
Usually, in well written units, there will be a reason for functions being made private to the unit. But if you feel it reasonable to override the author's decision then you need to redeclare the function in the interface section. This will make it available to your code which uses the 3rd party unit.
The file is not properly linked and/or not included in your projects search path and/or shadowed by some other file with same function names and/or odd functions are within $IFDEF clauses.
Check spelling, uses clauses, working function location (Ctrl+click on function name in your code), $IFDEF clauses.
The file is not properly linked in Delphi environment options
The file could be located outside of project search path. Hence it's not linked.
The file path is typed wrongly in project (DPR file). E.g. you are referring to older path with olde version of a file.
In each of these cases some functions could be taken from other files if name fits. E.g. function gluUnproject can be taken both from OpenGL.pas and dglOpenGL.pas, if first unit is not linked properly I would get the same problem as you are having now - some functions work and some are missing. In any of the cases you should compile your project, Ctrl+Click on a working function name and see where it brings you, check file version location.
The functions could be inside of $IFDEF clauses. These are compiler directives and code within such clause will be invisible to compiler if certain condition is not met. E.g. {$IFDEF MSWindows} Func {$ENDIF} won't be accessible on Linux.

Resources