How can I use OpenCV with Unicode versions of Delphi? - delphi

I am editing a open source project called Opencv for Delphi which compiles fine with Delphi 6 , Delphi 2009 and Delphi xe2.
I just removed incompatible (eg :Application.MainFormOnTaskbar := True;) for the Delphi 6 compilation.
On the run time d6 app works fine without errors, but the rest are compiling well but with run time error when calling cvopencv.dll.
The original project is a Spanish project compiled with Delphi 2007 which works fine.
In the d6 exe i can't read any thing except ??????? but others showing in spanish which means it is a UNICODE problem, when calling cvopencv.dllthe error happens.
What can i do to make this project work with Delphi 2009 or higher (still d2009 compiling well)
========================= After David Heffemans Answer =====================
procedure TForm1.Button2Click(Sender: TObject);
var
file1 : PAnsiChar; // "haarcascade_frontalface_alt.xml";
file2 : PAnsiChar; //"haarcascade_eye.xml";
file3 : PAnsiChar; //"haarcascade_upperbody.xml";
SourceFileName : AnsiString;
StorageType : Integer;
ImSize : CvSize;
begin
memo1.Lines.Clear;
GetMem(Storage, SizeOf(CvMemStorage));
SourceFileName:=Edit1.Text;
StorageType:=0;
storage:=nil;
storage :=cvCreateMemStorage(storageType);
file1 := PAnsiChar(ExtractFilePath(Application.ExeName)+'haarcascade_frontalface_alt.xml');
file2 := PAnsiChar(ExtractFilePath(Application.ExeName)+'haarcascade_eye.xml');
file3 := PAnsiChar(ExtractFilePath(Application.ExeName)+'haarcascade_upperbody.xml');
cascade_f := cvLoad(file1, nil, nil, nil);
cascade_e := cvLoad(file2, nil, nil, nil);
cascade_ub := cvLoad(file3, nil, nil, nil);
if cascade_f=nil then ShowMessage('Íå ìîãó çàãðóçèòü êàñêàä Face');
if cascade_e=nil then ShowMessage('Íå ìîãó çàãðóçèòü êàñêàä Eye');
if cascade_ub=nil then ShowMessage('Íå ìîãó çàãðóçèòü êàñêàä UpperBody');
img := cvLoadImage(PAnsiChar(AnsiString(SourceFileName)), 1);
cvNamedWindow((PAnsiChar(AnsiString(Edit1.Text))), 1);
detectEyes(img);
cvShowImage(PAnsiChar(AnsiString(SourceFileName)), img);
end;
end.

The OpenCV library you refer to says this at the top of the header:
Borland Delphi 4,5,6,7 API for Intel Open Source Computer Vision Library
This code presents a Delphi wrapper around the OpenCV DLLs.
For these older Delphi versions, Char, PChar and string map to AnsiChar, PAnsiChar and AnsiString respectively. This matches the OpenCV DLLs which are pure ANSI. There are no Unicode APIs in OpenCV (at least so far as I can discern).
If you wish to use this unit from Delphi 2009 and up you need to recognise that in Delphi 2009, Unicode support was added. This changed the mapping of Char, PChar and string map to WideChar, PWideChar and UnicodeString respectively. This is the cause of the incompatibility you have encountered because the DLL behind this unit is still processing single byte ANSI text.
Probably the most sensible approach with this unit is to change it to use AnsiChar, PAnsiChar and AnsiString. You can do this with a global search and replace in the unit.
This will make the OpenCV.pas unit match the DLLs again. You now need to be careful how you interface with this OpenCV.pas unit. The functions that receive PAnsiChar parameters need special treatment.
For example consider the following example:
Function cvLoadImage( const filename : PChar;
iscolor : integer=1) : PIplImage; cdecl;
This is the original declaration. After your search and replace it will read like this:
Function cvLoadImage( const filename : PAnsiChar;
iscolor : integer=1) : PIplImage; cdecl;
When you come to call it you need to ensure you pass a proper PAnsiChar. Like this:
var
FileName: string;
...
image := cvLoadImage(PAnsiChar(AnsiString(FileName)));
You must convert from string (which maps to UnicodeString) to AnsiString and only then can you cast to PAnsiChar. Do not write PAnsiChar(FileName). This will not convert the Unicode string to ANSI and will fail.
Fortunately for you the OpenCV library has a relatively small number of APIs that receive text.

See the project - Delphi-OpenCV on github (OpenCV version - 2.4.6, environment - Delphi XE2-XE4). Translation of OpenCV library header files in Delphi: https://github.com/Laex/Delphi-OpenCV

Related

Problems with Delphi 2009 + HidD_GetSerialNumberString

I am having problems getting HidD_GetSerialNumberString to simply give me the serialnumber of some devices.
On some devices it works fine, but on others all I get is "ÿÿÿ" as the serialnumber.
I am suspecting it has something to do with Delphi 2009 and it's unicode, but I simply cannot get my head around it.
Is there a solution to this?
Edit (declaration) :
type
THidD_GetSerialNumberString = function(HidDeviceObject: THandle; Buffer: PWideChar; BufferLength: Integer): LongBool; stdcall;
var
HidD_GetSerialNumberString: THidD_GetSerialNumberString;
//in the LoadHid-function
#HidD_GetSerialNumberString := GetModuleSymbolEx(HidLib, 'HidD_GetSerialNumberString', Result);
Usage :
var
test : PWideChar;
...
GetMem(test, 512);
if HidD_GetSerialNumberString(HidFileHandle, Test, 512) then
FSerialNumber := test; //FSerialNumber is WideString
FreeMem(test);
Your code is fine in all versions of Delphi. Although clearly it involves a UTF-16 to ANSI conversion when compiled by a pre-Unicode Delphi. However, since you use Delphi 2009 that is not an issue.
There are no problems regarding Unicode conversions. The HidD_GetSerialNumberString function fills out the buffer that you supply with a null-terminated UTF-16 string. And since you declared test to be exactly that, Delphi will generate code necessary to copy to a Delphi string variable.
Is it possible that HidD_GetSerialNumberString is returning false and so FSerialNumber is not initialized properly? Other than that, the only conclusions that one can make are that:
HidD_GetSerialNumberString is lying to you when it returns True.
The serial number returned by calling HidD_GetSerialNumberString really is ÿÿÿ.

Passing parameters from Delphi 5 to Delphi DLL XE

I have a Delphi 5 application in the application code calls a function in the DLL, passing integer and string parameters, this works well when the DLL is called in a static way, when I try to dynamically change does not work.
which is the correct way to pass parameters to function dynamically?
the code is as follows
main application
function Modulo_Pptos_Operacion(No_Orden : Integer; pathBD : string; PathBDConf : String) : Integer ; stdcall;
external 'LIB_Pptos_Oper.dll';
Modulo_Pptos_Operacion(DmDatos.OrdenesNO_Orden.AsInteger,
DmDatos.CiasPATHA.AsString, 'Alguna String');
DLL
Modulo_Pptos_Operacion function (No_Orden: Integer; PathDB: AnsiString; PathDBConfig: AnsiString): Integer; StdCall;
DYNAMIC CRASH
main application
type
TDLLPpto = function(No_Orden : Integer; PathDB : AnsiString; PathDBConfig : AnsiString) : Integer;
var
DLLHandle: THandle;
: TDLLPpto;
PROCEDURE CALL
DLLHandle := LoadLibrary('LIB_Pptos_Oper.dll');
DLLHandle <> 0 then
begin
#DLLPpto := GetProcAddress(DLLHandle, 'Modulo_Pptos_Operacion');
end;
;
which is the right way?
The problem is probably that you are mixing different runtimes and probably different heaps. Delphi strings are not valid interop types because their implementations vary from version to version.
In this case you can simply switch to using null-terminated strings, PAnsiChar.
In the case of dynamically loaded dll you omitted stdcall; calling convention directive in the declaration of TDLLPpto. Still it is advisable to use PAnsiChar type to pass strings across executable boundaries.
The layout of ansistring has changed with Delphi XE: now there is also a codepage field at negative offset and D5 does not have that. EG: strings from D5 and DXE are utterly incompatible. Thus you should use PAnsiChar or PWideChar in your interface, either zero terminated (Delphi strings are always zero terminated) of introduce an extra parameter with the length if the string might contain #$00 bytes.
Also: the different Delphi versions both have different memory managers. If a string is allocated by the main app and freed by the DLL (strings are reference counted) the pointer get's passed to the wrong memory manager which usually results in corrupted memory and thus nasty Access Violations etc.
Another solution is to use WideString; this is both in D5 en DXE equal to a COM BSTR stringtype and managed by the OS and not the Delphi memory manager. They are compatible. The only problem is: they are slow compared to the Delphi strings and are not ref counted.
In all: when using DLL interfaces, try to avoid string, use PAnsiChar or PWideChar, or WideString

Delphi - Store WideStrings inside a program

In the past I used INI-Files to store unicode text, but now I need to store unicode text in the executable. How can I achieve this?
I want to store these letters:
āčēūīšķļņž
If you want to save the Unicode INI files then you might try the following code. The files are saved in UTF8 encoding.
Also you might take a look at this Unicode library where you can find a lot of helper functions.
uses IniFiles;
function WideStringToUTF8(const Value: WideString): AnsiString;
var
BufferLen: Integer;
begin
Result := '';
if Value <> '' then
begin
BufferLen := WideCharToMultiByte(CP_UTF8, 0, PWideChar(Value), -1, nil, 0, nil, nil);
SetLength(Result, BufferLen - 1);
if BufferLen > 1 then
WideCharToMultiByte(CP_UTF8, 0, PWideChar(Value), -1, PAnsiChar(Result), BufferLen - 1, nil, nil);
end;
end;
function UTF8ToWideString(const Value: AnsiString): WideString;
var
BufferLen: integer;
begin
Result := '';
if Value <> '' then
begin
BufferLen := MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(Value), -1, nil, 0);
SetLength(Result, BufferLen - 1);
if BufferLen > 1 then
MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(Value), -1, PWideChar(Result), BufferLen - 1);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
IniFile: TIniFile;
const
UnicodeValue = WideString(#$0101#$010D#$0113#$016B#$012B#$0161);
begin
IniFile := TIniFile.Create('C:\test.ini');
try
IniFile.WriteString('Section', 'Key', WideStringToUTF8(UnicodeValue));
IniFile.UpdateFile;
finally
IniFile.Free;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
IniFile: TIniFile;
UnicodeValue: WideString;
begin
IniFile := TIniFile.Create('C:\test.ini');
try
UnicodeValue := UTF8ToWideString(IniFile.ReadString('Section', 'Key', 'Default'));
MessageBoxW(Handle, PWideChar(UnicodeValue), 'Caption', 0);
finally
IniFile.Free;
end;
end;
with Delphi 2007 on 64-bit Windows 7 Enterprise SP 1
If you definitely need to use Delphi 7 there are some variants:
Store strings in resources linked to executable file.
Store strings in big memo or same thing, located on global data module or any other visual or non-visual component and access it by index. It's possible because strings in Delphi resources stored in XML-encoded form. E.g. your symbols example āčēūīšķļņž will be stored as āčēūīšķļņž
Store XML-encoded or Base64-encoded strings in string constants inside your code.
For string conversion you can use EncdDecd.pas , xdom.pas or some functions of System.pas like UTF8Encode/UTF8Decode.
To display and edit Unicode strings in Delphi forms you can use special set of Unicode controls like TNT Unicode Controls or subclass original Delphi controls and do some other workarounds by yourself, like described in this excerpt from comments in TntControls.pas (part of TNT Unicode Controls):
Windows NT provides support for native Unicode windows. To add
Unicode support to a
TWinControl descendant, override CreateWindowHandle() and call
CreateUnicodeHandle().
One major reason this works is because the VCL only uses the ANSI
version of
SendMessage() -- SendMessageA(). If you call SendMessageA() on a
UNICODE
window, Windows deals with the ANSI/UNICODE conversion
automatically. So
for example, if the VCL sends WM_SETTEXT to a window using
SendMessageA,
Windows actually expects a PAnsiChar even if the target window
is a UNICODE
window. So caling SendMessageA with PChars causes no problems.
A problem in the VCL has to do with the TControl.Perform() method.
Perform()
calls the window procedure directly and assumes an ANSI window.
This is a
problem if, for example, the VCL calls Perform(WM_SETTEXT, ...)
passing in a
PAnsiChar which eventually gets passed downto DefWindowProcW()
which expects a PWideChar.
This is the reason for SubClassUnicodeControl(). This procedure
will subclass the
Windows WndProc, and the TWinControl.WindowProc pointer. It will
determine if the
message came from Windows or if the WindowProc was called
directly. It will then
call SendMessageA() for Windows to perform proper conversion on
certain text messages.
Another problem has to do with TWinControl.DoKeyPress(). It is
called from the WM_CHAR
message. It casts the WideChar to an AnsiChar, and sends the
resulting character to
DefWindowProc. In order to avoid this, the DefWindowProc is
subclassed as well. WindowProc
will make a WM_CHAR message safe for ANSI handling code by
converting the char code to
#FF before passing it on. It stores the original WideChar in the
.Unused field of TWMChar.
The code #FF is converted back to the WideChar before passing onto
DefWindowProc.
Do
const MyString = WideString('Teksts latvie'#$0161'u valod'#$0101);
Simple, the idea is to find a non-visual component, which can store text and store your text there. Prefer that such component can also provide you an editor to edit the text in design time.
There is a component call FormResource which can do this. I use TUniScript. I believe there are other similar components. However, I did not find a usable component from the standard library.
The approach Widestring(#$65E5#$672C) does not work, because Delphi 7 just doesn't expect more than one byte for the #, so the outcome is by far not what you expect when going above 255 or $FF.
Another approach WideChar($65E5)+ WideChar($672C) can be used to store single Unicode codepoints in your source code when knowing that you need to have a Widestring at the start of the assignment (which can also be an empty literal) so the compiler understands which datatype you want:
const
// Compiler error "Imcompatible types"
WONT_COMPILE: WideChar($65E5)+ WideChar($672C);
// 日本
NIPPON: Widestring('')+ WideChar($65E5)+ WideChar($672C);
Looks cumbersome, but surely has your UTF-16 texts in Delphi 7.
Alternatively, store your constants in UTF-8, which is ASCII safe - that way you can use # easily. One advantage is, that it's a lot less cumbersome to write in your source code. One disadvantage is, that you can never use the constant directly, but have to convert it to UTF-16 first:
const
// UTF-8 of the two graphemes 日 and 本, needing 3 bytes each
NIPPON: #$E6#$97#$A5#$E6#$9C#$AC;
var
sUtf16: Widestring;
begin
// Internally these are 2 WORDs: $65E5 and $672C
sUtf16:= UTF8ToWideString( NIPPON );

How can I pass a Delphi string to a Prism DLL?

We try to pass a string from a native Delphi program to a Delphi Prism DLL.
We have no problem passing integers, but strings are mismatched in the DLL.
We saw Robert Love's code snippet in response to another question, but there is no code for the native Delphi program.
How can we pass strings from Delphi to a Delphi Prism DLL?
The best way would be to use WideString.
For several reasons.
It is Unicode and works before D2009
It's memory is managed in ole32.dll, so no dependency on either Delphi's memory manager or the CLR GC.
You do not have to directly deal with pointers
In Oxygene, you could write it like so:
type
Sample = static class
private
[UnmanagedExport]
method StringTest([MarshalAs(UnmanagedType.BStr)]input : String;
[MarshalAs(UnmanagedType.BStr)]out output : String);
end;
implementation
method Sample.StringTest(input : String; out output : String);
begin
output := input + "ä ~ î 暗";
end;
"MarshalAs" tells the CLR how to marshal strings back and forth. Without it, strings are passed as Ansi (PAnsiChar), which is probably NOT what you would want to do.
This is how to use it from Delphi:
procedure StringTest(const input : WideString; out output : WideString);
stdcall; external 'OxygeneLib';
var
input, output : WideString;
begin
input := 'A b c';
StringTest(input, output);
Writeln(output);
end.
Also, never ever use types, that are not clearly defined, for external interfaces.
You must not use PChar for DLL imports or exports. Because if you do, you will run into exceptions when you compile it with D7 or D2009 (depending on what the original dev system was)
Strings in Delphi Win32 are managed differently from strings in .Net, so you can not pass a .Net string to Delphi Win32 or vice versa.
To exchange strings values you'd better use PChar type which is supported by both compilers. That is the same way you send string values to Windows API functions.
Regards
P.S. I am NOT Robert ;-)

Delphi folder scanner - Unicode folder names

Yo.
I need to scan a directory and its sub-folders, I used FindFirst and FindNext procedures, but the TSearchRec's Name property is a string, thus unicode folder names (hebrew, arabic etc) are '?????' in the Name property.
I tried using TntComponent, with WideFindFirst, WideFindNext and TSearchRecW.
But I still get ?????? for folder names.
Flname:=WideExtractFileName(FileSpec);
validres := WideFindFirst(FileSpec+'\*', faDirectory, SearchRec);
AssignFile(LogFile, ResultFilePath);
while validres=0 do begin
if (SearchRec.Attr and faDirectory>0) and (SearchRec.Name[1]<>'.') then begin
{invalid entry Findnext returns}
Append(LogFile);
WriteLn(LogFile, FileSpec+'\'+LowerCase(SearchRec.Name));
CloseFile(LogFile);
DirScan(FileSpec+'\'+SearchRec.Name, ResultFilePath)
end;
validres:=WideFindNext(SearchRec);
end;
WideFindClose(SearchRec);
Delphi does support unicode in the compiler by using WideString.
But you'll face the following problems:
Delphi < 2009 does not support unicode in their VCL.
A lot of API mapping is done on the ANSI (OpenFileA for instance) variants of the API.
The delphi compiler will convert the WideStrings to a string a lot, so be very explicit about them.
It will work if you use the raw unicode windows api's.
So FindFirst uses the api FindFirstFile which delphi maps to the FindFirstFileA variant, and you'll need to directly call FindFirstW.
So you'll have 2 options.
Upgrade to Delphi 2009 and have a lot of unicode mapping done for you
Write your own unicode mapping functions
For the text file writing you might be able to use the GpTextFile or GpTextSteam by Primoz Gabrijelcic (aka gabr), they have unicode support.
Her is an example of opening a file with a unicode filename:
function OpenLongFileName(const ALongFileName: WideString; SharingMode: DWORD): THandle; overload;
begin
if CompareMem(#(WideCharToString(PWideChar(ALongFileName))[1]), #('\\'[1]), 2) then
{ Allready an UNC path }
Result := CreateFileW(PWideChar(ALongFileName), GENERIC_READ, SharingMode, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
else
Result := CreateFileW(PWideChar('\\?\' + ALongFileName), GENERIC_READ, SharingMode, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
end;
function CreateLongFileName(const ALongFileName: WideString; SharingMode: DWORD): THandle; overload;
begin
if CompareMem(#(WideCharToString(PWideChar(ALongFileName))[1]), #('\\'[1]), 2) then
{ Allready an UNC path }
Result := CreateFileW(PWideChar(ALongFileName), GENERIC_WRITE, SharingMode, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)
else
Result := CreateFileW(PWideChar('\\?\' + ALongFileName), GENERIC_WRITE, SharingMode, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
end;
I've used these functions because the ANSI api's have a path limit of 254 chars, the unicode have a limit of 2^16 chars if I'm not mistaken.
After you've got the handle to the file you can just call the regular ReadFile delphi api mapping, to read data from your file.
Delphi versions prior to 2009 have very limited unicode support. So if you really want unicode, then I strongly advise you to upgrade to 2009. In 2009 the default string is unicode.
You say that you still got garbage characters with the wide versions. Have you tried to inspect the values with the debugger? The vcl of pre 2009 delphi can not show unicode chars.
Some update.. If I use UTF8Encode on the SearchRec.Name I get the unicode string!
Next problem is TFileStream I use. I couldn't find a WideString version for it(for filenames).
You could use CreateFileW to open the file and get a handle then use THandleStream instead of TFileStream to read the file.
Tnt Unicode (TMS Unicode now) has a TTntFileStream which can open files based on widestring.
And as Gamecat stated, if you want to do unicode you should really upgrade to Delphi 2009. (awesome release in general)

Resources