Tuesday, October 30, 2007

User Question: Native and Managed Interop

Since I get a lot of fan mail I figured it was time to start answering it.

"We've tried unmanaged exports before, but haven't had much luck passing or returning nontrivial types. I'd love to see some examples of this. For example, is it safe to call a .NET function that returns a string? Will memory management work correctly? Do I just need to declare the Win32 code to expect a WideString return, or is it more exotic than that?

I'd also be very interested to know if it's possible to pass interfaces from Win32 to a .NET function without using COM."

Here is an example of doing everything you asked; returning nontrivial types between managed and native code such as strings and interfaces without using any COM. We can talk about it in more detail because there are more things you need to know, but this will be a fairly long post so lets just look at the code. Note: I'm human just like all of you so there may be mistakes in the code especially since I whipped it up in under 10 minutes.

First create a Delphi for .NET project called Managed.dll. Here is the code that goes in Managed.dpr:


library Managed;

{$UNSAFECODE ON}

uses
System.Runtime.InteropServices,
ManagedUnit in 'ManagedUnit.pas';

[assembly: Guid('03664AA5-3EBF-4429-AFF9-584499520C68')]
[assembly: ComVisible(False)]

exports
Nothing, TakeString, ReturnString, TakeInterface, ReturnInterface;

end.


Then add a unit to Managed.dll and save it as ManagedUnit.pas. Here is the code that goes in ManagedUnit.pas:


unit ManagedUnit;

interface

uses
System.Runtime.InteropServices;

type
[ComVisible(True)]
ITakeInterface = interface
['{0E86E949-A124-451F-B42D-CA55B5AE7A14}']
function ReturnString: string;
end;

[ComVisible(True)]
IReturnInterface = interface
['{531C5CF4-5C2F-41D8-A5AF-C24D7868D282}']
function ReturnString: string;
end;

procedure Nothing;
procedure TakeString(Value: string);
function ReturnString: string;
procedure TakeInterface(const Value: ITakeInterface);
procedure ReturnInterface(var Value: IReturnInterface);

implementation

uses
Classes, SysUtils, ActiveX, Windows;

type
TDispatchObject = class(TInterfacedObject, IDispatch)
{ IDispatch }
[PreserveSig]
function GetTypeInfoCount(out Count: Integer): HResult;
[PreserveSig]
function GetTypeInfo(Index, LocaleID: Integer;
[MarshalAs(UnmanagedType.IUnknown)] out TypeInfo): HResult;
[PreserveSig]
function GetIDsOfNames([MarshalAs(UnmanagedType.LPStruct)] IID: TGUID;
[in] var Names: string; NameCount, LocaleID: Integer; out DispIDs: Integer): HResult;
[PreserveSig]
function Invoke(DispID: Integer; [MarshalAs(UnmanagedType.LPStruct)] IID: TGUID;
LocaleID: Integer; Flags: Word; var Params: TDispParams;
out VarResult; out ExcepInfo: TExcepInfo; ArgErr: IntPtr): HResult;
end;

TReturnInterface = class(TDispatchObject, IReturnInterface)
{ IReturnInterface }
function ReturnString: string;
end;

procedure Nothing;
begin
WriteLn('nothing');
end;

procedure TakeString(Value: string);
begin
WriteLn(Value);
end;

function ReturnString: string;
begin
Result := 'hello';
end;

procedure TakeInterface(const Value: ITakeInterface);
begin
WriteLn(Value.ReturnString);
end;

procedure ReturnInterface(var Value: IReturnInterface);
begin
Value := TReturnInterface.Create;
end;

{ TDispatchObject }

function TDispatchObject.GetIDsOfNames(IID: TGUID; var Names: string;
NameCount, LocaleID: Integer; out DispIDs: Integer): HResult;
begin
end;

function TDispatchObject.GetTypeInfo(Index, LocaleID: Integer;
out TypeInfo): HResult;
begin
end;

function TDispatchObject.GetTypeInfoCount(out Count: Integer): HResult;
begin
end;

function TDispatchObject.Invoke(DispID: Integer; IID: TGUID; LocaleID: Integer;
Flags: Word; var Params: TDispParams; out VarResult;
out ExcepInfo: TExcepInfo; ArgErr: IntPtr): HResult;
begin
end;

{ TReturnInterface }

function TReturnInterface.ReturnString: string;
begin
Result := 'hello';
end;

end.


Now create a Delphi for Win32 project called Native.dll. Add a unit to Native.dll and save it as NativeUnit.pas. Here is the code that goes in NativeUnit.pas:


unit NativeUnit;

interface

uses
Managed_TLB;

procedure Run;

implementation

uses
SysUtils, Math, ActiveX, Windows;

type
TNothingProc = procedure; stdcall;
TTakeStringProc = procedure(Value: PChar); stdcall;
TReturnStringProc = function: PChar; stdcall;
TTakeInterfaceProc = procedure(const Value: ITakeInterface); stdcall;
TReturnInterfaceProc = procedure(var Value: IReturnInterface); stdcall;

TTakeInterface = class(TInterfacedObject, IDispatch, ITakeInterface)
{ ITakeInterface }
function ReturnString: WideString; safecall;

{ IDispatch }
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;

var
SavedCW: Word;
Handle: THandle;
InternalNothing: TNothingProc;
InternalTakeString: TTakeStringProc;
InternalReturnString: TReturnStringProc;
InternalTakeInterface: TTakeInterfaceProc;
InternalReturnInterface: TReturnInterfaceProc;

procedure Nothing;
begin
SavedCW := Get8087CW;
Set8087CW($027f);
InternalNothing;
Set8087CW(SavedCW);
end;

procedure TakeString(Value: PChar);
begin
SavedCW := Get8087CW;
Set8087CW($027f);
InternalTakeString(Value);
Set8087CW(SavedCW);
end;

function ReturnString: PChar;
begin
SavedCW := Get8087CW;
Set8087CW($027f);
Result := InternalReturnString;
Set8087CW(SavedCW);
end;

procedure TakeInterface(const Value: ITakeInterface);
begin
SavedCW := Get8087CW;
Set8087CW($027f);
InternalTakeInterface(Value);
Set8087CW(SavedCW);
end;

procedure ReturnInterface(var Value: IReturnInterface);
begin
SavedCW := Get8087CW;
Set8087CW($027f);
InternalReturnInterface(Value);
Set8087CW(SavedCW);
end;

procedure Initialize;
begin
Handle := LoadLibrary(PChar('Managed.dll'));
@InternalNothing := GetProcAddress(Handle, 'Nothing');
@InternalTakeString := GetProcAddress(Handle, 'TakeString');
@InternalReturnString := GetProcAddress(Handle, 'ReturnString');
@InternalTakeInterface := GetProcAddress(Handle, 'TakeInterface');
@InternalReturnInterface := GetProcAddress(Handle, 'ReturnInterface');
end;

procedure Uninitialize;
begin
FreeLibrary(Handle);
end;

procedure Run;
var
R: IReturnInterface;
T: ITakeInterface;
begin
Initialize;

try
// Simple managed function call.
Nothing;

// Pass a string from native to managed.
TakeString('hello');

// Pass a string from managed to native.
WriteLn(ReturnString);

// Pass an object implementing an interface from native to managed.
T := TTakeInterface.Create;
TakeInterface(T);

// Pass an object implementing an interface from managed to native.
ReturnInterface(R);

if R <> nil then
WriteLn(R.ReturnString);
finally
Uninitialize;
end;
end;

{ TTakeInterface }

function TTakeInterface.GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
end;

function TTakeInterface.GetTypeInfo(Index, LocaleID: Integer;
out TypeInfo): HResult;
begin
end;

function TTakeInterface.GetTypeInfoCount(out Count: Integer): HResult;
begin
end;

function TTakeInterface.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
begin
end;

function TTakeInterface.ReturnString: WideString;
begin
Result := 'hello';
end;

end.


At this point you will need to have built Managed.dll. Run "tlibimp -P+ Managed.dll" to generate Managed_TLB.pas. Add Managed_TLB.pas to Native.dll so now Native.dpr should look like this:


program Native;

{$APPTYPE CONSOLE}

uses
Managed_TLB in 'Managed_TLB.pas',
NativeUnit in 'NativeUnit.pas';

begin
Run;
end.


Last step is to run native.exe where you should see the following output:


nothing
hello
hello
hello
hello

Thursday, October 25, 2007

Delphi Scoped Enums

Here is an example of using scoped enums. Scoped enums don't get added to the global namespace.

type
  TFoo = (A, B, Foo);
  {$SCOPEDENUMS ON}
  TBar = (A, B, Bar);
  {$SCOPEDENUMS OFF}

begin
  WriteLn(Integer(Foo)); 
  WriteLn(Integer(A)); // TFoo.A
  WriteLn(Integer(TBar.B));
  WriteLn(Integer(TBar.Bar));
  WriteLn(Integer(Bar)); // Error
end;

Friday, October 19, 2007

New Danny Thorpe Blog

Danny Thorpe's blog has has been jumping around the valley over the last couple of years and he has yet another blog that just went live. I believe this one will be his blog for some time given that his name is in the doman.

Wednesday, October 17, 2007

Delphi and C++Builder IDE Memory Usage Tip

Not only can you build .NET applications if you have the Delphi for .NET personality but the IDE uses .NET internally. If you have limited memory the .NET garbage collector might not collect the memory as often as you would like. You can force the .NET garbage collector to happen more frequently. Use Regedit and open the borland key and add a the "Globals" key if it doesn't already exist under BDS\x.0 (Example: BDS\5.0\Globals). Add a string value as follows:

CollectGarbageInterval = "300000"

This sets a timer to force garbage collection every 5 minutes (5 * 60 * 1000 = 300000).

Wednesday, October 3, 2007

Poll: How long have you used Delphi?

I haven't had much time lately to blog, but I figured I'd put a poll asking how long people have used Delphi. This poll will be up for the entire month of October so we can get as many people as possible to respond.

Update: If you have used Delphi for more than 5 years, and clearly a lot of you have, and you feel 5 years isn't even close to the amount of time you've used Delphi, then post a comment we'd all love to read it!

Tuesday, October 2, 2007

October Photo of the Month


Copyright © 2007 Chris Bensen. All rights reserved.


The new photo for this October is a photo I found in my archives and have spent the better part of a day stitching together from 8 photos. I took this photo on a trip to Yellowstone National Park in February 2006. Yes, that is the dead of winter. I snow shoed and snow mobile all around the wonderful outdoor amusement park for the better part of two weeks and enjoyed every minute of the -40 below storms to 20 above crystal clear sky. This photo was taken at sunset while a really big storm came in and we had over an hour to get back to the west entrance. Try snowmobiling through a herd of Bison that have made camp on the middle of the road in -30 below temperatures trying to not bump into the big furry beasts. It was a blast!

You can view more of my Yellowstone photos by clicking here.

Monday, October 1, 2007

Bad Variable Names

A few months back I posted about how to choose variable names. Since then I've had up on my white board, based on some real world variable names we've encountered, some sample bad variable names. It then morphed into visitors trying trying to come up with the worst variable name ever. The current rendition is:


if not IsNotCanNotUnDisable then
Result := not UnTrue;


What bad variable names have you encountered?

Wednesday, September 26, 2007

NASA Picture of the Day

I like to have a look at the NASA picture of the day every once in a while. I was just catching up and ran across this one:

http://antwrp.gsfc.nasa.gov/apod/ap070819.html

Friday, September 21, 2007

Amazon Unbox

Since I don't have TV service (yes I do have a TV but it is only connected to a DVD player), if I have time and feel like it I've tried some videos from online services such as iTunes, NetFlix and now Amazon Unbox. The only reason for testing out Amazon Unbox was the new Bionic Woman pilot was free so I figured why not. Oh NBC why did you leave iTunes. Amazon Unbox had to reboot my computer 3 times each time not restarting the install, the UI is poor and the video quality is lacking. For half the show there was a horizontal line through the video and the sound was not in sync with the video. Note that I did run this over a VMWare VM running Windows XP on my MacPro. iTunes, NetFlix and many other applications and videos as a comparison point have had no such problems so I doubt it's the VM. Anyway, it was a mildly entertaining show that I won't be watching so here's a link to check it out for yourself:

Thursday, September 20, 2007

Paint.NET

I wanted to add the application to the list of Useful Applications As Suggested by Blog Readers. I recently started using Paint.NET for any graphics work I need to do at work since it is free and Photoshop is overkill. It does a lot of the basics, does them well and is way better than Microsoft Paint.

Thursday, September 13, 2007

Delphi\C++ 2007 Bug Fixes

tregsvr now works on an Assembly again.

Import Component, browse GAC Assemblies now actually shows Assemblies! Recently I ran across Delphi users having problems with the Import Component Wizard thinking the wizard doesn't show any assemblies that are registered in the GAC. The truth of the matter is the Import Component Wizard displays assemblies registered in the GAC that have a CodeBase property, but the bug fix changed all this so any GAC assembly registered for 32-bit will show up (these are the Assemblies that live in the GAC_32 directory).

C++ Remote Data Module doesn't register each time it is run which caused problems for Vista or any limited user account. Note that the registration code (/regserver) caused the exception, but for some reason the unregistration code (/unregserver) does not throw an exception.

There is a bug where if you are working on a COM project all the units in your project would be opened taking considerable time and memory. This has now been fixed.

Tuesday, September 4, 2007

September Photo of the Month


Copyright © 2007 Chris Bensen. All rights reserved.


It's that time again, the first of the month and time for a new photo. This months photo I took over the Labor Day weekend just days ago in Yosemite National Park. Click on the photo to see it larger. The photo is of a Western Screech-Owl that I tracked down over and managed to get really close to snap this great photo. This is a case where the latest technology allowed me to take a photo that wouldn't have been possible just a few years ago. The photo is taken with a 400mm hand held at f5.6, 1/50th, ISO 800 with image stabilization. What a surprise it was to get home last night to find this one sharp photo.