Such a basic question, but I'm stuck...
I'm trying to get started with UI Automation using Delphi 2007 (Win32) on Windows 7. It seems I don't have declarations for some of the methods and types I need to use. I have .NET Framework 4.x installed on this machine, but I imported a type library from C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationClient.dll...which in UIAutomationClient_TLB.pas I see is:
UIAutomationClientMajorVersion = 1;
UIAutomationClientMinorVersion = 0;
I'm not sure if that's the problem (wrong version). I can declare the following with no problem:
var
UIAuto: IUIAutomation;
Element: IUIAutomationElement;
But methods of IUIAutomationElement appear to be missing. For instance, there's nothing in the interface declaring the method:
IUIAutomationElement.TryGetCurrentPattern()
...which, according to msdn.microsoft.com is an interface method going back to at least .NET 3.0.
Where / how do I get the necessary interface declarations? Is this possibly a registration problem? If so, what needs to be registered, and how?
Ultimately, I'm wanting to play with retrieving text from controls via UI Automation with something like the following, but technically speaking, I think you have to get code to compile before you can consider it a failure. ;)
var
UIAuto: IUIAutomation;
Element: IUIAutomationElement;
RetVal: HResult;
APattern: AutomationPattern; //not defined!
ValuePattern : ValuePattern; //not defined!
begin
UIAuto := CoCUIAutomation.Create;
Element := UIAuto.GetFocusedElement(RetVal);
if Assigned(Element) then begin
if Element.TryGetCurrentPattern(ValuePattern.Pattern, APattern) then begin //not defined!
Result := ValuePattern.Current.Value; //not defined!
...
end;
It looks like you have imported the .net assembly.
From native code it is best to import the native COM type library. The steps are:
Component | Import Component.
Import a Type Library.
Select UIAutomationClient which is implemented in UIAutomationCore.dll.
This imports a type library and creates a unit named UIAutomationClient_TLB.
The method that you need is IUIAutomationElement.GetCurrentPattern. The TryGetCurrentPattern method in the .net version of the interface is simply a convenience method that indicates failure with a boolean return value rather than by raising an exception. When you call IUIAutomationElement.GetCurrentPattern you will need to check the HRESULT return value to detect failure.
You are importing a .NET class as a COM object. In .NET, classes and methods have to be explicitly declared as COMVisible=True to be accessible via COM. Without seeing how UIAutomationClient.dll actually exports its AutomationElement class, my guess would be that its TryGetCurrentPattern() method is not declared a COM-visible.
The IUIAutomationElement interface exported from UIAutomationCore.dll, on the othe hand, does not have a TryGetCurrentPattern() method.
Related
Is it possible to put some classes into a DLL?
I have several custom classes in a project I am working on and would like to have them put in a DLL and then accessed in the main application when needed, plus if they are in a DLL I can reuse these classes in other projects if I need to.
I found this link: http://www.delphipages.com/forum/showthread.php?t=84394 which discusses accessing classes in a DLL and it mentions delegating to a class-type property but I could not find any further information on this in the Delphi help or online.
Is there any reason I should not put classes in a DLL, and if it is ok is there a better way of doing it then in the example from the link above?
Thanks
It is not possible to get a Class/Instance from a DLL.
Instead of the class you can hand over an interface to the class.
Below you find a simple example
// The Interface-Deklaration for Main and DLL
unit StringFunctions_IntfU;
interface
type
IStringFunctions = interface
['{240B567B-E619-48E4-8CDA-F6A722F44A71}']
function CopyStr( const AStr : WideString; Index, Count : Integer ) : WideString;
end;
implementation
end.
The simple DLL
library StringFunctions;
uses
StringFunctions_IntfU; // use Interface-Deklaration
{$R *.res}
type
TStringFunctions = class( TInterfacedObject, IStringFunctions )
protected
function CopyStr( const AStr : WideString; Index : Integer; Count : Integer ) : WideString;
end;
{ TStringFunctions }
function TStringFunctions.CopyStr( const AStr : WideString; Index, Count : Integer ) : WideString;
begin
Result := Copy( AStr, Index, Count );
end;
function GetStringFunctions : IStringFunctions; stdcall; export;
begin
Result := TStringFunctions.Create;
end;
exports
GetStringFunctions;
begin
end.
And now the simple Main Program
uses
StringFunctions_IntfU; // use Interface-Deklaration
// Static link to external function
function GetStringFunctions : IStringFunctions; stdcall; external 'StringFunctions.dll' name 'GetStringFunctions';
procedure TMainView.Button1Click( Sender : TObject );
begin
Label1.Caption := GetStringFunctions.CopyStr( Edit1.Text, 1, 5 );
end;
Use runtime packages for this purpose; it's exactly what they're designed for in the first place. They get loaded automatically (or can be loaded manually), and automatically set up the sharing of the same memory manager so you can freely use classes and types between them.
You're much better off using packages (which is exactly what the IDE does for much of its functionality for that very reason).
Delphi does not support either importing or exporting classes from DLLs. To import a class from another module, you need to use packages.
While the official answer is "you can't", anything is possible of course. Frameworks like Remobjects SDK and Remobjects Hydra has been doing this for a long time. The problem is that it requires you to create an infrastructure around such a system, which is not something Delphi deals with out of the box.
The first step is memory management. A DLL is injected into the process loading it, but it does not share memory management. It has to be this way since a DLL can be created in a myriad of languages, each with their own internal mechanisms. This poses a problem with safety (i.e program writing into DLL memory and visa versa).
Secondly, interface (read: content description). How will your application know what classes it can create, class members, parameter types and so on. This is why COM requires type-libraries, which describe the content of a DLL.
Third, life-time management. If memory management for the objects created from a DLL is handled by the DLL, the DLL must also release said objects.
The above steps already exists and it's called COM. You are of course free to create as many COM DLL files as you please, just remember that these have to be registered with Windows before you use them. Either "on the fly" by your application (if you have the security rights to do so) or by your installer.
This is why COM, while representing the ultimate plugin system, is rarely used by Delphi programmers, because the technical cost of using it as a plugin system outweighs the benefits.
The alternative way
If you can assure that your DLL's are only to be used by Delphi programs, then you have a second way to explore. You have to create methods to share the memory manager of your main program with the DLL (Remobjects does this). This allows you to share objects, strings and more between the DLL and the main application.
You can then use RTTI to "map" classes stored in the DLL (the DLL must do this and generate both class and method tables) which can be invoked through a proxy class you device yourself.
All in all, unless you have plenty of free time to waste, I would either buy a system like Remobjects Hydra - or stick with packages. But can it be done another way? Of course it can. But at the cost of time and hard work.
A TDictionary is assigned to an TObject variable. I can cast the TOBject back to a TDictionary in the same project. However if the cast is done in a referenced bpl project the cast fails depending on how the project is referenced.
The test code:
procedure TestTDefault.Test1;
var
Obj: TObject;
begin
Obj := TDictionary<string, Integer>.Create;
Check(Obj is TDictionary<string, Integer>);
CheckNotNull(Obj as TDictionary<string, Integer>);
// this calls the function in the referenced bpl package.
// Returns True if Obj can be cast back to a TDictionary.
CheckTrue(TestIfDictionary(Obj)); // outcome depends if referenced packge is included in <DCC_UsePackage>
end;
The function in the dependent package:
function TestIfDictionary(aObject: TObject): Boolean;
begin
Result := aObject is TDictionary<string,Integer>;
end;
I have a simple project group with only two packages:
The DUnit Test Runner (Package1Tests.exe)
Package1.bpl
Both packages have the same compiler/linker options set.
What is very odd is that the test works only if Package1 is NOT listed as a runtime package:
However test fails if Package1 is listed a runtime package!!
I am using XE2. As to the purpose, this issue surfaced when dealing with RTTI. I was able to isolate the problem in this simple Test project. Problem has nothing to do with RTTI.
Just as a note, if instead of using a TDictionary I use a TStringList, then test always works. So problem seems to be somehow related to generics.
If needed I can make the simple test project available.
I have spent quite some time tracking down this problem. I am happy that I got to discover what triggers the problem. But unfortunately I can just not understand why this happens.
The problem you have here is related to generic instantiation. Each module is separately instantiating the generic type, and so they have different type identity. This is a basic limitation of the design of generics, and its interaction with runtime packages.
You could instantiate the generic type in a unit in the package, for instance like this:
type
TDictionaryStringInteger = class(TDictionary<string, Integer>);
And then use TDictionaryStringInteger in place of TDictionary<string, Integer>. However, this pretty much cripples your code as it stops you writing generic methods that use generic types.
The other way out of your bind is to stop using runtime packages. Frankly, that seems rather more attractive to me.
You should use type declaration for this generic class.
type
Tx = TDictionary<string, Integer>;
....
Obj := Tx.Create;
Check(Obj is Tx);
RTTI doesn't matter in your case.
I am trying to access ntextcat dll from Delphi 2007.
Registering the dll´s failed (I have tried 32 and 64Bit regsvr32).
Importing the typelibrary in Delphi failed as well (as .dll and .Net).
Is there anybody out there who got access to ntextcat within Delphi?
This is a .net library. As such it is not readily accessible from unmanaged code such as Delphi. If you do want to consume it from Delphi you'll need to create a wrapper. For instance by using one of the following:
A COM interface.
A mixed mode C++/CLI assembly that wraps the managed library and exposes an unmanaged interface.
A wrapper based on Robert Giesecke's UnmanagedExports.
The latter two allow you to consume a .net assembly as a native DLL.
If the library has a rich and broad interface then the task of wrapping it if not going to be terribly easy or convenient. You may be better finding a different library that is easier to consume.
If the library has a small interface, or if you only need a small part of its capabilities, then making a wrapper may be more tractable.
Based on David´s answer (thank you for pointing me in the right direction) this the solution using Robert Giesecke's UnmanagedExports:
C# (be aware to set the platform target to the same as your Delphi application targets to! Do not use "AnyTarget"!)
using RGiesecke.DllExport;
using System.Runtime.InteropServices;
namespace ClassLibrary1
{
public class Class1
{
[DllExport("testfunc", CallingConvention = CallingConvention.StdCall)]
public static int anytest(int iInt) {
return iInt;
}
}
}
The Delphi source:
Type
Ttest = function(const iInt: Integer): Integer; stdcall;
procedure TForm1.btTestClick(Sender: TObject);
var
Handle: Integer;
test : Ttest;
begin
Handle := LoadLibrary(<PathToDLL>ClassLibrary1.dll');
if Handle <> 0 then begin
#test := GetProcAddress(Handle, 'testfunc');
if #test <> NIL then ShowMessage(IntToStr(test(1234)));
end;
end;
Based on this it is possible to access the ntextcat .net library.
Is it possible to put some classes into a DLL?
I have several custom classes in a project I am working on and would like to have them put in a DLL and then accessed in the main application when needed, plus if they are in a DLL I can reuse these classes in other projects if I need to.
I found this link: http://www.delphipages.com/forum/showthread.php?t=84394 which discusses accessing classes in a DLL and it mentions delegating to a class-type property but I could not find any further information on this in the Delphi help or online.
Is there any reason I should not put classes in a DLL, and if it is ok is there a better way of doing it then in the example from the link above?
Thanks
It is not possible to get a Class/Instance from a DLL.
Instead of the class you can hand over an interface to the class.
Below you find a simple example
// The Interface-Deklaration for Main and DLL
unit StringFunctions_IntfU;
interface
type
IStringFunctions = interface
['{240B567B-E619-48E4-8CDA-F6A722F44A71}']
function CopyStr( const AStr : WideString; Index, Count : Integer ) : WideString;
end;
implementation
end.
The simple DLL
library StringFunctions;
uses
StringFunctions_IntfU; // use Interface-Deklaration
{$R *.res}
type
TStringFunctions = class( TInterfacedObject, IStringFunctions )
protected
function CopyStr( const AStr : WideString; Index : Integer; Count : Integer ) : WideString;
end;
{ TStringFunctions }
function TStringFunctions.CopyStr( const AStr : WideString; Index, Count : Integer ) : WideString;
begin
Result := Copy( AStr, Index, Count );
end;
function GetStringFunctions : IStringFunctions; stdcall; export;
begin
Result := TStringFunctions.Create;
end;
exports
GetStringFunctions;
begin
end.
And now the simple Main Program
uses
StringFunctions_IntfU; // use Interface-Deklaration
// Static link to external function
function GetStringFunctions : IStringFunctions; stdcall; external 'StringFunctions.dll' name 'GetStringFunctions';
procedure TMainView.Button1Click( Sender : TObject );
begin
Label1.Caption := GetStringFunctions.CopyStr( Edit1.Text, 1, 5 );
end;
Use runtime packages for this purpose; it's exactly what they're designed for in the first place. They get loaded automatically (or can be loaded manually), and automatically set up the sharing of the same memory manager so you can freely use classes and types between them.
You're much better off using packages (which is exactly what the IDE does for much of its functionality for that very reason).
Delphi does not support either importing or exporting classes from DLLs. To import a class from another module, you need to use packages.
While the official answer is "you can't", anything is possible of course. Frameworks like Remobjects SDK and Remobjects Hydra has been doing this for a long time. The problem is that it requires you to create an infrastructure around such a system, which is not something Delphi deals with out of the box.
The first step is memory management. A DLL is injected into the process loading it, but it does not share memory management. It has to be this way since a DLL can be created in a myriad of languages, each with their own internal mechanisms. This poses a problem with safety (i.e program writing into DLL memory and visa versa).
Secondly, interface (read: content description). How will your application know what classes it can create, class members, parameter types and so on. This is why COM requires type-libraries, which describe the content of a DLL.
Third, life-time management. If memory management for the objects created from a DLL is handled by the DLL, the DLL must also release said objects.
The above steps already exists and it's called COM. You are of course free to create as many COM DLL files as you please, just remember that these have to be registered with Windows before you use them. Either "on the fly" by your application (if you have the security rights to do so) or by your installer.
This is why COM, while representing the ultimate plugin system, is rarely used by Delphi programmers, because the technical cost of using it as a plugin system outweighs the benefits.
The alternative way
If you can assure that your DLL's are only to be used by Delphi programs, then you have a second way to explore. You have to create methods to share the memory manager of your main program with the DLL (Remobjects does this). This allows you to share objects, strings and more between the DLL and the main application.
You can then use RTTI to "map" classes stored in the DLL (the DLL must do this and generate both class and method tables) which can be invoked through a proxy class you device yourself.
All in all, unless you have plenty of free time to waste, I would either buy a system like Remobjects Hydra - or stick with packages. But can it be done another way? Of course it can. But at the cost of time and hard work.
What's the reason this won't compile?
type
IInterfaceA = interface ['{44F93616-0161-4912-9D63-3E8AA140CA0D}']
procedure DoA;
end;
IInterfaceB = interface(IInterfaceA) ['{80CB6D35-E12F-462A-AAA9-E7C0F6FE0982}']
procedure DoB;
end;
TImplementsAB = class(TSingletonImplementation, IInterfaceB)
procedure DoA;
procedure DoB;
end;
var
ImplementsAB: TImplementsAB;
InterfaceA: IInterfaceA;
InterfaceB: IInterfaceB;
begin
ImplementsAB := TImplementsAB.Create;
InterfaceA := ImplementsAB; >> incompatible types
...
end
In contrast this is how I make it work:
InterfaceA := ImplementsAB as InterfaceB;
or
InterfaceA := InterfaceB;
I mean, if IInterfaceB inherits from IInterfaceA and TImplementsAB implements IInterfaceB, it wouldn't be logical to also implement IInterfaceA and be type compatible?
This so because early OLE/COM had a bug and Borland decided to be compatible with it. This is mentioned in this article: New Delphi language feature: Multiple inheritance for interfaces in Delphi for .NET. The solution is to list all ancestor interfaces explicitly in the class as Mikael wrote.
Some quotes from the linked article:
The problem was in COM itself. To load a module, COM would load the DLL, GetProcAddress on a well-known entry point that was supposed to be exported from the DLL, call the DLL function to obtain an IUnknown interface, and then QueryInterface for IClassFactory. The problem was, when Microsoft added support for IClassFactory2, they added the QueryInterface for IClassFactory2 after the existing code that queried for IClassFactory. IClassFactory2 would only be requested if the query for IClassFactory failed.
Thus, COM would never request IClassFactory2 on any COM server that implemented both IClassFactory2 and IClassFactory.
This bug existed in COM for a long time. Microsoft said that they couldn't fix the COM loader with an OS service pack because both Word and Excel (at the time) relied on the buggy behavior. Regardless of whether it's fixed in the latest releases of COM or not, Borland has to provide some way to preserve this behavior in Win32 Delphi for the forseeable future. Suddenly adding all ancestors into an implementing class that weren't there before is very likely to break existing code that unintentionally falls into the same pattern as the COM loader.
Another way to make it work is to include both interfaces in the class declaration.
TImplementsAB = class(TSingletonImplementation, IInterfaceA, IInterfaceB)
procedure DoA;
procedure DoB;
end;
I guess this is what is required for the compiler to realize that TImplementsAB implements both IInterfaceA and IInterfaceB.