We would like to share an ADOConnection across a DLL boundary (Delphi to Delphi at the moment, though could also be C# to Delphi in the near future).
As we would like the flexibility to call the DLL from c# in future, we were hoping to be able to define the DLL call using _Connection as a parameter. Something like:
procedure DoStuff (ADOConnection: _Connection)
var
InnerConnection: TADOConnection;
begin
InnerConnection := TADOConnection.create(nil);
try
InnerConnection.ConnectionObject := ADOConnection;
DoMoreStuff(InnerConnection);
finally
InnerConnection.free;
end;
end;
Unfortunately, the TADOConnection destructor code closes the connection passed into it, which is an unwanted side-effect. Adding
InnerConnection.ConnectionObject := nil
prior to the free doesn't do anything, as it's caught by
if Assigned(Value) = nil
in TADOConnection.SetConnectionObject, which results in the call not doing anything.
Is there a better way of achieving this? Passing the connection string is an alternative, but would mean that we would have to deal with username/password issues and encryption across the boundary. Passing the TADOConnection is another option, but that prevents calling from other languages.
Edit: For clarity, the Username/Password of the original TADOConnection object is set using the .Open routine, so these details aren't in the connection string (in fact, the wrong username is usually stored, as it's the name used to 'test connection' in the MS UDL editor)
You can try this way:
type TInit_StFattDLL = procedure( var DataBase:TAdoConnection);
var Init_StFattDLL:TInit_StFattDll;
The caller is:
Function ConnectDll():Boolean;
var
handleDll:THandle;
begin
handleDll := LoadLibrary('mydll.DLL');
#Init_StFattDLL := GetProcAddress(handleDll , 'myConnectFunction');
if #Init_StFattDLL <> nil then
begin
Init_StFattDLL(ADOConnection1);
result:=true;
end
else
result:=false;
end;
into the the dll put the following:
in the project file put the exports:
Exports myConnectFunction;
global section:
var Database:TAdoConnection;
the exported procedure is the following:
procedure myConnectFunction( var MyDataBase:TAdoConnection);export;
begin
Database:=MyDataBase;
end
Related
I have a dictionary crash in the DataSnap client because its FComparer is somehow nil.
Server side code:
TColorNames = TDictionary<integer, string>;
function TServerMethods.DictionaryTest: TColorNames;
begin
result := TColorNames.Create;
result.Add (1, 'Red');
result.Add (2, 'Blue');
end;
Client side code:
procedure TformClientMain.FetchColors;
var
Colors: TColorNames;
begin
Colors := easServer.DictionaryTest;
if Colors.Items[1]<>'Red'
then ShowMessage('Not red');
end;
Colors.Items[1] crashes (as well as other functions that need the FComparer). The crash happens in System.Generics.Collections, when the function tries to access the FComparer.
function TDictionary<TKey,TValue>.Hash(const Key: TKey): Integer;
I do receive all the data in the list, and just looping through it with for color in Colors.Values do ShowMessage(Color); works fine.
When I create a dictionary instance with TColorNames.Create, on client or server side, the FComparer has a value and these issues do not exist. I placed breakpoints in the dictionary constructor and traced the code during the datasnap call - FComparer always gets a value.
What am I (or Delphi) doing wrong?
The answer to "What is Delphi doing wrong" is:
DataSnap uses a TJsonMarshal and TJsonUnmarshal from the unit Data.DBXJSONReflect. Upon unmarshalling, an instance of a TDictionary<X,Y> is created by calling the parameterless constructor. The parameterless constructor here is the one inherited straight from TObject.
When you, however, type TDictionary<X, Y>.Create(); you're calling the "correct" constructor with a default parameter (Create(ACapacity: Integer = 0);). The TJsonUnmarshall class, however, does not since it is looking for a constructor with really no parameters. The one you're usually calling has a parameter, even if you don't have to pass it.
I don't know how DataSnap works, but you should probably be able to pass a custom marshal and unmarshaller to whatever does the serialization.
Since Embarcadero closed all the bug reports I know of (example) as "Works as expected", it's probably a safe bet that generic collections should not be marshalled back and forth and you should probably revert to arrays.
Here is the minimal code to reproduce:
unit Unit1;
interface
uses
System.Generics.Collections,
System.JSON,
Data.DBXJSONReflect;
type
TColorNames = TDictionary<Integer, String>;
procedure p();
implementation
procedure p();
var
original: TColorNames;
marshaller: TJSONMarshal;
unmarshaller: TJSONUnMarshal;
asJson: TJsonValue;
marshalledBack: TColorNames;
begin
original := TColorNames.Create();
marshaller := TJsonMarshal.Create();
asJson := marshaller.Marshal(original);
unmarshaller := TJSONUnMarshal.Create();
marshalledBack := unmarshaller.Unmarshal(asJson) as TColorNames;
marshalledBack.Add(0, ''); // << will crash because FComparer is nil
end;
end.
I assume that it is possible to get the complete path of the unit (not just the name) at runtime, since when I generate an error, Delphi already has this stored information:
try
Assert (False, '#');
except
on E: EAssertionFailed from
begin
ShowMessage (E.Message); // this show me the path
end;
end;
Would anyone know if there is any function that returns me the path of a specific unit, or something similar?
The complete path of the unit as it was on the machine that compiled the project is only possible using Assert. However personally I don't find that information incredibly useful unless you have many units with the same name in different folders or lost control over your source repository and and library paths in effect when compiling.
To get the unit name you can turn on map file or debug information (aka TD32) and do the following:
Use the FileByLevel function from JclDebug - in your case with the default value (0).
The level parameter tells the function how many calls it look up the callstack. If you put that method into a FormCreate of a VCL form for example and pass 1 it will give you Vcl.Forms.pas as the event handler was called from there.
I hesitate to write this answer as it shows a really dirty hack to get the unit name making use of the Assert compiler magic shown above.
Use the following unit:
unit UnitNameHack;
interface
const
cUnitNameSentinel = '$$$sentinel$$$';
var
HackUnitname: string = '';
implementation
var
OrgAssertErrorProc: TAssertErrorProc = nil;
procedure MyAssertErrorProc(const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer);
begin
if Message = cUnitNameSentinel then begin
HackUnitname:= Filename;
end
else begin
OrgAssertErrorProc(Message, Filename, LineNumber, ErrorAddr);
end;
end;
initialization
OrgAssertErrorProc := AssertErrorProc;
AssertErrorProc := MyAssertErrorProc;
finalization
AssertErrorProc := OrgAssertErrorProc;
end.
Now whenever you need the unit name call
Assert(False, cUnitNameSentinel);
and retrieve the unit name from HackUnitname.
Note that you cannot wrap the Assert call and reading HackUnitName into a function, not even if inlined.
You've said, something similar. I see that it might have been in a different context, but anyway, for objects you can inspect UnitName or UnitScope to get the name of the module where the object instance was declared, without the module path (which is not valuable information anyway).
Based on Uwe Raabe's answer:
// directly after the implementation uses:
var
ThisUnit: string = '<unknown>';
procedure MyAssertErrorProc(const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer);
begin
ThisUnit := Filename;
end;
procedure InitThisUnit;
var
OrgAssertErrorProc: TAssertErrorProc;
begin
OrgAssertErrorProc := AssertErrorProc;
try
AssertErrorProc := MyAssertErrorProc;
Assert(False);
finally
AssertErrorProc := OrgAssertErrorProc;
end;
end;
// [...]
// call InitThisUnit in the initialization section
initialization
InitThisUnit;
end.
It's still a hack, but a bit more elegant. ;-)
Does anyon know how to create word ole object in DLL.
I have one application that load a DLL which in turn create word ole object.
My application crash every time.
MSWord:= CreateOleObject('Word.Application');
Assuming that Word is installed, then the primary reason why you code might fail is that COM has not been initialized in the calling thread. That is not something that should be attempted from the DLL, because you want the DLL to be able to work with consumers that have already initialized COM.
So, the correct way to tackle this is to state as part of the DLL's interface contract that COM must be initialized by the caller. Typically by calling CoInitialize or CoInitializeEx.
One further comment, is that it if the application crashes, that suggests that you error handling is broken. All the functions in your DLL should take steps to catch any exceptions and convert into error codes to be returned to the caller. I suspect that you have not done this and are throwing a Delphi exception out of the DLL. You must never do that.
Note that I have given a broad and general answer. That matches the broad nature of the question, and the fact that there are few details in the question. If you had provided an MCVE we could have offered a more detailed response.
As DavidH points out, CoInitialize has to be called in the calling thread.
A point to watch out for in connection with the main thread of a VCL application is that whether a VCL application calls CoInitialize automatically depends on whether it uses the ComObj unit: if it does the CoInitialize is called via TApplication.Initialize and the InitComObj routine in ComObj; if it does not, you must call it (or CoInitializeEx) yourself.
The easy way to test this is to call the DLL from a TApplication-less console application - this will avoid being misled by ComObj being used some other than your main unit.
Suppose you have a DLL that contains the following exported procedure:
procedure CreateWordDoc;
var
DocText : String;
MSWord,
Document : OleVariant;
begin
MSWord := CreateOleObject('Word.Application');
MSWord.Visible := True;
Document := MSWord.Documents.Add;
DocText := 'Hello Word!';
MSWord.Selection.TypeText(DocText);
end;
then you could call it like this:
program WordCaller;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows, ActiveX;
type
TWordProc = procedure;
var
LibHandle : THandle;
WordProc : TWordProc;
begin
CoInitialize(Nil);
LibHandle := LoadLibrary('WordDll.Dll');
try
if LibHandle <> 0 then begin
try
WordProc := GetProcAddress(LibHandle, 'CreateWordDoc');
if Assigned(WordProc) then
WordProc;
finally
FreeLibrary(LibHandle);
end;
end;
finally
CoUnInitialize;
Readln;
end;
end.
I'm working on a Delphi wrapper for the Googledocs api using Delphi XE2. I generated all the classes using the XML Data Binding Wizard. This is a lot easier to explain using code, so here is the function my test calls.
function TGoogleDocsApi.GetEntries : IXMLEntryTypeList;
var
httpHelper : IHttpHelper;
xml, url : string;
xmlDoc : TXmlDocument;
ss : TStringStream;
feed : IXmlFeedType;
begin
ss := TStringStream.Create;
httpHelper := THttpHelper.Create;
if(fToken.IsExpired) then
fToken.Refresh(fClientId,fClientSecret);
url := BaseUrl + 'feeds/default/private/full?showfolders=true&access_token='+fToken.AccessToken+'&v=3';
xml := httpHelper.GetResponse(url);
ss.WriteString(xml);
ss.Position := 0;
xmlDoc := TXmlDocument.Create(nil);
xmlDoc.LoadFromStream(ss);
feed := GoogleData2.Getfeed(xmlDoc);
Result := feed.Entry;
end;
Now, at the point that 'end' is hit, Result.ChildNodes has an address in memory and it's count is 20. IXMLEntryTypeList is a child interface of IXMLNodeCollection.
Now here is my test:
procedure TestIGoogleDocsApi.TestGetEntries;
var
ReturnValue: IXMLEntryTypeList;
begin
ReturnValue := FIGoogleDocsApi.GetEntries;
if(ReturnValue = nil) then
fail('Return value cannot be nil');
if(ReturnValue.ChildNodes.Count = 0) then
fail('ChildNodes count cannot be 0');
end;
On the second if statement, I get an access violation saying "Access violation at address 0061A55C in module 'GoogleDocsApiTests.exe'. Read of address 00000049" and when I look at my watches for ReturnValue and ReturnValue.ChildNodes, I see that ReturnValue has the same address as Result did in the TGoogleDocsApi.GetEntries method, but it gives me the access violation on the watch for ReturnValue.ChildNodes and in the TGoogleDocsApi.GetEntires method, Result.ChildNodes has a valid address and its properties are filled out.
To me it looks like Delphi is releasing the ChildNodes property somewhere along the line, but that doesn't make sense to me since ReturnValue should still be referencing it which (I think) should keep it around.
Any ideas what might be going on?
You're calling TXMLDocument.Create with an Owner of nil. That means its lifetime is controlled via interface reference counting. In order for that to work, you need to actually use interfaces. Change xmlDoc's type to IXMLDocument to maintain a reference, or else something internal to the VCL will free it when you're not expecting it.
I have a DLL which exectues some code at its entry point, i.e.
procedure MainDLL(Reason: Integer);
begin
{ ... Code here ... }
end;
begin
DLLProc := #MainDLL;
end.
Now, I would like to pass some values to the DLL's entry point from an external application. I have tried creating a hidden window inside the DLL, like that:
const
WM_JAJCO = WM_USER + 1024;
type
TWnd = class(TObject)
class procedure DLLWndProc(var Msg: TMessage);
end;
{ ... }
class procedure TWnd.DLLWndProc(var Msg: TMessage);
var
Tmp: DWORD;
begin
if (Msg.Msg = WM_JAJCO) then
begin
PNewHandle := Msg.LParam;
CreateThread(nil, 0, #Starter, nil, 0, Tmp);
Msg.Result := 0;
end else
Msg.Result := DefWindowProc(MyHnd, Msg.Msg, Msg.WParam, Msg.LParam);
end;
// in the entry point
MyHnd := AllocateHWND(TWnd.DLLWndProc);
Then, after I initialize the DLL in the caller application, I use:
SendMessage(FindWindow('TPUtilWindow', nil), WM_USER + 1024, 0, wi.WndHandle);
Application.ProcessMessages();
But the window created inside the DLL does not seem to receive the message. Do you happen to know why?
If that's a bad method and you have a different solution, please let me know.
You shouldn't be using DLLMain for this. Just export your own init function and call it manually.
That's a rather tortuous approach. You are supposed to do as little as possible in the DllMain function. The canonical solution is to create a dedicated function to perform initialization. Arrange for the host app to call the initialization function before calling anything else.
The most likely reason your version fails is that there are a lot of windows with that class name. Every window created by AllocHwnd has that class name. FindWindow probably just finds the wrong one.
On the other hand, you mention in passing in a comment that this DLL is injected! In that case you can make your method work by using a unique class name or giving the window a unique title so that you can find it.
Finally the call to ProcessMessages looks to be gratuitous.
First make sure that the injected DLL really does create your window handle. WinSight or Spy++ should help you there. Once you know the window really does exist make sure FindWindow find your window handle and not another one with the same class name. IIRC, even the Delphi IDE itself creates window handles using this class name.