We have several DataSnap (ugh, it still pains me to say that instead of MIDAS  ) servers in our application suite. We can run them either as regular processes or services. Recently, one of our customers went to Windows 2003 and our applications quit working. It turns out that Microsoft made some changes in Win2003 that causes a "Server Execution Failed" error when running a COM server as a service.
While searching Google, I found one post from Anders Evensen, who had contacted Microsoft directly about the issue. According to him, "The problem is that Microsoft requires StartServiceCtrlDispatcher to be called before CoRegisterClassObject. However, this requirement has not been implemented in earlier versions of Windows than 2003. Windows 2003 checks that the service has been started before it allows CoRegisterClassObject to be called."
Delphi does this backwards, since StartServiceCtrlDispatcher is called in TService.Execute, and CoRegisterClassObject is called during the call to Application.Initialize. I ended up with the following solution, and things now work wonderfully on all Windows versions.
function IsRunningInInstallMode : Boolean;
function FindSwitch(const Switch: string): Boolean;
begin
Result := FindCmdLineSwitch(Switch, ['-', '/'], True);
end;
begin
Result := FindSwitch('REGSERVER') or FindSwitch('UNREGSERVER')
or FindSwitch('INSTALL') or FindSwitch('UNINSTALL');
end;
procedure TService1.ServiceExecute(Sender: TService);
begin
{ Windows 2003 Server support. The problem is that you should always call
StartServiceCtrlDispatcher (done in VCL) before calling CoRegisterClassObject
(done from Application.Initialize). Win2003 now enforces this explicitly, but
earlier versions did nothing if this was done improperly. }
if not IsRunningInInstallMode then
SvcMgr.Application.Initialize;
while not Terminated do
begin
ServiceThread.ProcessRequests(false);
Sleep(500);
end;
end;
I installed AD/AM (download link is here) a while back in order to do some LDAP testing on my local XP box without having to turn it into a full-fledged Domain Controller. It installs easily and works reasonably well.
However, I was having all sorts of problems getting the WinLDAP JEDI port to work against AD/AM. AD/AM is LDAP version 3, so I modified the demo that came with JEDI to say:
Version := LDAP_VERSION3;
ldap_set_option(nil, LDAP_OPT_PROTOCOL_VERSION, @Version);
The result of all of this is that I get an "Operations error" when calling ldap_search_s. Doing some digging on Google, I believe there is some problem with the authentication against AD/AM when using ldap_simple_bind_s. No error is returned, but my gut tells me the error lies there.
My first thought at solving this was to create a hook DLL that would do OutputDebugStrings each and every time I used the wldap32.dll. Doing this, I could compare the calls and parameters used when using the Address Book (wab.exe) program - which works - and the sample program from the JEDI port - which doesn't work. I even used ldp.exe to do some testing and couldn't get authentication and searching to work properly. ldp reported that my ldap_simple_bind_s authenticated me properly, but the subsequent call to ldap_search_s resulted in the Operations Error with extended server error information telling me that "In order to perform this operation a successful bind must be completed on the connection.".
In the end, I had to succumb to deadline pressures. I now use ADSI, and things work just fine. I'd really like to get the low-level LDAP working, but that has to be a task left for another day.
If you've ever developed an ActiveForm, then you probably have run into the scenario where you go to a standard control in your form (e.g. an Edit control), select the text, and then press Ctrl+C to copy the text. However, I'm willing to bet that your next reaction was stunned amazement when you realized that the text was not actually copied to the clipboard. To make matters even more maddening, if you use Ctrl+X or Ctrl+V in that very same edit control, things work as you would expect.
I had to fix this problem recently, and it is awfully strange. Pressing Ctrl+X and Ctrl+V makes the TActiveFormControl.TranslateAccelerator method return S_FALSE. Pressing Ctrl+C, the return is S_OK. So in order to fix this, I want to return S_FALSE when Ctrl+C is pressed. I do this by creating a new TActiveXControlClass that overrides the TranslateAccelerator method and returns S_FALSE when needed. The only other thing that needs to be done is to use the new class in your TActiveFormFactory.Create call so that we can get calls to our TranslateAccelerator method and do our own custom stuff.
One caveat: Things are working quite well in IE6. I'm not sure what will happen in other ActiveX containers. Use at your own risk, or at least test in whatever container you're deploying to first!
Here's the code. Please let me know what you think. If you see any improvements that can be made, I'd love to hear about those, too. But at least, for now, my customers are happy again.
type
TTranslateAcceleratorFormControl = class(TActiveFormControl, IOleInPlaceActiveObject)
function TranslateAccelerator(var msg: TMsg): HResult; stdcall;
end;
{ TTranslateAcceleratorFormControl }
{
When pressing Ctrl+C, the inherited method returns S_OK meaning that the container thinks
it did what it needed to, and therefore, the message is consumed. When pressing Ctrl+X or
Ctrl+V, the return from the inherited method is S_FALSE.
Therefore, we will always call the inherited method to allow for default processing. If the
return is S_OK, and the message is WM_KEYDOWN, and we have pressed Ctrl+C, we override
the result and return S_FALSE instead.
}
function TTranslateAcceleratorFormControl.TranslateAccelerator(var msg: TMsg): HResult;
begin
Result := inherited TranslateAccelerator(msg);
if Result = S_OK then
begin
if (msg.message = WM_KEYDOWN) and (GetKeyState(VK_CONTROL) < 0) and (msg.wParam = 67) then
Result := S_FALSE;
end;
end;
initialization
TActiveFormFactory.Create(
ComServer,
//TActiveFormControl, // standard Delphi-generated class
TTranslateAcceleratorFormControl, // our replacement class
TAFActionTest,
Class_AFActionTest,
1,
'',
OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL,
tmApartment);
end.
Falafel has assembled a team whose excellence is hard to surpass. Now they're putting on a conference. The line up of speakers and cost makes me wonder how it's possible, but who am I to question it.
I hope everyone doesn't rush out there and sign up right away and shut me out of a spot. I'm trying to work some things out so that I can get there.
I was in the process of solving a problem that required me to do some message spying. I haven't used WinSight in several years, but the last time I used it, it was sufficient for my needs. Well, either the past is causing me to remember the experience more fondly (doubtful), or the environments have changed enough over the past few years to cause WinSight to not be as good any more (more likely). The current problem boils down to several windows that were not properly identified, and as a result, I could not trace messages, see window details, or simply navigate through the window hierarchy.
I installed Spy++ from VS.NET, and it worked rather well. I found what I needed with minimal fuss. Spy++ isn't a bad application, overall.
I did do some digging and found a free message spy application. I haven't tried it, but it looks promising and is billed to be better than Spy++. Check out Winspector and try it for yourself.
I've written some about this interface, and how it allows you to grab data from an HTML page and use that data inside an ActiveX control. In a nutshell, your HTML will have an OBJECT tag for the ActiveX control and - optionally - some PARAM tags inside the OBJECT tag. One thing to keep in mind is that the IPersistPropertyBag methods will only fire if you actually define at least one PARAM tag. Without a PARAM tag, the code will not execute.
The moral of the story is: if you have some kind of initialization code in your IPersistPropertyBag methods, be doubly sure that you define a PARAM tag!
Which reminds me, now that Delphi Informant is no longer in business, I'll see what I can do to get my articles published with them posted on my web site. I'll post an entry here when I can do that.
In SysUtils, there is a handy function (CheckWin32Version) to check that you are running at least a certain version of Windows. Unfortunately, it is wrong. For example, let's say you want to make sure you are running Win2000 or greater. The following code should do just that:
if CheckWin32Version(5) then
CallMyWin2000OrGreaterFunctionHere;
When running on XP, the major version will be 5, and the minor version will be 1. These are stored in writable constants in SysUtils. Inside CheckWin32Version, the check for AMinor is backwards, so your results will be wrong.
I am now using this function instead and everything works fine:
function FixedCheckWin32Version(AMajor: Integer; AMinor: Integer = 0): Boolean;
begin
Result := (AMajor < Win32MajorVersion) or
((AMajor = Win32MajorVersion) and
(AMinor <= Win32MinorVersion));
end;
Just a side-note: This was fixed in Delphi 7 and above. Of course, if you're using D6, you'll need to use this.
I'm a little partial to Borland DB technologies, and especially BDP.  However, I think this demo shows some of the power that BDP has in the upcoming version of Delphi. Check out the BDNtv episode presented by Jason Vokes and see what you think.
I saw many other properties and methods in this demo that weren't touched on. All in all, BDP looks incredibly flexible in Diamondback - especially when either doing multi-tier apps, or using data from heterogenous sources.
By default, COM Servers register themselves in HKEY_CLASSES_ROOT (HKCR). The actual registry entries will get written to HKEY_LOCAL_MACHINE (HKLM) in most cases. HKCR is an alias to HKEY_LOCAL_MACHINE in older versions of Windows, but starting in Windows 2000 and later, HKCR is a merged view of HKLM and HKEY_CURRENT_USER (HKCU). I needed a way to register my COM server into HKCU so that locked down machines that don't have access to HKLM could still run properly. For some background information on HKCR, check out MSDN.
I came across the function RegOverridePredefKey, and it looked like it would do the job by allowing me to hook into a root HKEY and write to a completely different HKEY. So I started looking in the Delphi source code, and noticed that this function was not imported. The following code snippet shows the procedure that I ended up writing to have anything written to HKCR be written to HKCU instead.
function RegOverridePredefKey(hKey: HKEY; hNewKey: HKEY): Longint; stdcall;
external advapi32 name 'RegOverridePredefKey';
procedure OverrideRegistryKey(Register: boolean);
var
HKCU: HKEY;
ret: integer;
begin
if Register then
begin
RegOpenKeyEx(HKEY_CURRENT_USER, 'Software\Classes', 0, HEY_ALL_ACCESS, HKCU);
try
ret := RegOverridePredefKey(HKEY_CLASSES_ROOT, HKCU);
if ret <> ERROR_SUCCESS then
ShowMessage('Error overriding HKCU:' + #13#10 + SysErrorMessage(ret));
finally
RegCloseKey(HKCU);
end;
end
else
RegOverridePredefKey(HKEY_CLASSES_ROOT, 0);
end;
So that left me with the question "Where do I call this?". My first thought was to hook into the UpdateRegistry method and call my OverrideRegistry procedure there. Unfortunately, by the time UpdateRegistry is called in my Remote DataModule (RDM), several other registration calls have already finished. This left me with some things in HKLM and some in HKCU. Then I remembered that the COM registration occurs when Application.Initialize is called in the dpr file. The following is the code I use in my dpr to write to the right place in the registry.
OverrideRegistryKey(true);
try
Application.Initialize;
finally
OverrideRegistryKey(false);
end;
After doing this, I checked in regedit and verified that everything was registered in HKCU, and it was there. Since HKCR shows the merge of HKLM and HKCU, it was also present in HKCR. This means that COM calls will find the app server. Running a quick test showed me that everything worked as I expected.
In my production code, I'll also put in a quick check to make sure that we're running on Windows 2000 or greater. Since we run both as a Server and a Service, I'll put this code in a conditional define, since there is no concept of HKCU in a service.
Note: I also failed to find the header for RegOpenUserClassesRoot in Delphi. If you find that you need this function, you'll need to import the function yourself.
|