I am trying to load the contents of a file from one of the Windows virtual folders (for example, a camera or iPhone picture folder). Below is some sample code that I am using to play around with this:
procedure TfrmForm.ButtonClick(Sender: TObject);
Var
Dialog: TAttachDialog;
Enum: IEnumShellItems;
Name: LPWSTR;
Item: IShellItem;
Strm: IStream;
OStrm: TOLEStream;
FStrm: TFileStream;
Result: HRESULT;
Buf: Array[0..99] Of Char;
Read: LongInt;
begin
Result := CoInitializeEx(Nil, COINIT_APARTMENTTHREADED Or
COINIT_DISABLE_OLE1DDE);
If Succeeded(Result) Then
Begin
Dialog := TAttachDialog.Create(Self);
Try
Dialog.Options := [fdoAllowMultiSelect, fdoPathMustExist,
fdoFileMustExist];
Dialog.Title := 'Select Attachments';
If Dialog.Execute(Self.Handle) Then
Begin
If FAILED(Dialog.ShellItems.EnumItems(Enum)) Then
Raise Exception.Create('Could not get the list of files selected.');
While Enum.Next(1, Item, Nil) = S_OK Do
Begin
If (Item.GetDisplayName(SIGDN_NORMALDISPLAY, Name) = S_OK) Then
Begin
mResults.Lines.Add(Name);
CoTaskMemFree(Name);
End;
If Item.BindToHandler(Nil, BHID_Stream, IID_IStream, Strm) = S_OK Then
Begin
OStrm := TOLEStream.Create(Strm);
FStrm := TFileStream.Create('C:\Temp\Test.jpg', fmCreate);
FStrm.CopyFrom(OStrm, OStrm.Size);
FreeAndNil(OStrm);
FreeAndNil(FStrm);
Strm := Nil;
End;
Item := Nil;
End;
End;
Finally
FreeAndNil(Dialog);
End;
CoUninitialize;
End;
end;
TAttachDialog is just a descendant of TCustomFileOpenDialog that exposes the ShellItems property. In my actual application, I need a TStream object returned. So, in this example, I am using a TFileStream top copy the source file as proof of concept that I have successfully accessed the file using a Delphi stream. Everything works Ok until I try the FStrm.CopyFrom at which point I get a "Not Implemented" error. What am I doing wrong with this or is there a better way entirely to do what I want?
The only time TStream itself raises a "not implemented" error is if neither the 32bit or 64bit version of Seek() are overridden in a descendant class (or one of them erroneously called the inherited method). If that were true, an EStreamError exception is raised saying "ClassName.Seek not implemented".
TOLEStream does override the 32bit version of Seek() to call IStream.Seek(). However, it does not override the TStream.GetSize() property getter. So when you are reading the OStrm.Size value before calling CopyFrom(), it calls the default TStream.GetSize() method, which uses Seek() to determine the stream size - Seek() to get the current position, then Seek() again to the end of the stream, saving the result, then Seek() again to go back to the previous position.
So, my guess would be that the IStream you have obtained likely does not support random seeking so its Seek() method is returning E_NOTIMPL, which TOLEStream.Seek() would detect and raise an EOleSysError exception saying "Not implemented".
Try calling IStream.Stat() to get the stream size (or derive a class from TOLEStream and override the GetSize() method to call Stat()), and then pass the returned size to CopyFrom() if > 0 (if you pass Count=0 to CopyFrom(), it will read the source stream's Position and Size properties, thus causing the same Seek() error), eg:
var
...
Stat: STATSTG;
begin
...
if Item.BindToHandler(Nil, BHID_Stream, IID_IStream, Strm) = S_OK Then
try
OStrm := TOLEStream.Create(Strm);
try
FStrm := TFileStream.Create('C:\Temp\Test.jpg', fmCreate);
try
OleCheck(Strm.Stat(Stat, STATFLAG_NONAME));
if Stat.cbSize.QuadPart > 0 then
FStrm.CopyFrom(OStrm, Stat.cbSize.QuadPart);
finally
FreeAndNil(FStrm);
end;
finally
FreeAndNil(OStrm);
end;
finally
Strm := Nil;
end;
...
end;
The alternative would be to simply avoid TStream.CopyFrom() and manually copy the bytes yourself, by allocating a local buffer and then calling OStrm.Read() in a loop, writing each read buffer to FStrm, until OStrm.Read() reports that there is no more bytes to read:
var
...
Buf: array[0..1023] of Byte;
NumRead: Integer;
begin
...
if Item.BindToHandler(Nil, BHID_Stream, IID_IStream, Strm) = S_OK Then
try
OStrm := TOLEStream.Create(Strm);
try
FStrm := TFileStream.Create('C:\Temp\Test.jpg', fmCreate);
try
repeat
NumRead := OStrm.Read(Buf[0], SizeOf(Buf));
if NumRead <= 0 then Break;
FStrm.WriteBuffer(Buf[0], NumRead);
until False;
finally
FreeAndNil(FStrm);
end;
finally
FreeAndNil(OStrm);
end;
finally
Strm := Nil;
end;
...
end;
Related
In Delphi 10.1 Berlin, I'm trying to change a picture on a form by loading a PNG image from a resource.
I've followed this:
Load image from embedded resource
and used a TWICImage to automatically handle different possible image formats.
In this case I specifically want to use a PNG for transparency.
For some reason the function I've created returns nothing.
However, if I call result.savetofile('test.png') within the function the resource is succesfully saved, which verifies that the resource exists in the EXE and has been found.
function LoadImageResource(NativeInstance: NativeUInt; ImageResource: string): TWICImage;
var
Strm: TResourceStream;
WICImage: TWICImage;
begin
Strm := TResourceStream.Create(NativeInstance, ImageResource, RT_RCDATA);
try
Strm.Position := 0;
WICImage := TWICImage.Create;
try
WICImage.LoadFromStream(Strm);
result := WICImage; //these return empty
result.savetofile('test.png'); //this succesfully saves the resource to disk
finally
WICImage.Free;
end;
finally
Strm.Free;
end;
end;
Outside of the function, if I attempt to assign the image by calling for example Image1.picture.assign(LoadFromResource(...)) or Image1.picture.graphic := LoadFromResource(...) nothing gets assigned. And If I then call Image1.savetofile('test.png') I get an access violation error.
What might I be missing?
The problem is that you are destroying the image that you return. It's important to understand that classes are reference types in Delphi. So after the assignment to Result, in your code, you still have only a single instance, but two references to that same single instance.
You need to remove the call to Free.
function LoadImageResource(Module: NativeUInt; const ResName: string): TWICImage;
var
Strm: TResourceStream;
begin
Strm := TResourceStream.Create(Module, ResName, RT_RCDATA);
try
Result := TWICImage.Create;
Result.LoadFromStream(Strm);
finally
Strm.Free;
end;
end;
A little tweak is needed to make the function exception safe:
function LoadImageResource(Module: NativeUInt; const ResName: string): TWICImage;
var
Strm: TResourceStream;
begin
Strm := TResourceStream.Create(Module, ResName, RT_RCDATA);
try
Result := TWICImage.Create;
try
Result.LoadFromStream(Strm);
except
Result.Free;
raise;
end;
finally
Strm.Free;
end;
end;
When you call the function it behaves like a constructor. It either succeeds and returns a new instance, handing over ownership to the caller. Or it raises an exception. Accordingly I would name the function CreateImageFromResource.
I want to get the index in the system imagelist of an object in the shell namespace.
If this object was a file i could use SHGetFileInfo:
function GetFileImageIndex(const Filename: string): Integer;
var
sfi: TSHFileInfo;
begin
SHGetFileInfo(PChar(Filename), FILE_ATTRIBUTE_NORMAL, sfi, SizeOf(sfi),
SHGFI_USEFILEATTRIBUTES or SHGFI_SYSICONINDEX);
Result := sfi.iIcon;
end;
Except i don't have a file
The thing i have doesn't exist on the hard-drive as a folder or file, e.g.:
Control Panel
Homegroup
Network
But i still need to get the index in the system imagelist of the icon that corresponds to this thing. I started with SHGetFileInfo (as it supports pidls). But that fell apart. Then i tried using IExtractIcon, but that fell apart:
function GetObjectImageIndex(ParentFolder: IShellFolder; const ChildPidl: PItemIDList): Integer;
//var
// sfi: TSHFileInfo;
// extractIcon: IExtractIcon;
// iconFile: WideString;
// iconIndexInFile: Integer;
// flags: Cardinal;
begin
{
This function is the shell namespace equivalent of GetFileImageIndex helper function.
}
(*
Won't work (MSDN: "The PIDL must be a fully qualified PIDL. Relative PIDLs are not allowed.")
SHGetFileInfo(PWideChar(ChildPidl), FILE_ATTRIBUTE_NORMAL,
sfi, SizeOf(sfi),
SHGFI_PIDL or SHGFI_SYSICONINDEX);
*)
(*
Won't work. Doesn't return an index into the system imagelist
ParentFolder.GetUIObjectOf(0, 1, ChildPidl, IExtractIcon, nil, {out}extractIcon);
SetLength(iconFile, MAX_PATH);
extractIcon.GetIconLocation(0, PWideChar(iconFile), Length(iconFile), iconIndexInFile, {out}flags);
*)
Result := -1; //TODO: Figure out how to do it.
end;
Given an IShellFolder and a pidl in that folder, how do i get the icon in the system imagelist of that thing?
The simple answer is that you pass an absolute PIDL that identifies the object to SHGetFileInfo. You say you tried that without success, but this is the way to solve your problem.
You should go back to SHGetFileInfo and make it work. It looks like you got as far as having a relative PIDL and stopped. Make an absolute PIDL with ILCombine and you should be home.
If you don't have a PIDL for the containing IShellFolder then you'll want to read this topic: How to obtain the PIDL of an IShellFolder
function CreateGlobalChildIDList(AParentFolder: IShellFolder; const AChildIDList: PItemIDList): PItemIDList; forward;
function GetObjectImageIndex(AParentFolder: IShellFolder; const AChildIDList: PItemIDList): Integer;
var
ShellIcon: IShellIcon;
ChildIDList: PItemIDList;
FileInfo: TSHFileInfo;
begin
try
Result := -1;
try
OleCheck(AParentFolder.QueryInterface(IShellIcon, ShellIcon));
try
OleCheck(ShellIcon.GetIconOf(AChildIDList, GIL_FORSHELL, Result));
finally
ShellIcon := nil;
end;
except
Result := -1;
end;
if Result = -1 then
begin
ChildIDList := CreateGlobalChildIDList(AParentFolder, AChildIDList);
try
ZeroMemory(#FileInfo, SizeOf(FileInfo));
SHGetFileInfo(PWideChar(ChildIDList), FILE_ATTRIBUTE_NORMAL, FileInfo, SizeOf(FileInfo), SHGFI_PIDL or SHGFI_SYSICONINDEX);
Result := FileInfo.iIcon;
finally
CoTaskMemFree(ChildIDList);
end;
end;
except
Result := -1;
end;
end;
function CretaeGlobalChildIDList(AParentFolder: IShellFolder; const AChildIDList: PItemIDList): PItemIDList;
var
PersistFolder2: IPersistFolder2;
PersistIDList: IPersistIDList;
ParentIDList: PItemIDList;
begin
if Succeeded(AParentFolder.QueryInterface(IPersistFolder2, PersistFolder2)) then
try
OleCheck(PersistFolder2.GetCurFolder(ParentIDList));
try
Result := ILCombine(ParentIDList, AChildIDList);
finally
CoTaskMemFree(ParentIDList);
end;
finally
PersistFolder2 := nil;
end
else
if Succeeded(AParentFolder.QueryInterface(IPersistIDList, PersistIDList)) then
try
OleCheck(PersistIDList.GetIDList(ParentIDList));
try
Result := ILCombine(ParentIDList, AChildIDList);
finally
CoTaskMemFree(ParentIDList);
end;
finally
PersistIDList := nil;
end
else
raise Exception.Create('Cannot create PIDL');
end;
I use a timage in a form which load a background image.
The problem is when i choose another picture in run time and change it by
Img_Bk.Picture.LoadFromFile( SaveFileName );
It doesnt work (Picture did n't change ). I mean it shows previous picture and doesn't show the new image during run time. Id like to change application background image during run time in my company by users which main form is a mdi form .
I use delphi 7 .
try
Img_Bk.Picture := nil ;
if FileSize > 100 then
begin
Img_Bk.Picture.LoadFromFile( SaveFileName );
end;
Img_Bk.Stretch := True ;
except
end;
LoadFromFile is known to work. So there must be a more prosaic explanation.
The first possible explanation is that FileSize is not greater than 100 and the if condition evaluates false.
Another possible explanation is that the image in the file that you specify is not the one you are expecting.
Otherwise, your code has a swallow all exception handler. And so when the call to LoadFromFile fails and raises an exception, your code ignores that and carries on as if nothing un-toward had happened. Remove the try/except, and deal with the error that will be revealed.
The real lesson for you to learn is never to write such an exception handler again.
This program should prove to you that LoadFromFile is just fine:
program ImageDemo;
uses
Types, Math, IOUtils, SHFolder, Forms, Controls, StdCtrls, ExtCtrls, jpeg;
var
Form: TForm;
Image: TImage;
Timer: TTimer;
ImageIndex: Integer = -1;
MyPictures: string;
Images: TStringDynArray;
type
THelper = class
class procedure Timer(Sender: TObject);
end;
class procedure THelper.Timer(Sender: TObject);
begin
inc(ImageIndex);
if ImageIndex>high(Images) then
ImageIndex := 0;
if ImageIndex>high(Images) then
exit;
Image.Picture.LoadFromFile(Images[ImageIndex]);
end;
function GetMyPictures: string;
var
Str: array[0..260] of Char;
begin
if SHGetFolderPath(0, CSIDL_MYPICTURES, 0, 0, Str) = S_OK then
Result := Str;
end;
procedure BuildForm;
begin
Form.ClientWidth := 700;
Form.ClientHeight := 500;
Image := TImage.Create(Form);
Image.Parent := Form;
Image.Align := alClient;
Image.Stretch := True;
Timer := TTimer.Create(Form);
Timer.OnTimer := THelper.Timer;
Timer.Interval := 100;
end;
begin
MyPictures := GetMyPictures;
Images := TDirectory.GetFiles(MyPictures, '*.jpg', TSearchOption.soAllDirectories);
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm, Form);
BuildForm;
Application.Run;
end.
I had the same problem today. After the call of LoadFromFile() the image does not change. I have tried Refresh, Repaint, Invalidate and Update -> nothing helped. Then I found that resizing the from immediately updated the image.
Finally I found that setting property Visible to false and back to true updates the image, too.
FormMain.Image1.Visible := false;
FormMain.Image1.Picture.LoadFromFile(newImageFileName);
FormMain.Image1.Visible := true;
Perhaps not the best but it works for me.
I'am struggling implementing an OSX CoreMidi MidiCallback procedure with Lazarus / FreePascal.
In the MIDIServices unit, MIDIReadProc, the callback routine, is definded:
MIDIReadProc = procedure( (*const*) pktlist: MIDIPacketListPtr; readProcRefCon: UnivPtr; srcConnRefCon: UnivPtr );
This routine is called on a separate high-priority thread owned by CoreMidi when midi events are received.
I defined a callback procedure for handling received midi events:
Type procedure MyMidiCallback(pktList: MIDIPacketListPtr;readProcRefCon: UnivPtr; srcConnRefCon: UnivPtrMy);
procedure TMainForm.MyMidiCallback(pktList: MIDIPacketListPtr;readProcRefCon: UnivPtr; srcConnRefCon: UnivPtr);
begin
// handle midi packets
end;
The midi callback hook is defined in the following code at 'MidiInputPortCreate':
procedure TMainForm.ReceiveMidiTestClick(Sender: TObject);
var
NumOfSources, NumOfDestinations: ItemCount;
x: byte;
MIDIDestinationPointer, MidiSourcePointer: MIDIEndpointRef;
EndPointName: CFStringRef;
MidiClient: MidiClientRef;
InputPort: MidiPortRef;
MidiCallback: MidiReadProc;
begin
NumOfDestinations := MIDIGetNumberOfDestinations;
NumOfSources := MIDIGetNumberOfSources;
Memo.Lines.Add('Number of Midi Sources: ' + IntToStr(NumOfSources));
EndPointName := nil;
MidiClient := nil;
InputPort := nil;
MidiCallback := #TMainform.MyMidiCallback;
for x := 0 to NumOfDestinations -1 do // show destinations
begin
MidiDestinationPointer := MidiGetDestination(x);
MIDIObjectGetStringProperty(MidiDestinationPointer, kMIDIPropertyName, EndPointName);
Memo.Lines.Add('Destination ' + IntToStr(x) + ': ' + CFStrToAnsiStr(EndPointName));
end;
for x := 0 to NumOfSources -1 do // show sources
begin
MidiSourcePointer := MIDIGetSource(x);
MIDIObjectGetStringProperty(MidiSourcePointer, kMIDIPropertyName, EndPointName);
Memo.Lines.Add('Source ' + IntToStr(x) + ': ' + CFStrToAnsiStr(EndPointName));
end;
MidiClientCreate(CFSTRP('Midi Input Client'), nil, nil, MidiClient);
MidiInputPortCreate(MidiClient, CFSTRP('Input'), MidiCallback, nil, InputPort); // MidiCallback
MIDISourcePointer := MIDIGetSource(0); // select source(0) = midi keyboard
MidiPortConnectSource(InputPort, MIDISourcePointer, nil);
end;
Compiling generates the following error message:
mainunit.pas(480,19) Error: Incompatible types: got "<procedure variable type of procedure(MIDIPacketListPtr,Pointer,Pointer) of object;Register>" expected "<procedure variable type of procedure(MIDIPacketListPtr,Pointer,Pointer);MWPascal>"
I'am stuck here now; hope someone can help.
--------------------------------- UPDATE #1 ----------------------------------
The code above was indeed a bit strange so I rewrote things:
procedure TMainForm.ReceiveMidiTestClick(Sender: TObject);
var
MidiClient: MidiClientRef;
InputPort: MidiPortRef;
MidiCallback: MIDIReadProc;
begin
MidiCallback := MyMidiCallback;
MidiClientCreate(CFSTRP('Midi Input Client'), nil, nil, MidiClient);
MidiInputPortCreate(MidiClient, CFSTRP('Input'), MidiCallback, nil, InputPort);
MidiPortConnectSource(InputPort, MIDIGetSource(0), nil);
end;
procedure MyMidiCallback(pktList: MIDIPacketListPtr; readProcRefCon: UnivPtr; srcConnRefCon: UnivPtr);
begin
// handle midi packets
end;
Now the code compiles without errors but as soon as i hit a key on the midi keyboard, the application crashes with the following error message:
'ERROR Project ... raised exception class 'External: Sigtrap' at address FFFFD96F'
(FFFFD96F is probably the pointer to the MidiCallback routine).
Basically, the issue I have is how to let the MidiCallback pointer in MidiInputPortCreate point correctly to my MyMidiCallback procedure where I handle midi events.
BTW, sending Midi events works fine.
Let's put the error declarations in the error on separate lines:
mainunit.pas(480,19) Error: Incompatible types: got "
procedure(MIDIPacketListPtr,Pointer,Pointer) of object;Register>" expected "<
procedure variable type of procedure(MIDIPacketListPtr,Pointer,Pointer);MWPascal>"
Note two crucial differences:
1) The "of object" difference in the two procedure declarations in the error means you passed a method instead of a proper procedure.
2) Besides that, calling conventions doesn't seem to match, one is mwpascal; one is Register. Register is the default for most modes, so no calling convention modifier means register.
The "callback" part of your question is strange. You define a type as a procedure, but provide a method as implementation?
In order to learn multithreading, I've created a thread inside a COM Thread (TRemoteDataModule).
This is my Component Factory:
TComponentFactory.Create(ComServer, TServerConn2, Class_ServerConn2, ciMultiInstance, tmApartment);
Inside the Thread, I didn't needed to Call CoInitialize to use TADOQuery.Create, .Open... .Exec
I read that I need to initialize the COM library on a thread before you call any of the library functions except CoGetMalloc, to get a pointer to the standard allocator, and the memory allocation functions.
But in this case, the absence of CoInitialize didn't brought me any trouble.
Is this related with Thread Model?
Where can I Find the explanation for this subject?
UPDATE:
When I say INSIDE, it means inside the COM method context:
interface
type
TWorker = class(TThread);
TServerConn2 = class(TRemoteDataModule, IServerConn2)
public
procedure Method(); safecall;
end;
implementation
procedure TServerConn2.Method();
var W: TWorker;
begin
W := TWorkerTread.Create(Self);
end;
UPDATE 2:
The TADOConnection used to connect to database are currently being created in the COM Thread context (TThread.Create constructor). Although, TADOConnection.Open and TADOQuery.Create/.Open are both being performed inside TThread.Execute .
UPDATE 3 - Simulacrum
Interface:
type
TServerConn2 = class;
TWorker = class(TThread)
private
FDB: TADOConnection;
FOwner: TServerConn2;
protected
procedure Execute; override;
public
constructor Create(Owner: TServerConn2);
destructor Destroy; override;
end;
TServerConn2 = class(TRemoteDataModule, IServerConn2)
ADOConnection1: TADOConnection;
procedure RemoteDataModuleCreate(Sender: TObject);
private
{ Private declarations }
protected
class procedure UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); override;
procedure CheckException; safecall;
public
User, Pswd, Str: String;
Ok: Boolean;
end;
Implementation:
class procedure TServerConn2.UpdateRegistry(Register: Boolean; const ClassID, ProgID: string);
begin
if Register then
begin
inherited UpdateRegistry(Register, ClassID, ProgID);
EnableSocketTransport(ClassID);
EnableWebTransport(ClassID);
end else
begin
DisableSocketTransport(ClassID);
DisableWebTransport(ClassID);
inherited UpdateRegistry(Register, ClassID, ProgID);
end;
end;
{ TWorker }
constructor TWorker.Create(Owner: TServerConn2);
begin
inherited Create(False);
FreeOnTerminate := True;
FDB := TADOConnection.Create(nil);
FOwner := Owner;
end;
destructor TWorker.Destroy;
begin
FDB.Free;
FOwner.Ok := True;
inherited;
end;
procedure TWorker.Execute;
var Qry: TADOQuery;
begin
FDB.LoginPrompt := False;
FDB.ConnectionString := FOwner.Str;
FDB.Open(FOwner.User, FOwner.Pswd);
Qry := TADOQuery.Create(nil);
try
Qry.Connection := FDB;
Qry.LockType := ltReadOnly;
Qry.SQL.Text := 'SELECT TOP 1 * FROM MyTable';
Qry.Open;
finally
Qry.Free;
end;
end;
procedure TServerConn2.CheckException;
var W: TWorker;
begin
W := TWorker.Create(Self);
while not Ok do Sleep(100);
end;
procedure TServerConn2.RemoteDataModuleCreate(Sender: TObject);
begin
User := 'user';
Pswd := 'pass';
Str := ADOConnection1.ConnectionString;
end;
initialization
TComponentFactory.Create(ComServer, TServerConn2,
Class_ServerConn2, ciMultiInstance, tmApartment);
end.
UPDATE 4
The error should happen here:
function CreateADOObject(const ClassID: TGUID): IUnknown;
var
Status: HResult;
FPUControlWord: Word;
begin
asm
FNSTCW FPUControlWord
end;
Status := CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or
CLSCTX_LOCAL_SERVER, IUnknown, Result);
asm
FNCLEX
FLDCW FPUControlWord
end;
if (Status = REGDB_E_CLASSNOTREG) then
raise Exception.CreateRes(#SADOCreateError) else
OleCheck(Status);
end;
By somehow (because of TComponentFactory maybe?) CoCreateInstance identifies that TWorker is in the same context than TServerConn2 and don't raise errors?
Either or both of the following might apply:
On a thread not initialized with COM all existing interface pointers keep working until you make a COM API call or otherwise require COM marshalling which then fails detecting an uninitialized thread. That is, your "didn't brought me any trouble" might actually be too early to say.
If any thread in the process calls CoInitialize[Ex] with the COINIT_MULTITHREADED flag, then that not only initializes the current thread as a member of the multi-threaded apartment, but it also says, "Any thread which has never called CoInitialize[Ex] is also part of the multi-threaded apartment." - so called impicit MTA thing
The TADOConnection used to connect to database are currently being created in the COM Thread context (TThread.Create constructor). Although, TADOConnection.Open and TADOQuery.Create/.Open are both being performed inside TThread.Execute .
That will not work, for 2 reasons:
TWorker.Create() and TWorker.Execute() will run in different thread contexts. Create() will run in the context of the thread that is calling TServerConn2.CheckException() (which will have already called CoInitialize/Ex() on itself beforehand), but Execute() will run in the context of the TThread thread instead. ADO is apartment threaded, which means its COM interfaces cannot be used across thread/apartment boundaries unless you marshal them, either via the IGlobalInterfaceTable interface or the CoMarshalInterThreadInterfaceInStream() and CoGetInterfaceAndReleaseStream() functions.
even if you did marshal the ADO interfaces, TWorker.Execute() must call CoInitialize/Ex() on itself. EVERY individual thread must initialize COM to establish its threading model before then accessing any COM interfaces. The threading model dictates how COM accesses interfaces (direct or through proxies), whether message queues are used, etc.
So the simple solution to your problem is to NOT create and use the ADO components across thread boundaries at all. Move your TADOConnection into Execute() instead:
constructor TWorker.Create(Owner: TServerConn2);
begin
inherited Create(False);
FreeOnTerminate := True;
FOwner := Owner;
end;
destructor TWorker.Destroy;
begin
FOwner.Ok := True;
inherited;
end;
procedure TWorker.Execute;
var
DB: TADOConnection;
Qry: TADOQuery;
begin
CoInitialize;
try
DB := TADOConnection.Create(nil);
try
DB.LoginPrompt := False;
DB.ConnectionString := FOwner.Str;
DB.Open(FOwner.User, FOwner.Pswd);
Qry := TADOQuery.Create(nil);
try
Qry.Connection := DB;
Qry.LockType := ltReadOnly;
Qry.SQL.Text := 'SELECT TOP 1 * FROM MyTable';
Qry.Open;
finally
Qry.Free;
end;
finally
DB.Free;
end;
finally
CoUninitialize;
end;
end;
When you create an apartment thread using TComponentFactory it calls CoInitialize and CoUnInitialize for you - it's right in the VCL source (System.Win.VCLCom.pas):
procedure TApartmentThread.Execute;
var
msg: TMsg;
Unk: IUnknown;
begin
try
CoInitialize(nil); // *** HERE
try
FCreateResult := FFactory.CreateInstanceLic(FUnkOuter, nil, FIID, '', Unk);
FUnkOuter := nil;
FFactory := nil;
if FCreateResult = S_OK then
CoMarshalInterThreadInterfaceInStream(FIID, Unk, IStream(FStream));
ReleaseSemaphore(FSemaphore, 1, nil);
if FCreateResult = S_OK then
while GetMessage(msg, 0, 0, 0) do
begin
DispatchMessage(msg);
Unk._AddRef;
if Unk._Release = 1 then break;
end;
finally
Unk := nil;
CoUninitialize; // ** AND HERE
end;
except
{ No exceptions should go unhandled }
end;
end;