Skopiowanie całej zawartości zmiennej PChar w dll - zawierającej znaki #0.

0

Cześć.

Bawię się po raz pierwszy w życiu Kaillerą jako lame developer - koder, a nie gracz. I mam problem. Jak wynika z dokumentacji exportowana funkcja kailleraclient.dll'o nazwie _kailleraSetInfos@4, przyjmuje jako parametr pewną strukturę. W pliku nagłowkowym w C++ wygooglowanym na sieci widzimy:

//...
 typedef struct {
    char *appName;
    char *gameList;

    int (WINAPI *gameCallback)(char *game, int player, int numplayers);

    void (WINAPI *chatReceivedCallback)(char *nick, char *text);
    void (WINAPI *clientDroppedCallback)(char *nick, int playernb);

    void (WINAPI *moreInfosCallback)(char *gamename);
  } kailleraInfos;
//...
  /*
     kailleraSetInfos:
     Use this method for setting up various infos:
     Required:
     - appName must be 128 chars max.
     - gameList is composed of all the game names separated by a NULL char (\0).
       The list ends with 2 NULL chars (\0\0).
       Be sure to only put available games there.
     - gameCallback will be the function called when a new game starts
       game -> name of the selected game
       player -> player number (1-8). 0 for spectator.
       numplayers -> number of players in the game (1-8)

     Optional:
     - chatReceivedCallback will be the function called when a chat line text
       has been received. Set it to NULL if you don't need/want this callback.
     - clientDroppedCallback will be the function called when a client drops
       from the current game. Set it to NULL if you don't need/want this callback.
     - moreInfosCallback will be the function called when the user selects
       "More infos about this game..." in the game list context menu.
       Set it to NULL if you don't need/want this feature.
  */
  void (__stdcall* kailleraSetInfos) (kailleraInfos *infos);

W Delphi w mojej dllce wygląda to tak:

type
type
  TkailleraInfos = packed record
    appName : PChar;
    gameList : PChar;
    gameCallback : function(game : PChar; player : integer; numplayers : integer) : integer; stdcall;
    chatReceivedCallback : procedure(Nick : PChar; text : PChar); stdcall;
    clientDroppedCallback : procedure(nick : PChar; playernb : integer); stdcall;
    moreInfosCallback : procedure(gamename : PChar); stdcall;
  end;

I teraz mam hookniętą lokalnie funkcje w takiej postaci:

procedure Hooked_kailleraSetInfos(var Infos : TkailleraInfos); stdcall;
type
  TOriginal_kailleraSetInfos = procedure(var Infos : TkailleraInfos); stdcall;
var
  S : string;
begin
  Hook[2].UnHook;
  S := PChar(Infos.gameList);
  SaveTextToFile(Test_Path + 'gejmlist.aaa', S);
  TOriginal_kailleraSetInfos(Hook[2].BaseAddr)(Infos);
  Hook[2].Hook;
end;

I w pliku winikowym mam zawsze pierwszą pozycję z listy. Nie pomogło:

  SetString(S, PChar(@Infos.gameList), 100);

Nie pomogły różne lcpystr i takie tam. Chyba, że źle ich użyłem. I tutaj pytanie. Nie mając żadnej zmiennej zawierającej wielkość tej listy, a chce od Emulatora MAME otrzymać ich kompletną listę w postaci stringa - nawet z tymi bajtami null. Ustawianie większego rozmiaru przez SetLength też nie pomogło.

Wiem, że lame pytanie, ale tutaj nie wiem jak to ogarnąc. Gdy robiłem, tak jak powyżej z SetString, w pliku otrzymałem jakieś "śmieci". A kiedy samodzielnie zrobię:

  Infos.gameList := 'jeden' + #0 + 'dwa' + #0#0;

To jest ok, ale ja chcę mieć całą listę przekazywaną przez emulator. A nie tylko pierwszą jej pozycję. Z góry dziękuję za wszelkie przykłady kodów w Delphi.

3
const cos:PChar='Ala'#0'ma'#0'kota.'#0#0;
var S:PChar;
var Lines:String;
begin
  SetLength(Lines,0);
  S:=cos;
  while StrLen(S)>0 do
  begin
    Lines:=Lines+StrPas(S)+#13#10;
    S:=S+StrLen(S)+1;
  end;
  Write(Lines);
end.
1

@olesio - a czy każdy łańcuch, jaki masz sobie rozdzielić używa separatora #0? I czy każdy taki łańcuch zakończony jest dwoma znakami #0?

Bo jeśli tak, to mając wskaźnik na pierwszy znak łańcucha można bez problemu zrobić sobie funkcję, która będzie dotąd wyłuskiwać pojedyncze podłańcuchy, aż natrafi na dwa znaki null; Teraz niestety nie podam Ci przykładu, ale za pół godzinki mogę podać przykładowy kod; Choć jak widzę podobnie wykonał @_13th_Dragon, tyle że skorzystał już z gotowych funkcji jak StrLen czy StrPas.


Poniżej masz przykład procedury przeszukującej łańcuch aż do napotkania dwóch znaków null, gdzie poszczególne podłańcuchy rozdziela się na podstawie znaku #0 i wyświetlane są na ekranie; W parametrze przekazywany jest tylko wskaźnik na pierwszy znak bufora;

  procedure ShowSubStrings(AChar: PAnsiChar);
  var
    pchrBegin, pchrEnd: PAnsiChar;
    strSub: AnsiString;
  begin
    pchrBegin := AChar;
    pchrEnd := AChar;

    while True do
    begin
      while pchrEnd^ <> #0 do
        Inc(pchrEnd);

      if pchrBegin = pchrEnd then Exit;

      SetLength(strSub, pchrEnd - pchrBegin);
      Move(pchrBegin^, strSub[1], pchrEnd - pchrBegin);
      WriteLn('"', strSub, '"');

      Inc(pchrEnd);
      pchrBegin := pchrEnd;
    end;
  end;

const
  SIMPLE_LINE: PAnsiChar = 'First'#0'Second'#0'Third'#0#0;
begin
  ShowSubStrings(SIMPLE_LINE);
  ReadLn;
end.

Wykonanie tego programu spowoduje wypisanie na ekranie poszczególnych substringów w znakach cudzysłowu:

"First"
"Second"
"Third"

Nie trzeba wcale używać dodatkowej zmiennej strSub, bo wskaźnik pchrBegin wskazuje zawsze na pierwszy znak substringu, a wskaźnik pchrEnd wskazuje na znak #0 kończący daną wartość; Dzięki temu możesz wykorzystać pchrBegin jeśli nie potrzebujesz kopiować łańcuchów do osobnych zmiennych (czy tablicy).


A tak poza tym to nie przesadzaj @olesio - nie są to wcale takie podstawy, żeby wątek zakładać w Newbie :]

1

@furious programming, żartujesz nieprawdaż?

procedure ShowSubStrings(AChar:PAnsiChar);
begin
  while AChar^<>#0 do
  begin
    WriteLn('"',AChar,'"');
    AChar:=AChar+StrLen(AChar)+1;
  end;
end;
 
const SIMPLE_LINE:PAnsiChar='First'#0'Second'#0'Third'#0#0;
begin
  ShowSubStrings(SIMPLE_LINE);
  ReadLn;
end.
0

Dziękuję za przykładowe kody. Sprawdzę je dopiero jutro wieczorem, kiedy będę w domu.

@FP: co do stringa to pole rekordu gameList ma zawierać więcej niż jedną linię. Linie te są rozdzielone znakiem o kodzie ASCII 0, a całość kończy się dwoma takimi znakami. A przekazywana lista przez emulator - w moim przypadku MAME32K, na pewno tak jest skonstuowana. Ponieważ dopiero później przycisk Create zawiera listę takich gier w Popup Menu w kolejnych liniach. A ja muszę mieć je wszystkie. Mam nadzieję, że Wasze kody się sprawdzą, jakby co to będę pisał. Bo muszę nadmienić, że Pos(#0, Infos.gameList) zwraca 0.

0

Skoro bawimy się w "profesjonalne" rozwiązania:

Type IStringUnserializer =
     Interface
      Procedure Reset;

      Function GetNext: String;
      Function GetIndex: uint32;
      Function Can: Boolean;
     End;

Type TStringData = Array of Char;

Type TKailleraStringUnserializer =
     Class sealed (TInterfacedObject, IStringUnserializer)
      Private
       Data   : TStringData;
       DataPos: uint32;
       Index  : uint32;

      Private
       Function FetchChar: Char;

      Public
       Constructor Create(const fString: String);

       Constructor Create(const fData: TStringData);
       Constructor Create(const fString: PChar);

       Procedure Reset;

       Function GetNext: String;
       Function GetIndex: uint32;
       Function Can: Boolean;
      End;

Function TKailleraStringUnserializer.FetchChar: Char;
Begin
 if (not Can) Then
  Exit(#0);

 Result := Data[DataPos];
 Inc(DataPos);
End;

Constructor TKailleraStringUnserializer.Create(const fData: TStringData);
Begin
 Data    := fData;
 DataPos := 0;
 Index   := 0;
End;

Constructor TKailleraStringUnserializer.Create(const fString: String);
Var I: int32;
Begin
 SetLength(Data, Length(fString));

 For I := 1 To Length(fString) Do
 Begin
  if (fString[I] = #0) and (fString[I+1] = #0) Then
  Begin
   SetLength(Data, I);
   Break;
  End;

  Data[I-1] := fString[I];
 End;
End;

Constructor TKailleraStringUnserializer.Create(const fString: PChar);
Var Ch: PChar;
Begin
 Ch := fString;

 SetLength(Data, 0);

 While (not ((Ch^ = #0) and ((Ch+1)^ = #0))) Do
 Begin
  SetLength(Data, Length(Data)+1);
  Data[High(Data)] := Ch^;

  Inc(Ch);
 End;
End;

Procedure TKailleraStringUnserializer.Reset;
Begin
 DataPos := 0;
End;

Function TKailleraStringUnserializer.GetNext: String;
Var Ch: Char;
Begin
 Result := '';

 if (not Can) Then
  Exit;

 Inc(Index);

 While (true) Do
 Begin
  Ch := FetchChar;

  if (Ch = #0) Then
   Exit Else
   Result += Ch;
 End;
End;

Function TKailleraStringUnserializer.GetIndex: uint32;
Begin
 Result := Index;
End;

Function TKailleraStringUnserializer.Can: Boolean;
Begin
 Result := (DataPos < Length(Data));
End;

Var Un: IStringUnserializer;
Begin
 Un := TKailleraStringUnserializer.Create('Hello'#0'World'#0'!'#0#0);

 While (Un.Can) Do
  Writeln('[', Un.GetIndex, '] = ', Un.GetNext);

 Readln;
End.

(sylwestrowa nuda :P)

0

Ok, sprawdze to jutro. Jednak widzę, że pewnie rozwiązanie Patryka trzeba będzie dostosować do Delphi 7. Ponieważ nie wiem czy da się zrobić niektóre Exity. Zapomniełem o tym wspomnieć, bo dla mnie stało się oczywiste, że jeśli nie wspomnę inaczej to zależy mi na kodzie dla Delphi 7 i najlepiej WinAPI "friendly" :) A wejściowe dane z #0 to według nagłówków z C++ to char *gameList czyli jak rozumiem pod Delphi PChar. Zresztą taki przykład tłumaczenia później znalazłem w Delphi na jakiejś stronie tlumacząc ją z Japońskiego.

A co do tego, że umieściłem wątek oryginalnie w Newbie, to tak się zastanawiałem, że pewnie rozwiązanie to mimo wszystko banał, na który nie umiem sam wpaść.

Zaś docelowo otrzymane elementy w liniach planuję póki co dodać do ListBoxa, we własnym Dialogu pod WinAPI.

0

Jeszcze raz wszystkim dziękuję. Zatwierdzam odpowiedź @_13th_Dragon'a. Jest imo najlepsza. Teraz już nie problem to ogarnąć. Wśród pozycji pojawiają się jeszcze też takie, jak poniżej:

*Chat (not game)
*Away (leave messages)

Jednak ponieważ zaczynają się od gwiazki, łatwo je zignorować żeby mieć faktycznie same gry na liście. Poniżej kod dla Delphi 7 i oczywiście "WinAPI friendly" ;) Potrzebne funkcje pożyczyłem ze źródel VCL SysUtils.pas.

//...

procedure SMsg(Text : string);
begin
  MessageBox(GetActiveWindow, PChar(Text), 'Test', MB_OK);
end;

function StrPas(const Str : PChar) : string;
begin
  Result := Str;
end;

function StrLen(const Str : PChar) : Cardinal; assembler;
asm
        MOV     EDX,EDI
        MOV     EDI,EAX
        MOV     ECX,0FFFFFFFFH
        XOR     AL,AL
        REPNE   SCASB
        MOV     EAX,0FFFFFFFEH
        SUB     EAX,ECX
        MOV     EDI,EDX
end;

procedure Hooked_kailleraSetInfos(var Infos : TkailleraInfos); stdcall;
type
  TOriginal_kailleraSetInfos = procedure(var Infos : TkailleraInfos); stdcall;
var
  S : PChar;
  Lines : string;
begin
  Hook[2].UnHook;
  SetLength(Lines, 0);
  S := Infos.gameList;
  while StrLen(S) > 0 do
  begin
    Lines := Lines + StrPas(S) + #13#10;
    S := S + StrLen(S) + 1;
  end;
  SMsg(Lines);
  TOriginal_kailleraSetInfos(Hook[2].BaseAddr)(Infos);
  Hook[2].Hook;
end;

//...

I wiem, że pokazywanie MessageBoxem jest lame, ale to najszybszy sposób "prezentacji wizualnej". Gdyż ja lamer - nie bardzo umiem debuggować dllki. I wiadomo, że jest jeszcze OutputDebugString, ale trzeba by odpalić Debug Window i pod nim podglądać. A tak jest dla mnie na etapie testów po prostu najszybciej :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1