Delphi mapping for Wine function wine_nt_to_unix_file_name - delphi

How can I correctly call wine_nt_to_unix_file_name from WINE's ntdll.dll in Delphi (10.4)?
In the web I found the definition to be like this:
NTSTATUS wine_nt_to_unix_file_name(const UNICODE_STRING *nameW, ANSI_STRING *unix_name_ret, UINT disposition, BOOLEAN check_case)
Disposition changes the return result for non existent last path part and check_case is self explanatory.
I would like to use this function to display real unix paths of my application to the user when running in WINE. This should make it more easy for a medium user to find a folder to share data between native apps and the WINE environment.
What I tried:
type
TWineGetVersion = function: PAnsiChar; stdcall;
TWineNTToUnixFileName = procedure(pIn: Pointer; pOut: Pointer; aParam: integer; caseSens: Boolean); stdcall;
...
initialization
try
LHandle := LoadLibrary('ntdll.dll');
if LHandle > 32 then
begin
LWineGetVersion := GetProcAddress(LHandle, 'wine_get_version');
LWineNTToUnixFileName := GetProcAddress(LHandle, 'wine_nt_to_unix_file_name');
end;
except
LWineGetVersion := nil;
LWineNTToUnixFileName := nil;
end;
Retrieving the WINE version works great but I cannot get the path conversion up and running as I don't know how to handle the returned Pointer to ANSI_STRING what seems to be a Windows structure like this:
typedef struct _STRING {
USHORT Length;
USHORT MaximumLength;
PCHAR Buffer;
} STRING;
I tried to approach the problem this way:
MyBuffer: array [0 .. 2048] of AnsiChar;
LWineNTToUnixFileName(PChar(aWinPath), #MyBuffer, 0, true);
But the function is returning total garbage in the buffer when output byte by byte.
Update
Following the hint to the current Wine source and the hint with the structure I tried this version, unfortunately delivering garbage. The first parameter is a UNICODE STRING structure, the second a simple ansistring. The third parameter receives the length of the returned buffer.
type
TWineNTToUnixFileName = procedure(pIn: Pointer; pOut: Pointer; aLen: Pointer); stdcall;
TWineUnicodeString = packed record
Len: Word;
MaxLen: Word;
Buffer: PWideChar;
end;
function WinePath(const aWinPath: String): String;
var
inString: TWineUnicodeString;
MyBuffer: array [0 .. 2048] of AnsiChar;
aLen,i: integer;
begin
inString.Buffer := PChar(aWinPath);
inString.Len := length(aWinPath);
inString.MaxLen := inString.Len;
LWineNTToUnixFileName(#inString, #MyBuffer, #aLen);
result := '';
for i := 1 to 20 do
result := result + MyBuffer[i];
end;
Based on Zeds great answer i created this function that automatically tries the new API call if the old one fails
type
TWineAnsiString = packed record
Len: Word;
MaxLen: Word;
Buffer: PAnsiChar;
end;
PWineAnsiString = ^TWineAnsiString;
TWineUnicodeString = packed record
Len: Word;
MaxLen: Word;
Buffer: PWideChar;
end;
PWineUnicodeString = ^TWineUnicodeString;
var
wine_get_version: function: PAnsiChar; cdecl;
// Both are assigned to the function in ntdll.dll to be able to try both alternatives
wine_nt_to_unix_file_name: function(const nameW: PWineUnicodeString; unix_name_ret: PWineAnsiString; disposition: Cardinal): Cardinal; cdecl;
wine_nt_to_unix_file_name_1: function(const nameW: PWineUnicodeString; nameA: PAnsiChar; Sz: PCardinal; disposition: Cardinal): Cardinal; cdecl;
LHandle: THandle;
function WinePath(const aPathIn: String): String;
var
VSz: Cardinal;
VNameA: AnsiString;
VNameW: TWineUnicodeString;
VUnixNameRet: TWineAnsiString;
VStatus: Cardinal;
aPath: String;
newVersion: Boolean;
begin
if not assigned(wine_nt_to_unix_file_name) then
begin
Result := 'n/a';
exit;
end;
aPath := '\??\' + aPathIn;
Result := '?';
newVersion := false;
VNameW.Len := Length(aPath) * SizeOf(WideChar);
VNameW.MaxLen := VNameW.Len;
VNameW.Buffer := PWideChar(aPath);
VUnixNameRet.Len := 0;
VUnixNameRet.MaxLen := 0;
VUnixNameRet.Buffer := nil;
VStatus := wine_nt_to_unix_file_name(#VNameW, #VUnixNameRet, 0);
if VStatus <> 0 then
begin
VSz := 255;
SetLength(VNameA, VSz);
ZeroMemory(Pointer(VNameA), VSz);
VStatus := wine_nt_to_unix_file_name_1(#VNameW, Pointer(VNameA), #VSz, 0);
newVersion := true;
end;
if VStatus <> 0 then
begin
Result := 'Error ' + IntToStr(Status);
exit;
end;
if not newVersion then
begin
VSz := VUnixNameRet.Len;
SetString(VNameA, VUnixNameRet.Buffer, VSz);
// ToDo: RtlFreeAnsiString(#VUnixNameRet)
end
else
SetLength(VNameA, VSz);
Result := StringReplace(VNameA, '/dosdevices/c:/', '/drive_c/', [rfIgnoreCase]);
end;

Try this type for MyBuffer:
type
TWineString = packed record
Len : Word;
MaxLen : Word;
Buffer : PAnsiChar;
end;
Also you can't pass PChar as input string because it isn't a UNICODE_STRING as defined in wine:
typedef struct _UNICODE_STRING {
USHORT Length; /* bytes */
USHORT MaximumLength; /* bytes */
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
You should use this equivalent:
type
TWineUnicodeString = packed record
Len : Word;
MaxLen : Word;
Buffer : PWideChar;
end;
Update: This function has changed its API 6 months ago, so depending on wine version you should use one of two ways: define USE_WINE_STABLE if you are on stable wine v5.0 or undefine it if you use newer version:
program WineTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Winapi.Windows,
System.SysUtils;
{$DEFINE USE_WINE_STABLE}
type
{$IFDEF USE_WINE_STABLE}
TWineAnsiString = packed record
Len : Word;
MaxLen : Word;
Buffer : PAnsiChar;
end;
PWineAnsiString = ^TWineAnsiString;
{$ENDIF}
TWineUnicodeString = packed record
Len : Word;
MaxLen : Word;
Buffer : PWideChar;
end;
PWineUnicodeString = ^TWineUnicodeString;
var
wine_get_version: function: PAnsiChar; cdecl;
{$IFDEF USE_WINE_STABLE}
wine_nt_to_unix_file_name: function(const nameW: PWineUnicodeString;
unix_name_ret: PWineAnsiString; disposition: Cardinal): Cardinal; cdecl;
{$ELSE}
wine_nt_to_unix_file_name: function(const nameW: PWineUnicodeString;
nameA: PAnsiChar; Sz: PCardinal; disposition: Cardinal): Cardinal; cdecl;
{$ENDIF}
procedure TestWinePath(const APath: string);
var
VSz: Cardinal;
VNameA: AnsiString;
VNameW: TWineUnicodeString;
{$IFDEF USE_WINE_STABLE}
VUnixNameRet: TWineAnsiString;
{$ENDIF}
VStatus: Cardinal;
begin
VNameW.Len := Length(APath) * SizeOf(WideChar);
VNameW.MaxLen := VNameW.Len;
VNameW.Buffer := PWideChar(APath);
{$IFDEF USE_WINE_STABLE}
VUnixNameRet.Len := 0;
VUnixNameRet.MaxLen := 0;
VUnixNameRet.Buffer := nil;
VStatus := wine_nt_to_unix_file_name(#VNameW, #VUnixNameRet, 0);
{$ELSE}
VSz := 255;
SetLength(VNameA, VSz);
ZeroMemory(Pointer(VNameA), VSz);
VStatus := wine_nt_to_unix_file_name(#VNameW, Pointer(VNameA), #VSz, 0);
{$ENDIF}
Writeln('wine_nt_to_unix_file_name:');
Writeln('status = 0x', IntToHex(VStatus, 8));
if VStatus <> 0 then begin
Exit;
end;
{$IFDEF USE_WINE_STABLE}
VSz := VUnixNameRet.Len;
SetString(VNameA, VUnixNameRet.Buffer, VSz);
// ToDo: RtlFreeAnsiString(#VUnixNameRet)
{$ELSE}
SetLength(VNameA, VSz);
{$ENDIF}
Writeln('unix len = ', VSz);
Writeln('unix: ', VNameA);
Writeln('nt: ', APath);
end;
function LoadProc(const AHandle: THandle; const AName: string): Pointer;
begin
Result := GetProcAddress(AHandle, PChar(AName));
if Result = nil then begin
raise Exception.CreateFmt('Can''t load function: "%s"', [AName]);
end;
end;
var
LHandle: THandle;
LNtFileName: string;
begin
try
LNtFileName := ParamStr(1);
if LNtFileName = '' then begin
Writeln('Usage: ', ExtractFileName(ParamStr(0)), ' NtFileName');
Exit;
end;
LHandle := LoadLibrary('ntdll.dll');
if LHandle > 32 then begin
wine_get_version := LoadProc(LHandle, 'wine_get_version');
Writeln('wine version = ', wine_get_version() );
wine_nt_to_unix_file_name := LoadProc(LHandle, 'wine_nt_to_unix_file_name');
TestWinePath(LNtFileName);
end;
except
on E: Exception do begin
Writeln(E.ClassName, ': ', E.Message);
end;
end;
end.
Output (tested on Ubuntu 20.04):
$ wine WineTest.exe "\??\c:\windows\notepad.exe"
wine version = 5.0
wine_nt_to_unix_file_name:
status = 0x00000000
unix len = 49
unix: /home/zed/.wine/dosdevices/c:/windows/notepad.exe
nt: \??\c:\windows\notepad.exe

Related

How to find out which port uses a process?

I would like to know how I can find out which ports a program / process uses. I want to know the used ports from one process and write then in a label.
Is there a unit or function that is available?
You can use the GetExtendedTcpTable function passing the TCP_TABLE_OWNER_PID_ALL TableClass value , this will return a MIB_TCPTABLE_OWNER_PID structure which is an array to the MIB_TCPROW_OWNER_PID record , this structure contains the port number (dwLocalPort) and the PID (dwOwningPid) of the process, you can resolve the name of the PID using the CreateToolhelp32Snapshot function.
Sample
{$APPTYPE CONSOLE}
uses
WinSock,
TlHelp32,
Classes,
Windows,
SysUtils;
const
ANY_SIZE = 1;
iphlpapi = 'iphlpapi.dll';
TCP_TABLE_OWNER_PID_ALL = 5;
type
TCP_TABLE_CLASS = Integer;
PMibTcpRowOwnerPid = ^TMibTcpRowOwnerPid;
TMibTcpRowOwnerPid = packed record
dwState : DWORD;
dwLocalAddr : DWORD;
dwLocalPort : DWORD;
dwRemoteAddr: DWORD;
dwRemotePort: DWORD;
dwOwningPid : DWORD;
end;
PMIB_TCPTABLE_OWNER_PID = ^MIB_TCPTABLE_OWNER_PID;
MIB_TCPTABLE_OWNER_PID = packed record
dwNumEntries: DWORD;
table: Array [0..ANY_SIZE - 1] of TMibTcpRowOwnerPid;
end;
var
GetExtendedTcpTable:function (pTcpTable: Pointer; dwSize: PDWORD; bOrder: BOOL; lAf: ULONG; TableClass: TCP_TABLE_CLASS; Reserved: ULONG): DWord; stdcall;
function GetPIDName(hSnapShot: THandle; PID: DWORD): string;
var
ProcInfo: TProcessEntry32;
begin
ProcInfo.dwSize := SizeOf(ProcInfo);
if not Process32First(hSnapShot, ProcInfo) then
Result := 'Unknow'
else
repeat
if ProcInfo.th32ProcessID = PID then
Result := ProcInfo.szExeFile;
until not Process32Next(hSnapShot, ProcInfo);
end;
procedure ShowTCPPortsUsed(const AppName : string);
var
Error : DWORD;
TableSize : DWORD;
i : integer;
pTcpTable : PMIB_TCPTABLE_OWNER_PID;
SnapShot : THandle;
LAppName : string;
LPorts : TStrings;
begin
LPorts:=TStringList.Create;
try
TableSize := 0;
//Get the size o the tcp table
Error := GetExtendedTcpTable(nil, #TableSize, False, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0);
if Error <> ERROR_INSUFFICIENT_BUFFER then exit;
GetMem(pTcpTable, TableSize);
try
SnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
try
//get the tcp table data
if GetExtendedTcpTable(pTcpTable, #TableSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) = NO_ERROR then
for i := 0 to pTcpTable.dwNumEntries - 1 do
begin
LAppName:=GetPIDName(SnapShot, pTcpTable.Table[i].dwOwningPid);
if SameText(LAppName, AppName) and (LPorts.IndexOf(IntToStr(pTcpTable.Table[i].dwLocalPort))=-1) then
LPorts.Add(IntToStr(pTcpTable.Table[i].dwLocalPort));
end;
finally
CloseHandle(SnapShot);
end;
finally
FreeMem(pTcpTable);
end;
Writeln(LPorts.Text);
finally
LPorts.Free;
end;
end;
var
hModule : THandle;
begin
try
hModule := LoadLibrary(iphlpapi);
try
GetExtendedTcpTable := GetProcAddress(hModule, 'GetExtendedTcpTable');
ShowTCPPortsUsed('Skype.exe');
finally
FreeLibrary(hModule);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
In order to get the correct Port number you have to use ntohs()
if SameText(LAppName, AppName) and
(LPorts.IndexOf(IntToStr(pTcpTable.Table[i].dwLocalPort))=-1) then
LPorts.Add(IntToStr(ntohs(pTcpTable.Table[i].dwLocalPort)));
more info here

Check if memory is readable or why do it not catches the exception?

I have this code that gets called from an injected DLL from a foreign process. It sould read some memory ranges but I sometimes get a segmentation fault at this line DataBuffer := TCharPointer(Address + CharOffset)^;. So is there any way to check if the memory is readable?
function GetCurrentData(Address: Pointer): PChar;
var
DataBuffer: Char;
CharArray: Array of Char;
CharOffset: Integer;
ReadBytes: longword;
begin
CharOffset := 0;
SetLength(CharArray, 0);
repeat
DataBuffer := TCharPointer(Address + CharOffset)^;
CharOffset := CharOffset + 1;
SetLength(CharArray, CharOffset);
CharArray[CharOffset - 1] := DataBuffer;
until (Ord(DataBuffer) = 0);
Result := PChar(#CharArray[0]);
end;
i also tryed to catch the exception but for some reason this is not working. The host programm still crashes.
unit UnitEventBridgeExports;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Windows, ShellAPI, JwaTlHelp32, SimpleIPC;
type
TCharPointer = ^Char;
const
WOWEXE = 'TestProgramm.exe';
var
IPCClient: TSimpleIPCClient;
PID: DWord;
Process: THandle;
procedure EventCalled;
procedure InitializeWoWEventBridge; stdcall;
implementation
function GetProcessIDByName(Exename: String): DWord;
var
hProcSnap: THandle;
pe32: TProcessEntry32;
begin
Result := 0;
hProcSnap := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
if hProcSnap <> INVALID_HANDLE_VALUE then
begin
pe32.dwSize := SizeOf(ProcessEntry32);
if Process32First(hProcSnap, pe32) = True then
begin
while Process32Next(hProcSnap, pe32) = True do
begin
if pos(Exename, pe32.szExeFile) <> 0 then
Result := pe32.th32ProcessID;
end;
end;
CloseHandle(hProcSnap);
end;
end;
procedure InitializeEventBridge; stdcall;
begin
IPCClient := TSimpleIPCClient.Create(nil);
IPCClient.ServerID := 'EventBridgeServer';
IPCClient.Active := True;
IPCClient.SendStringMessage('init');
PID := GetProcessIDByName(EXE);
Process := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
end;
function GetCurrentData(Address: Pointer): PChar;
var
DataBuffer: Char;
CharArray: Array of Char;
CharOffset: Integer;
ReadBytes: longword;
CharPointer: TCharPointer;
BreakLoop: Boolean;
begin
CharOffset := 0;
SetLength(CharArray, 0);
BreakLoop := False;
repeat
try
CharPointer := TCharPointer(Address + CharOffset);
DataBuffer := CharPointer^;
CharOffset := CharOffset + 1;
SetLength(CharArray, CharOffset);
CharArray[CharOffset - 1] := DataBuffer;
except
BreakLoop := True;
end;
until (Ord(DataBuffer) = 0) or BreakLoop;
Result := PChar(#CharArray[0]);
end;
procedure EventCalled;
var
TmpAddress: Pointer;
StringData: PChar;
begin
{$ASMMODE intel}
asm
mov [TmpAddress], edi
end;
StringData := GetCurrentData(TmpAddress);
IPCClient.SendStringMessage('update:' + StringData);
//IPCClient.SendStringMessage('update');
end;
end.
Your GetCurrentData() implementation is returning a pointer to a local array that goees out of scope when the function exits, then EventCalled() tries to use that poiner after it is no longer valid. Try this instead:
function GetCurrentData(Address: Pointer): AnsiString;
var
Offset: Integer;
begin
Result := '';
Offset := 0;
repeat
try
if PByte(Longint(Address) + Offset)^ = #0 then Break;
Inc(Offset);
except
Break;
end;
until False;
SetString(Result, PAnsiChar(Address), Offset);
end;
procedure EventCalled;
var
TmpAddress: Pointer;
StringData: AnsiString;
begin
{$ASMMODE intel}
asm
mov [TmpAddress], edi
end;
StringData := GetCurrentData(TmpAddress);
IPCClient.SendStringMessage('update:' + StringData);
//IPCClient.SendStringMessage('update');
end;
IsBadReadPtr API is here to help. You give address and size, and you get the readability back. Raymond Chen suggests to never use it though.
Other than that, VirtualQuery should give you information about the address in question to tell its readability.
Since Ken in comments below re-warned about danger of IsBadReadPtr, I bring it up to the answer to not pass by. Be sure to read the comments and links to Raymdond's blog. Be sure to see also:
Most efficient replacement for IsBadReadPtr?
How to check if a pointer is valid?

Open any File in a Memo?

In Notepad you can Open any File and it will display the raw data inside.
I would like to do this in a TMemo but have struggled to find out how to do this.
I managed to find this code here.
I modified it to a function and changed it slightly for my purposes:
function OpenBinaryFile(var Data; Count: Cardinal): string;
var
Line: string[80];
i: Cardinal;
P: PAnsiChar;
nStr: string[4];
SL: TStringList;
const
posStart = 1;
binStart = 7;
ascStart = 57;
begin
P := #Data;
Line := '';
SL := TStringList.Create;
try
for i := 0 to Count - 1 do
begin
if (i mod 16) = 0 then
begin
if Length(Line) > 0 then
SL.Add(Trim(Line));
FillChar(Line, SizeOf(Line), ' ');
Line[0] := Chr(72);
end;
if P[i] >= ' ' then
Line[i mod 16 + ascStart] := P[i]
else
Line[i mod 16 + ascStart] := '.';
end;
SL.Add(Trim(Line));
Result := SL.Text;
finally
SL.Free;
end;
end;
It works, but it only displays in a fixed amount of characters per line, like this:
What do I need to change so it fills all the memo in the same way Notepad would?
Well, it's the if (i mod 16) = 0 test that is truncating the lines at 16 characters.
I believe that Notepad does the same as this code:
var
i: Integer;
s: AnsiString;
Stream: TFileStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead);
try
SetLength(s, Stream.Size);
if Stream.Size>0 then
Stream.ReadBuffer(s[1], Stream.Size);
finally
Stream.Free;
end;
for i := 1 to Length(s) do
if s[i]=#0 then
s[i] := ' ';
Memo1.Text := s;
end;
If you want to replace non-printable characters with '.' then you can easily do so by modifying the code above like this:
if s[i]<#32 then
s[i] := '.';
TStrings became TEncoding-aware in D2009. By default, TStrings.LoadFrom...() will use TEncoding.Default unless you tell it otherwise. I would suggest implementing a custom TEncoding derived class that reads/writes raw 8-bit data, eg:
type
TRawEncoding = class(TEncoding)
protected
function GetByteCount(Chars: PChar; CharCount: Integer): Integer; override;
function GetBytes(Chars: PChar; CharCount: Integer; Bytes: PByte; ByteCount: Integer): Integer; override;
function GetCharCount(Bytes: PByte; ByteCount: Integer): Integer; override;
function GetChars(Bytes: PByte; ByteCount: Integer; Chars: PChar; CharCount: Integer): Integer; override;
public
constructor Create;
function GetMaxByteCount(CharCount: Integer): Integer; override;
function GetMaxCharCount(ByteCount: Integer): Integer; override;
function GetPreamble: TBytes; override;
end;
.
constructor TRawEncoding.Create;
begin
FIsSingleByte := True;
FMaxCharSize := 1;
end;
function TRawEncoding.GetByteCount(Chars: PChar; CharCount: Integer): Integer;
begin
Result := CharCount;
end;
function TRawEncoding.GetBytes(Chars: PChar; CharCount: Integer; Bytes: PByte; ByteCount: Integer): Integer;
var
i : Integer;
begin
Result := Math.Min(CharCount, ByteCount);
for i := 1 to Result do begin
// replace illegal characters > $FF
if Word(Chars^) > $00FF then begin
Bytes^ := Byte(Ord('?'));
end else begin
Bytes^ := Byte(Chars^);
end;
//advance to next char
Inc(Chars);
Inc(Bytes);
end;
end;
function TRawEncoding.GetCharCount(Bytes: PByte; ByteCount: Integer): Integer;
begin
Result := ByteCount;
end;
function TRawEncoding.GetChars(Bytes: PByte; ByteCount: Integer; Chars: PChar; CharCount: Integer): Integer;
var
i : Integer;
begin
Result := Math.Min(CharCount, ByteCount);
for i := 1 to Result do begin
Word(Chars^) := Bytes^;
//advance to next char
Inc(Chars);
Inc(Bytes);
end;
end;
function TRawEncoding.GetMaxByteCount(CharCount: Integer): Integer;
begin
Result := CharCount;
end;
function TRawEncoding.GetMaxCharCount(ByteCount: Integer): Integer;
begin
Result := ByteCount;
end;
function TRawEncoding.GetPreamble: TBytes;
begin
SetLength(Result, 0);
end;
Then you can use it like this:
var
Enc: TEncoding;
begin
Enc := TRawEncoding.Create;
try
Memo1.Lines.LoadFromFile('filename', Enc);
finally
Enc.Free;
end;
end;

How to convert ISO 639-1 code to language id

Is there a way to get the primary language id from an language ISO code using the Windows API? I want to use it for my GetDateFormatInt function and I am curious if there is a way without using the constants. Although the function doesn't use the default sublang for english, I'm interested in the primary language id only.
function GetDateFormatInt(const aLanguageISOCode: string): string;
const
C_ISO_CODES: array[0..3] of string = (
'nl', 'en', 'de', 'fr'
);
C_LCIDS: array[0..3] of Cardinal = (
((SUBLANG_DUTCH shl 10) or LANG_DUTCH) or (SORT_DEFAULT shl 16),
((SUBLANG_ENGLISH_UK shl 10) or LANG_ENGLISH) or (SORT_DEFAULT shl 16),
((SUBLANG_GERMAN shl 10) or LANG_GERMAN) or (SORT_DEFAULT shl 16),
((SUBLANG_FRENCH shl 10) or LANG_FRENCH) or (SORT_DEFAULT shl 16)
);
var
i: Integer;
lLCID: Cardinal;
lBuffer: array[0..512] of Char;
begin
i := AnsiIndexText(aLanguageISOCode, C_ISO_CODES);
if i > -1 then
lLCID := C_LCIDS[i]
else
lLCID := LOCALE_USER_DEFAULT;
i := GetDateFormat(lLCID, DATE_LONGDATE, nil, nil, lBuffer, 512);
SetString(Result, lBuffer, i - 1);
end;
Disclaimer: I wrote the following solely based on the existence of the LOCALE_SISO639LANGNAME constant. I have no idea if it actually works or is useful in any way. I haven't tested it at all.
unit Iso639;
interface
uses
Windows;
function Iso639ToPrimaryLangID(const S: string): LANGID;
implementation
uses
SysUtils, Classes;
var
Iso639Languages: TStringList = nil;
function GetLocaleDataW(ID: LCID; Flag: DWORD): WideString;
var
Buffer: array[0..1023] of WideChar;
begin
Buffer[0] := #0;
SetString(Result, Buffer, GetLocaleInfoW(ID, Flag, Buffer, SizeOf(Buffer) div 2));
end;
function LangIDFromLcID(ID: LCID): LANGID;
begin
Result := LANGID(ID);
end;
function PrimaryLangID(LangID: LANGID): LANGID;
begin
Result := LangID and $3FF;
end;
procedure InitializeIso639Languages;
var
I: Integer;
ALocaleID: LCID;
ALangID: LANGID;
S: string;
begin
Iso639Languages := TStringList.Create;
try
Iso639Languages.Sorted := True;
for I := 0 to Languages.Count - 1 do
begin
ALocaleID := Languages.LocaleID[I];
ALangID := PrimaryLangID(LangIDFromLcID(ALocaleID));
if Iso639Languages.IndexOfObject(TObject(ALangID)) = -1 then
begin
S := GetLocaleDataW(ALocaleID, LOCALE_SISO639LANGNAME);
Iso639Languages.AddObject(S, TObject(ALangID));
end;
end;
except
FreeAndNil(Iso639Languages);
raise;
end;
end;
function Iso639ToPrimaryLangID(const S: string): LANGID;
var
I: Integer;
begin
Result := 0;
if not Assigned(Iso639Languages) then
InitializeIso639Languages;
I := Iso639Languages.IndexOf(S);
if I <> -1 then
Result := LANGID(Iso639Languages.Objects[I]);
end;
initialization
finalization
FreeAndNil(Iso639Languages);
end.
Warning for Delphi 7 users:
TLanguages.Create has an issue with DEP and throws an Access Violation when DEP is enabled. So don't use Languages[I].LocaleID and get the LocaleID yourself:
function EnumLocalesProc(aLocaleString: PChar): Integer; stdcall;
var
lLocaleID: LCID;
begin
lLocaleID := StrToInt('$' + Copy(aLocaleString, 5, 4));
Result := 1;
end;
EnumSystemLocales(#EnumLocalesProc, LCID_SUPPORTED);

A unit calling wsock32.dll to be adapted for D2009

Here a unit i can't get working properly on Delphi 2009. I give you the original code that correctly transmit data when compiled with Delphi 2007. Ansifying the code for Delphi 2009 gives me a connection to the server but no data is transmitted and no feedback). Thanks.
unit SMTP_Connections2007;
// *********************************************************************
// Unit Name : SMTP_Connections *
// Author : Melih SARICA (Non ZERO) *
// Date : 01/17/2004 *
//**********************************************************************
interface
uses
Classes, StdCtrls;
const
WinSock = 'wsock32.dll';
Internet = 2;
Stream = 1;
fIoNbRead = $4004667F;
WinSMTP = $0001;
LinuxSMTP = $0002;
type
TWSAData = packed record
wVersion: Word;
wHighVersion: Word;
szDescription: array[0..256] of Char;
szSystemStatus: array[0..128] of Char;
iMaxSockets: Word;
iMaxUdpDg: Word;
lpVendorInfo: PChar;
end;
PHost = ^THost;
THost = packed record
Name: PChar;
aliases: ^PChar;
addrtype: Smallint;
Length: Smallint;
addr: ^Pointer;
end;
TSockAddr = packed record
Family: Word;
Port: Word;
Addr: Longint;
Zeros: array[0..7] of Byte;
end;
function WSAStartup(Version:word; Var Data:TwsaData):integer; stdcall; far; external winsock;
function socket(Family,Kind,Protocol:integer):integer; stdcall; far; external winsock;
function shutdown(Socket,How:Integer):integer; stdcall; far; external winsock;
function closesocket(socket:Integer):integer; stdcall; far; external winsock;
function WSACleanup:integer; stdcall; far; external winsock;
function bind(Socket:Integer; Var SockAddr:TSockAddr; AddrLen:integer):integer; stdcall; far; external winsock;
function listen(socket,flags:Integer):integer; stdcall; far; external winsock;
function connect(socket:Integer; Var SockAddr:TSockAddr; AddrLen:integer):integer; stdcall; far; external winsock;
function accept(socket:Integer; Var SockAddr:TSockAddr; Var AddrLen:Integer):integer; stdcall; far; external winsock;
function WSAGetLastError:integer; stdcall; far; external winsock;
function recv(socket:integer; data:pchar; datalen,flags:integer):integer; stdcall; far; external winsock;
function send(socket:integer; var data; datalen,flags:integer):integer; stdcall; far; external winsock;
function gethostbyname(HostName:PChar):PHost; stdcall; far; external winsock;
function WSAIsBlocking:boolean; stdcall; far; external winsock;
function WSACancelBlockingCall:integer; stdcall; far; external winsock;
function ioctlsocket(socket:integer; cmd: Longint; var arg: longint): Integer; stdcall; far; external winsock;
function gethostname(name:pchar; size:integer):integer; stdcall; far; external winsock;
procedure _authSendMail(MailServer,uname,upass,mFrom,mFromName,mToName,Subject:string;mto,mbody:TStringList);
function ConnectServer(mhost:string;mport:integer):integer;
function ConnectServerwin(mhost:string;mport:integer):integer;
function DisConnectServer:integer;
function Stat: string;
function SendCommand(Command: String): string;
function SendData(Command: String): string;
function SendCommandWin(Command: String): string;
function ReadCommand: string;
function encryptB64(s:string):string;
var
mconnHandle: Integer;
mFin, mFOut: Textfile;
EofSock: Boolean;
mactive: Boolean;
mSMTPErrCode: Integer;
mSMTPErrText: string;
mMemo: TMemo;
implementation
uses
SysUtils, Sockets, IdBaseComponent,
IdCoder, IdCoder3to4, IdCoderMIME, IniFiles,Unit1;
var
mClient: TTcpClient;
procedure _authSendMail(MailServer, uname, upass, mFrom, mFromName,
mToName, Subject: string; mto, mbody: TStringList);
var
tmpstr: string;
cnt: Integer;
mstrlist: TStrings;
RecipientCount: Integer;
begin
if ConnectServerWin(Mailserver, 587) = 250 then //port is 587
begin
Sendcommandwin('AUTH LOGIN ');
SendcommandWin(encryptB64(uname));
SendcommandWin(encryptB64(upass));
SendcommandWin('MAIL FROM: ' + mfrom);
for cnt := 0 to mto.Count - 1 do
SendcommandWin('RCPT TO: ' + mto[cnt]);
Sendcommandwin('DATA');
SendData('Subject: ' + Subject);
SendData('From: "' + mFromName + '" <' + mfrom + '>');
SendData('To: ' + mToName);
SendData('Mime-Version: 1.0');
SendData('Content-Type: multipart/related; boundary="Esales-Order";');
SendData(' type="text/html"');
SendData('');
SendData('--Esales-Order');
SendData('Content-Type: text/html;');
SendData(' charset="iso-8859-9"');
SendData('Content-Transfer-Encoding: QUOTED-PRINTABLE');
SendData('');
for cnt := 0 to mbody.Count - 1 do
SendData(mbody[cnt]);
Senddata('');
SendData('--Esales-Order--');
Senddata(' ');
mSMTPErrText := SendCommand(crlf + '.' + crlf);
try
mSMTPErrCode := StrToInt(Copy(mSMTPErrText, 1, 3));
except
end;
SendData('QUIT');
DisConnectServer;
end;
end;
function Stat: string;
var
s: string;
begin
s := ReadCommand;
Result := s;
end;
function EchoCommand(Command: string): string;
begin
SendCommand(Command);
Result := ReadCommand;
end;
function ReadCommand: string;
var
tmp: string;
begin
repeat
ReadLn(mfin, tmp);
if Assigned(mmemo) then
mmemo.Lines.Add(tmp);
until (Length(tmp) < 4) or (tmp[4] <> '-');
Result := tmp
end;
function SendData(Command: string): string;
begin
Writeln(mfout, Command);
end;
function SendCommand(Command: string): string;
begin
Writeln(mfout, Command);
Result := stat;
end;
function SendCommandWin(Command: string): string;
begin
Writeln(mfout, Command + #13);
Result := stat;
end;
function FillBlank(Source: string; number: Integer): string;
var
a: Integer;
begin
Result := '';
for a := Length(trim(Source)) to number do
Result := Result + ' ';
end;
function IpToLong(ip: string): Longint;
var
x, i: Byte;
ipx: array[0..3] of Byte;
v: Integer;
begin
Result := 0;
Longint(ipx) := 0;
i := 0;
for x := 1 to Length(ip) do
if ip[x] = '.' then
begin
Inc(i);
if i = 4 then Exit;
end
else
begin
if not (ip[x] in ['0'..'9']) then Exit;
v := ipx[i] * 10 + Ord(ip[x]) - Ord('0');
if v > 255 then Exit;
ipx[i] := v;
end;
Result := Longint(ipx);
end;
function HostToLong(AHost: string): Longint;
var
Host: PHost;
begin
Result := IpToLong(AHost);
if Result = 0 then
begin
Host := GetHostByName(PChar(AHost));
if Host <> nil then Result := Longint(Host^.Addr^^);
end;
end;
function LongToIp(Long: Longint): string;
var
ipx: array[0..3] of Byte;
i: Byte;
begin
Longint(ipx) := long;
Result := '';
for i := 0 to 3 do Result := Result + IntToStr(ipx[i]) + '.';
SetLength(Result, Length(Result) - 1);
end;
procedure Disconnect(Socket: Integer);
begin
ShutDown(Socket, 1);
CloseSocket(Socket);
end;
function CallServer(Server: string; Port: Word): Integer;
var
SockAddr: TSockAddr;
begin
Result := socket(Internet, Stream, 0);
if Result = -1 then Exit;
FillChar(SockAddr, SizeOf(SockAddr), 0);
SockAddr.Family := Internet;
SockAddr.Port := swap(Port);
SockAddr.Addr := HostToLong(Server);
if Connect(Result, SockAddr, SizeOf(SockAddr)) <> 0 then
begin
Disconnect(Result);
Result := -1;
end;
end;
function OutputSock(var F: TTextRec): Integer; far;
begin
if F.BufPos <> 0 then
begin
Send(F.Handle, F.BufPtr^, F.BufPos, 0);
F.BufPos := 0;
end;
Result := 0;
end;
function InputSock(var F: TTextRec): Integer; far;
var
Size: Longint;
begin
F.BufEnd := 0;
F.BufPos := 0;
Result := 0;
repeat
if (IoctlSocket(F.Handle, fIoNbRead, Size) < 0) then
begin
EofSock := True;
Exit;
end;
until (Size >= 0);
F.BufEnd := Recv(F.Handle, F.BufPtr, F.BufSize, 0);
EofSock := (F.Bufend = 0);
end;
function CloseSock(var F: TTextRec): Integer; far;
begin
Disconnect(F.Handle);
F.Handle := -1;
Result := 0;
end;
function OpenSock(var F: TTextRec): Integer; far;
begin
if F.Mode = fmInput then
begin
EofSock := False;
F.BufPos := 0;
F.BufEnd := 0;
F.InOutFunc := #InputSock;
F.FlushFunc := nil;
end
else
begin
F.Mode := fmOutput;
F.InOutFunc := #OutputSock;
F.FlushFunc := #OutputSock;
end;
F.CloseFunc := #CloseSock;
Result := 0;
end;
procedure AssignCrtSock(Socket:integer; Var Input,Output:TextFile);
begin
with TTextRec(Input) do
begin
Handle := Socket;
Mode := fmClosed;
BufSize := SizeOf(Buffer);
BufPtr := #Buffer;
OpenFunc := #OpenSock;
end;
with TTextRec(Output) do
begin
Handle := Socket;
Mode := fmClosed;
BufSize := SizeOf(Buffer);
BufPtr := #Buffer;
OpenFunc := #OpenSock;
end;
Reset(Input);
Rewrite(Output);
end;
function ConnectServer(mhost: string; mport: Integer): Integer;
var
tmp: string;
begin
mClient := TTcpClient.Create(nil);
mClient.RemoteHost := mhost;
mClient.RemotePort := IntToStr(mport);
mClient.Connect;
mconnhandle := callserver(mhost, mport);
if (mconnHandle<>-1) then
begin
AssignCrtSock(mconnHandle, mFin, MFout);
tmp := stat;
tmp := SendCommand('HELO bellona.com.tr');
if Copy(tmp, 1, 3) = '250' then
begin
Result := StrToInt(Copy(tmp, 1, 3));
end;
end;
end;
function ConnectServerWin(mhost: string; mport: Integer): Integer;
var
tmp: string;
begin
mClient := TTcpClient.Create(nil);
mClient.RemoteHost := mhost;
mClient.RemotePort := IntToStr(mport);
mClient.Connect;
mconnhandle := callserver(mhost, mport);
if (mconnHandle<>-1) then
begin
AssignCrtSock(mconnHandle, mFin, MFout);
tmp := stat;
tmp := SendCommandWin('HELO bellona.com.tr');
if Copy(tmp, 1, 3) = '250' then
begin
Result := StrToInt(Copy(tmp, 1, 3));
end;
end;
end;
function DisConnectServer: Integer;
begin
closesocket(mconnhandle);
mClient.Disconnect;
mclient.Free;
end;
function encryptB64(s: string): string;
var
hash1: TIdEncoderMIME;
p: string;
begin
if s <> '' then
begin
hash1 := TIdEncoderMIME.Create(nil);
p := hash1.Encode(s);
hash1.Free;
end;
Result := p;
end;
end.
Here some code to give it a try:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
// Button1: TButton;
// Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
SMTP_Connections2007;
procedure TForm1.Button1Click(Sender: TObject);
var
mto, mbody: TStringList;
MailServer, uname, upass, mFrom, mFromName,
mToName, Subject: string;
begin
mMemo := Memo1; // to output server feedback
//..........................
MailServer := 'somename.servername';
uname := 'username';
upass := 'password';
mFrom := 'someuser#xyz.net';
mFromName := 'forename surname';
mToName := '';
Subject := 'Your Subject';
//..........................
mto := TStringList.Create;
mbody := TStringList.Create;
try
mto.Add('destination_emailaddress');
mbody.Add('Test Mail');
//Send Mail.................
_authSendMail(MailServer, uname, upass, mFrom, mFromName, mToName, Subject, mto, mbody);
//..........................
finally
mto.Free;
mbody.Free;
end;
end;
end.
I ansified your code, and tested it with Delphi2009, it works without any problem. I've managed to send email from gmx.com to mail.google.com.
I did change string to AnsiString, Char to AnsiChar, and PChar to PAnsiChar.
Maybe you simply forgot to ansify Char or PChar?
One thing to consider would be the TCP/IP library Synapse, of which the latest development version in SVN compiles and runs against Delphi 2009 with Unicode and
has all of the functionality in your unit and can easily perform the steps of your test program.

Resources