I've decided toolbars in nearly all applications are pointless. The buttons hardly ever do what I want them to do. Take the print button as an example. This button has been the bane of my existence for some time. Press this button in most applications and off the printer goes. This might work fine for some people, but for me something always goes horribly wrong and I'm always better off going to File | Print to get the print dialog. The reason for this; I have two printers connected to my computer. All applications default to the last printer a job was sent to. Why oh why can't applications remember the last printer I printed from this application and use that printer? The last thing I need is to print a document with archival ink and heavy weight cotton rag paper at 16" x 24".
When writing your applications there are two things to learn from this:
1. Applications need to remember the last printer they printed to and default to that printer. Not the last printer printed to from the computer.
2. Toolbar buttons need to be configurable. They are meant to be a shortcut.
Wednesday, January 31, 2007
Why I Disable Most Application Toolbars
Posted by Chris Bensen at 9:18 PM 0 comments
Thursday, January 25, 2007
Using Google Analytics For Tracking Website Traffic
I've been using Google Analytics for tracking traffic to my website and my blog for a while now and I find the information it provides very useful. Google Analytics can tell you things like where in the world the traffic is coming from, configurations of users machines, and the number of returning and new visitors to your website. Very useful to determine your target audience. And best of all it's free! It's also easy to use which is an added bonus. But you get what you pay for. I have three problems with Google Analytics:
1. The lack of real time statistics. I would like to see what is going on now not tomorrow.
2. I question the accuracy of Google Analytics traffic numbers. I've compared it to a couple other logs and there are some discrepancies. The only thing that could account for these differences are robots, visitors who turn off Javascript (but I know under 5% of the visitors to my sites have Javascript turned off), or proxy servers like Proxomitron.
3. What does Google do with the data they collect? At this point the service is free and until they do something questionable I will continue to use them. Google Analytics is like the Safeway Club Card for the web; they know what store you visit, what soda you buy and when you buy it, now they can target adds to you with more accuracy.
One bit of interesting information is that no one with a 30" monitor visits my website. The top three resolutions are 1280 x 1024, 1024 x 768 and 1600 x 1200 totaling over 75% of the visitors. I guess my request for a 30" monitor to optimize the Delphi user experience is pointless. Bummer 'cause I love my 30" monitor at home.
Posted by Chris Bensen at 8:16 AM 0 comments
Monday, January 22, 2007
PHP Functions
I've been using PHP for a little over a year now. I ended up redoing my website (www.chrisbensen.com) entirely with PHP over the last holiday and I couldn't be happier. It is still a work in progress but at this point I haven’t used any PHP packages for a few reasons. Here were my personal reasons for my choice to write basically everything from scratch:
1. Both my wife’s website and my website soon share the same PHP backend, and it had to be dead simple to update the photo gallery.
2. In the past when I have had to change hosts and my website required a particular package to be installed it was a lot of work to migrate everything. I also do my photography on a Mac and that is the easiest place to build my website. I’ve also found that PHP on Windows is lacking. So I wanted to rely on the lowest common denominator. This also excluded PHP 5.
3. Performance. I’ve found that some photo galleries are really slow and really difficult to tie to a shopping cart.
4. Photo galleries and shopping carts are difficult to tie together.
In doing everything from scratch I’ve come across many things that are lacking in the PHP library. One of the functions I’ve come to rely on is ChangeFilePath and ChangeFileExtension. Here are both these functions as I’ve implemented them.
function ChangeFilePath($FileName, $Path)
{
$name = basename($FileName);
if ($Path[strlen($Path) - 1] != '/')
return "$Path/$name";
return "$Path$name";
}
function ChangeFileExtension($FileName, $Extension)
{
$index = 0;
for ($index = strlen($FileName) - 1; $index >= 0; $index--)
{
$c = $FileName[$index];
if ($c == '.')
{
$index--;
break;
}
}
$temp = "";
for (; $index >= 0; $index--)
{
$c = $FileName[$index];
$temp = "$c$temp";
}
return "$temp$Extension";
}
I know they might not be the most optimal, but I searched for 20 minutes and didn’t find anything with Google or php.net so I wrote them both in a few minutes and moved on. I find it odd with such an open source web language that two functions as simple as changing the path or the file extension are so difficult to come by. Does anyone know of a good resource for PHP programmers?
Posted by Chris Bensen at 8:33 PM 2 comments
Sunday, January 21, 2007
New Photo Of The Week
Copyright © 2007 Chris Bensen. All rights reserved.
The new photo of the week has been updated with a photograph I took in Yosemite Valley in October of 2005 in the early early morning.
Posted by Chris Bensen at 9:48 PM 2 comments
Labels: photo
Thursday, January 18, 2007
Ice Skating Cars
This winter seems to be a good year for ice skating cars.
Posted by Chris Bensen at 9:17 PM 0 comments
Wednesday, January 17, 2007
COM+ Objects Created in Delphi and Hosted on Windows 2003
It seems that Windows 2003 has matured to the point where this is a pretty common server OS along with a dual or quad core processor. There have been reports that COM+ objects created with Delphi 2005 and 2006 can cause a loader-lock when started from Component Services. Just to get the word out, we found that in most cases the loader-lock can be circumvented starting the COM+ objects with a dead simple application containing only one button that calls CoCreateInstance on the COM+ object and immediately releases the interface. Since the COM+ object is registered with COM+ the object pool will be created and everything is fine (besides the ugly hack). I have also heard of some customers that set the timeout on the COM+ objects still encountering the loader-lock. Loader-locks are tricky bugs to track down and with each test case we get closer and closer to closing all the gaps, but obviously something changed in both Windows 2003 and Delphi 2005 to cause this unfortunate problem. If you think you have some information please drop me a comment on this blog or email me at codegearalias@gmail.com with the word "blog" anywhere in the message
Posted by Chris Bensen at 8:00 AM 2 comments
Tuesday, January 16, 2007
.NET and COM: The Complete Interoperability Guide: Book Review
.NET and COM: The Complete Interoperability Guide
On the cover Don Box is quoted saying “This is the last book that should be written about COM programming. There is nothing left to say.” I think this is a great book and a must have for anyone doing any .NET and COM Interop, I most certainly don’t think this is the last book that will be or could be written. It is a bold statement. This book provides in depth understanding into the strange world we find ourselves in with COM and .NET, and has way too many pages to just read from cover to cover. Typically I use this book as a reference book. I tried to read it but I personally find reading vacuum manuals less dry. That being said I highly recommend this book to anyone jumping in with two feed into COM Interop. I still reference this book today, but when I first started doing COM Interop it sat on my desk next to my keyboard waiting to be opened every few minutes.
Posted by Chris Bensen at 8:00 AM 0 comments
Monday, January 15, 2007
The Life of Mammals Nature Documentary
Recently I watched the four disk DVD series The Life of Mammals and I was very impressed by it’s quality, presentation and material. This has got to be the best nature documentary every produced. I it is that good. If you haven't seen it I highly recommend it you rent it from NetFlix or buy it. The cinema photography is simply stunning and the moments they capture of the animals is amazing.
Posted by Chris Bensen at 2:11 PM 0 comments
Completed My Delphi Blog Move
I just completed the move of the relevant posts from my CodeGear blog to here. This will now allow me to produce more content on a regular basis.
Posted by Chris Bensen at 2:02 PM 4 comments
Side-by-Side COM Registration Part I
Previously I posted a little bit about Side-By-Side but I have discovered a lot since then. Here is my attempt to share what I’ve discovered with an easy to read easy to understand post step by step tutorial of Side-By-Side. In Part II of the series I will walk you through an example application that will use COM and never touch your registry! How exciting is that?
If you don’t know what Side-by-side, it is a feature in Windows XP, 2003 and Vista (Windows 2000 does not support Side-By-Side) that allows an application to remove their dependency on the windows registry for COM registration and tools such as regasm.exe and regsvr32.exe/tregsvr.exe. This means that Xcopy deployment is now possible while using COM and COM Interop from your application. This is very exciting for those of us who use COM. Those who don’t use COM, well don’t start using it but you could still read this and get a chuckle out of what the rest of use have to deal with. Hopefully I can save you all some time because this stuff ain’t easy.
So here are the 10 easy steps to using Side-By-Side:
1. Start Project1.exe and the loader looks for Project1.exe.manifest
2. The application .manifest file starts with a header:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0";>
Next you specify the name of the module, a few other default things, and a version number that must match the EXE. If there is no version info then the default is 1.0.0.0. Example:
<assemblyIdentity
name="Project1.exe"
version="1.0.0.0"
type="win32"
processorArchitecture="x86"
/>
3. If you reference other modules that are Assemblies then they must have a section with the name of the module, some default things, a version number that must match the DLL and an optional publicKeyToken. Example:
<dependency>
<dependentAssembly>
<assemblyIdentity
name="Test.Server"
version="1.2.3.4"
type="win32"
publicKeyToken="b03a7f5905842b5a"
/>
</dependentAssembly>
</dependency>
Note that if you are delay signing your assemblies you cannot use Side-By-Side. To get the public key string type sn –T Test.Server.dll.
4. If you reference other modules that are COM Servers there must be a section with the DLL name, and GUIDs of all the CoClasses that CoCreateInstance is called with.
<file name="Server.dll">
<comClass clsid="{C7D9C6F6-3D2C-11B2-BBAC-00B05FB17624}"/>
</file>
5. The application manifest then ends with </assembly>
6. Each Assembly that is referenced needs a manifest file and a Win32 manifest resource. The .manifest file is named the assembly file name with the .manifest extension. For Example: Test.Server.dll’s manifest file is named Test.Server.manifest. This is a little confusing since the application manifest includes the .exe extension.
At this point I need to make a special note that if you are running on Windows XP 64-bit or Windows 2003 (and I presume you can include Vista in this list), then you can either have the resource or the .manifest file but both are not required. Windows XP 32-bit on the other hand requires only the manifest resource differing from Windows XP 64-bit. It makes one wonder why the Windows XP 64-bit and 32-bit don’t have the same loader besides the 64-bit part. So if you want your application to work on the lowest common denominator, in this case Windows XP 32-bit, then both the manifest file and the manifest resource are required.
7. The assembly manifest starts with a header:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0";>
Next you specify the name of the module, some default things, a version number that must match the DLL and an optional publicKeyToken. Example:
<assemblyIdentity
name="Test.Server"
version="1.2.3.4"
type="win32"
publicKeyToken="a91a7c5705831a4f"
/>
Since this Assembly will be used through COM Interop it needs a section to describe the type library:
<file name="Test.Server.tlb">
<typelib
tlbid="{9AE5A349-7CDD-4B99-ABAE-8F4646E194B6}"
version="1.0"
helpdir=""
flags="hasdiskimage"/>
</file>
At this point I want to mention that I highly recommend reading my previous post on Good COM Interop Practices.
8. For each .NET class that you create through COM Interop with CoCreateInstance there must be a section with a namespace and name, GUID, .NET Version and threading model.
<clrClass
name="Test.Server.TFoo"
clsid="{97109D87-BC72-4285-9FF6-C22BCF58B361}"
threadingModel="Both"
runtimeVersion="v2.0.50727"
/>
9. To generate the manifest resource you need a .rc file containing:
#include <winuser.h>
RCDATA RT_MANIFEST "Test.Server.manifest"
You can then run brcc32.exe Test.Server.rc to generate Test.Server.res. To link in the resource with Delphi add {$R Test.Server.res} to the .dpk, with C# and J# use the /win32res option, and ilasm.exe use the /RESOURCE option.
10. The assembly manifest then ends with </assembly>
Posted by Chris Bensen at 8:00 AM 0 comments
Thursday, January 11, 2007
Using PInvoke from Delphi for .NET
Recently I was asked how to call a DLL exporting methods that have a char* parameter from Delphi for .NET. It's really easy but getting all the details right can be a pain. So here is a really simple example so I can just say "go to my blog" when asked again :)
Say you have two methods in a DLL:
library StringLibrary;
uses SysUtils, Classes, Dialogs;
{$R *.res}
procedure PassInPChar(S: PChar); stdcall;
begin
ShowMessage(S);
end;
procedure ReturnPChar(S: PChar); stdcall;
begin
ShowMessage(S);
StrCopy(S, 'Hello Delphi');
end;
exports
PassInPChar, ReturnPChar;
begin
end.
Don't forget stdcall. On the Delphi for .NET side define the exported function prototypes as:
[DllImport('StringLibrary.dll')]
procedure PassInPChar(S: string); external;
[DllImport('StringLibrary.dll')]
procedure ReturnPChar(S: StringBuilder); external;
Notice that I used StringBuilder in the second method. This is because the callee is going to modify the string. Now to invoke the methods do something like the following:
procedure TForm3.Button1Click(Sender: TObject);
begin
PassInPChar(Edit1.Text);
end;
procedure TForm3.Button2Click(Sender: TObject);
var
S: StringBuilder;
begin
S := StringBuilder.Create(Edit2.Text, 256);
ReturnPChar(S);
Edit3.Text := S.ToString;
end;
Posted by Chris Bensen at 4:59 PM 0 comments
Labels: Delphi
Fun with V-Tables and COM
If you’ve ever imported a Visual Basic (VB) ActiveX control with Delphi chances are you’ve called a method and gotten an AV. Fortunately this long standing bug having to do with sparse v-tables has been fixed in Delphi 2005 Update 2. But it turns out that VB isn’t the only language that generates sparse v-tables in type libraries. C# or any .NET language can do this rather easily.
For those of you who watch Star Trek instead of studying assembly you might be wondering what a v-table is. A v-table is an array of function pointers where each method is a 4 byte offset from the previous method. A sparse v-table has spaces in that list of offsets. IUnknown and IDispatch are the foundation of COM, and have the following v-table offsets.
Method Name V-Table Offset
IUnknown.QueryInterface 0
IUnknown.AddRef 4
IUnknown.Release 8
IDispatch.GetTypeInfoCount 12
IDispatch.GetTypeInfo 16
IDispatch.GetIDsOfNames 20
IDispatch.Invoke 24
These are defined in Delphi as:
IInterface = interface
['{00000000-0000-0000-C000-000000000046}']
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
IDispatch = interface(IUnknown)
['{00020400-0000-0000-C000-000000000046}']
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult; stdcall;
end;
Not that much fun, but I’m getting to the fun part so keep reading. Here is a C# interface with a sparse v-table:
[ComVisible(true)]
[GuidAttribute("7F328E68-C0FA-482D-8700-5D605DE9E4B5")]
public interface IFoo
{
void A();
void B();
[ComVisible(false)]
void C();
string D(string S);
[ComVisible(false)]
void E();
}
Compile this and run tlbexp.exe to generate a type library then view this interface with a type library viewer that shows the TFuncDesc.oVft such as Reflection.exe and you will see something like this:
Method Name V-Table Offset
IFoo.A 28
IFoo.B 32
IFoo.D 40
See the space? It’s right there between the 32 and 40. I told you this would be fun! Remember there was a method called C? It’s gone, but not really since the slot for method C is still there. When importing this with the version of tlibimp.exe in Delphi 2005 Update 2 a Pascal interface will be defined as:
IFoo = interface(IDispatch)
['{7F328E68-C0FA-482D-8700-5D605DE9E4B5}']
procedure A; safecall;
procedure B; safecall;
procedure GhostMethod_IFoo_36_1; safecall;
function D(const S: WideString): WideString; safecall;
end;
Notice the method GhostMethod_IFoo_36_1 is simply a space holder for method C. But what happened to Method E? Ah, this is the tricky part and is best explained with more source:
IGoo = interface(IDispatch)
['{A9EC35FF-EA31-4B94-ADDD-D9E64C518862}']
procedure F; safecall;
procedure G; safecall;
end;
Method Name V-Table Offset
IGoo.F 28
IGoo.G 32
Since the interface was created by .NET the parent interface is IDispatch and there is nothing to do, but if this interface was created with VB there’d be a few more things going on when generating the v-table for a child interface. Let’s say you created a child interface in VB from IFoo (I’m going to write this in C# because I can):
[ComVisible(true)]
[GuidAttribute("A9EC35FF-EA31-4B94-ADDD-D9E64C518862")]
public interface IGoo : IFoo
{
void E();
void F();
}
Method Name V-Table Offset
IGoo.F 48
IGoo.G 52
And after importing the type library with tlibimp.exe, your Pascal interface for IGoo would be:
IGoo = interface(IFoo)
['{A9EC35FF-EA31-4B94-ADDD-D9E64C518862}']
procedure GhostMethod_IFoo_44_1; safecall;
procedure F; safecall;
procedure G; safecall;
end;
Here you see that the method GhostMethod_IFoo_44_1 is a place holder in the v-table for method E.
All of this gets really interesting when the interface in the type library implement IUnknown or IDispatch (take a look at the type library for mscorlib and do a search for GetIDsOfNames, it'll surprise you how many times it shows up).
So the moral of the story is if you get an AV when calling a COM object, the interface may not be defined correctly so do some investigation with what I described here.
Posted by Chris Bensen at 4:58 PM 0 comments
Good COM Interop Practices
I'm going to explain good practices you should follow to use a .NET
object from COM. I do want to warn you that all the classes and
interfaces in an Assembly are by default visible to COM. This means
that if you do use an Assembly that does not follow the steps below
there is potential for problems because GUIDs are auto generated by
the .NET runtime based on the signature of the type.
1. To access the attributes you are going to use add the following to
the files you are modifying:
uses System.Runtime.InteropServices;
2. If your assembly is not accessed from COM just add the
ComVisible(False) attribute to be safe. The reason for making all
Assemblies not visible to COM by default is that it is much easier to
hide the entire Assembly and then selectively make individual classes
and interfaces visible. If you register an Assembly with all of its
contents visible then each of those classes and interfaces will
clutter the registry and this isn’t always pretty when you go
to unregister - more on this later. Add the following attributes to
your AssemblyInfo file:
[assembly: ComVisible(False)]
[assembly: GuidAttribute('YourGuid')]
[assembly: TypeLibVersion(X, Y)]
3. To make an object available to COM it must implement an interface, be made visible to COM and implement a default constructor or the CoClass won't have a CanCreate flag.. The interface will become a COM interface and
the object will become a CoClass. On the interface put the following
attributes:
[ComVisible(True)]
[GuidAttribute('YourGuid')]
4. When generating a type library from an Assembly a .NET object creates
two entries in the type library. For example the type library
generated from Assembly Flubber.dll containing object Foo will
contain the CoClass Foo and the default interface for Foo called
_Foo. This is a perfectly valid interface but there are no methods on
it. You can pass it back to .NET and it’ll know what to do with
it. You can even cast it to IDispatch and call Invoke on it but that
isn’t much fun. To not generate the default interface put the
following attributes on the objects you want visible to COM:
[ComVisible(True)]
[GuidAttribute('YourGuid')
[ClassInterface(ClassInterfaceType.None)]
5. If you are using Delphi 2005 you can import the COM object by using
the Import Component Wizard available on the Component menu. This
will display all the Assemblies registered in the GAC and you can
browse to any other Assembly on your system. When clicking the Finish
button the following steps are done for you that users of previous
versions of Delphi will need to follow:
regasm.exe /tlb YourAssembly.dll
tlibimp.exe YourAssembly.dll
It is important to register the type library of an Assembly so you get
type library marshalling. You should only care about this if you use
threads.
6. It is important to unregister your Assembly if you are making
changes. To unregister the assembly with Delphi 2005 you can use
tregsvr.exe:
tregsvr.exe -u YourAssembly.dll
but if you don't have Delphi 2005 or later you will need to type:
regasm.exe /u /tlb YourAssembly.dll
Posted by Chris Bensen at 1:33 PM 0 comments
Delphi 2006 Type Library Editor Optimization of the Uses Tab
New to Delphi 2006 is an optimization in the Type Library Editor to remove references to any Type Library who's types aren't being used. So if you just add a reference to say Apple QuickTime Control, Version 2.0, C:\Program Files\QuickTime\QTPlugin.ocx, and don't create something in your type library that uses IQTActiveXPlugin for example, then the QuickTime type library will be removed from the references when you save. Dan Miser blogged about this just last week but I thought I'd try to get it out to more people.
Posted by Chris Bensen at 8:00 AM 0 comments
Delphi/C++Builder 2006 Import ActiveX Control Update
A bug was recently found when importing the Microsoft Message Queue 3.0 Object Library type library contained in the DLL mqoa.dll. When calling ITypeInfo.GetDocumentation we would fail with the error TYPE_E_ELEMENTNOTFOUND. It turns out that even though the documentation states that ITypeInfo.GetDocumentation can be called with NULL as the last four parameters that in some cases methods that don't contain a help string such as the case with the method IMSMQManagement.TransactionalStatus, NULL is required. Here is the IDL of the interface as seen from OLE View:
interface IMSMQManagement : IDispatch
{
[id(00000000), helpstring("Method to initialize the MSMQManagement
object.")]
HRESULT Init(
[in, optional] VARIANT* Machine,
[in, optional] VARIANT* PathName,
[in, optional] VARIANT* FormatName);
[id(0x00000001), propget, helpstring("Property that identifies the queue.
The format name of a queue is generated by MSMQ when the queue is created, or
generated later by the application.")]
HRESULT FormatName([out, retval] BSTR* pbstrFormatName);
[id(0x00000002), propget, helpstring("Property returning the name of the
relevant machine.")]
HRESULT Machine([out, retval] BSTR* pbstrMachine);
[id(0x00000003), propget, helpstring("Property returning the number of
messages in a queue.")]
HRESULT MessageCount([out, retval] long* plMessageCount);
[id(0x00000004), propget, helpstring("Property indicating that the
queue is known to be foreign, that it is known not to be foreign,
or that its status in this regard is unknown.")]
HRESULT ForeignStatus([out, retval] long* plForeignStatus);
[id(0x00000005), propget, helpstring("Property indicating the type of a
queue.")]
HRESULT QueueType([out, retval] long* plQueueType);
[id(0x00000006), propget, helpstring("Property indicating whether the
queue resides on the local machine.")]
HRESULT IsLocal([out, retval] VARIANT_BOOL* pfIsLocal);
[id(0x00000007), propget]
HRESULT TransactionalStatus([out, retval] long* plTransactionalStatus);
[id(0x00000008), propget, helpstring("Property indicating the amount of
storage used for the queue.")]
HRESULT BytesInQueue([out, retval] VARIANT* pvBytesInQueue);
};
So now the code has been changed from:
OleCheck(FParent.FTypeInfo.GetDocumentation(FuncDesc.memid, @Name, @HelpString,
@FHelpContext, nil));
To the slightly more complicated:
Check := FParent.FTypeInfo.GetDocumentation(FuncDesc.memid, @Name, @HelpString,
@FHelpContext, nil);
if Check = TYPE_E_ELEMENTNOTFOUND then
begin
OleCheck(FParent.FTypeInfo.GetDocumentation(FuncDesc.memid, @Name, nil,
nil, nil));
end
else
OleCheck(Check);
If you are getting an "element not found" error when using the Microsoft Message Queue 3.0 Object Library type library then you can download an unofficial update of tlib100.bpl here.
Posted by Chris Bensen at 8:00 AM 4 comments