Inhaltsverzeichnis
NetTalk-Plugins schreiben in C++
Ein Tutorial
Dieses Tutorial soll zeigen wie man ein Plugin in C++ schreib, welches auf in einem Channel gesprochene Worte reagiert.
Schritt 1: WinMain
Zuerst (wie sollte es anders sein) brauchen wir ein Windows-Programm-Grundgerüst:
// LaberPlugin.cpp // // Zuerst ein paar includes #include <windows.h> #include <vector> #include <string> using namespace std; // Dann ein paar Variablen (was wären wir nur ohne) char szClassName[]="netteFensterklasse"; // Fensterklasse string strPluginName; char szNumberBuffer[33]; // Um Zahlen in Text umzuwandeln HWND hPluginHwnd; // Handle unseres Fensters HWND hNTalkHwnd; // Handles des NetTalk-Kommunikationsfensters string strPluginHwnd; // string unseres Plugin-Handles string strNTalkHwnd; // string des NetTalk-Handles // Hier noch ein paar Prototypen LRESULT CALLBACK WinProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); BOOL StartPlugin(char *cmdLine); LRESULT SendPlain(string text); LRESULT SendPlain(char *text); LRESULT SendText(string text, int FrameId); LRESULT SendText(char *text, int FrameId); LRESULT ExecuteNTalkCommand(char *command); void tokenizeString(vector<string> &tokenList, char *plainStr, char seperator); LRESULT Chan_Msg(string Text, string Nick, string Channel, int FrameId, int ConnId); // Hier kommt endlich die WinMain int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdLine, int cmdShow) { HWND hwnd; MSG msg; WNDCLASSEX wc; // Fensterklasse definieren wc.cbSize=sizeof (WNDCLASSEX); wc.cbClsExtra=0; wc.cbWndExtra=0; wc.hInstance=hInst; wc.lpszClassName=szClassName; wc.lpfnWndProc=WinProc; wc.hIcon=LoadIcon (NULL, IDI_APPLICATION); wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION); wc.hCursor=LoadCursor(NULL, IDC_ARROW); wc.style=CS_DBLCLKS; wc.hbrBackground=(HBRUSH) COLOR_BACKGROUND; wc.lpszMenuName=NULL; // Fensterklasse registrieren if (!RegisterClassEx (&wc)) { MessageBox(NULL, "Failed to register Window!", "Error", MB_OK | MB_ICONEXCLAMATION); return 0; } // und endlich...ein Fenster erstellen hwnd=CreateWindowEx ( 0, szClassName, "KAnJu - Pre-Beta-App", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 544, 375, HWND_DESKTOP, NULL, hInst, NULL ); if(hwnd==NULL){ MessageBox(NULL, "Failed to create window!", "Error", MB_OK | MB_ICONEXCLAMATION); return 0; } // erstmal unser eigenes Handles merken (in Zahl und Text) hPluginHwnd=hwnd; strPluginHwnd=string( ltoa( hPluginHwnd, szNumberBuffer, 10 ) ); //ShowWindow (hwnd, cmdShow); // Nein, das Fenster wollen wir nicht zeigen ;p // Das Plugin starten (Wenn das nicht klappt wird das Programm beendet) if(!StartPlugin(cmdLine)) { DestroyWindow(hwnd); return -1; } // Und der ach so beliebte Message-Loop while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }// end while(GetMessage()) return msg.wParam; }// end #WinMain()
Anmerkung: Beim Plugin-Namen ist unbedingt(!) darauf zu achten, das dieser keine Leerzeichen enthält.
Schritt 2: StartPlugin
Jetzt kommt die Funktion 'StartPlugin(char *)'. Hier müssen wir uns bei NetTalk 'anmelden'. Den Parameter (die Kommandozeile welche der WinMain übergeben wurde) brauchen wir um den Handle des NetTalk-Kommunikationsfensters zu erhalten (diesen erhält man auch wen man in NetTalk den Befehl '?DataPort' in der Textleiste eingibt). Läuft hier alles so ab wie gedacht, wird TRUE zurück gegeben, sonst FALSE.
BOOL StartPlugin(char *cmdLine) { // Parameter parsen vector<string> parameter; tokenizeString(parameter, cmdLine, ' '); if(parameter.size()==0) return FALSE; // es wurden keine Parameter übergeben! // der String des NetTalk-Fensterhandles ist der letzte (oder einzige) Parameter. strNTalkHwnd=parameter[parameter.size()-1]; // Da uns der String aber nicht weiter hilft, müssen wir ihn in eine Zahl umwandeln hNTalkHwnd=(HWND)atol(parameter[parameter.size()-1].c_str()); if( IsWindow(hNTalkHwnd)==0 ) return FALSE; // Parameter beschreibt kein gültiges Fenster! // Plugin bei NetTalk anmelden. "<PluginName> inst <PluginHwnd>" str=strPluginName + string(" inst ") + strPluginHwnd; SendPlain(str); // Jetzt sagen wir NetTalk, auf was für Ereignisse das Plugin reagieren soll. // "<PluginName> addevent <ScriptEvent>" str=strPluginName + string(" addevent Chan_Msg"); SendPlain(str); return TRUE; }// end #StartPlugin()
Schritt 3: Send...
Jetzt will ich mal zu den 'Send…()' Befehlen kommen. Mit deren Hilfe senden Wir Botschaften an NetTalk. 'SendPlain()' Sendet den übergebenen Text dabei so wie er ist an NetTalk. 'SendText()' hingegen löst durch den „send“ Befehl die Scriptfunktion 'PluginEvent()' aus, an welche dann der Parameter 'text' gesendet wird (“<PluginName> send <Text>“). Die Funktion 'SendText()' schreibt zusätzlich auch noch die FrameId (und ein Komma) vor den Text, damit das Script später auch weiß wo es den Text hinsenden soll.
LRESULT SendPlain(char *text) { return SendMessage(hNTalkHwnd, WM_SETTEXT, (WPARAM)(-1), (LPARAM)text); }// end #SendPlain() LRESULT SendPlain(string text){ return SendPlain((char *)text.c_str()); }// end #SendPlain() LRESULT SendText(char *text, int FrameId) { return SendText( string(text) , FrameId); }// end #SendText() LRESULT SendText(string text, int FrameId) { char numbuffer[11]; string str; memset(numbuffer, 0, sizeof(numbuffer)); str=strPluginName; str+=" send "; str+=string( ltoa(FrameId, numbuffer, 10) ); str+=string( "," ); str+=text; return SendMessage(hNTalkHwnd, WM_SETTEXT, (WPARAM)(-1), (LPARAM)str.c_str()); }// end #SendText()
Schritt 4: WinProc
Puh, so weit so gut. Jetzt fehlt natürlich noch die FensterFunktion für die ganzen Nachrichten die während eines Pluginlebens so eintrudeln. An Dieser Stelle sei auch noch etwas angemerkt: Wenn man im NetTalk-Einstellungsfenster bei einem Plugin auf 'Bearbeiten' klickt, wird die Botschaft 'show' an das ausgewählte Plugin gesendet. Damit hat es dann die Möglichkeit ein Fenster mit Optionen zum Script anzuzeigen. Da ein 'schriftliches' Fensterdesign jedoch viel Platz in anspruch nimmt (und unser kleines Plugin keine Optionen braucht) lasse ich es ganz einfach mal weg :D .
LRESULT CALLBACK WinProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: // Wer einen Optionsdialog wünscht, kann das aktuele Fenster dazu // verwenden un an dieser Stelle seine Controls einfügen. return 0; case WM_SETTEXT: // WM_SETTEXT. Über diese Nachricht sendet uns NetTalk seine // Befehle (string im 'lParam'). Um diese Funktion klein zu hal- // ten, leiten wir diesen Parameter einfach weiter: ExecuteNTalkCommand((char *)lParam); return -1; // <- Wichtig case WM_CLOSE: // Wenn beim Optiondialog auf's Kreuz geklickt wird, soll das // Fenster nur unsichtbar werden(und nicht das Programm beenden). ShowWindow(hwnd, SW_HIDE); return 0; case WM_DESTROY: PostQuitMessage(0); // Ok, Finito. Alles hat mal ein Ende ;) return 0; default: return DefWindowProc (hwnd, message, wParam, lParam); }// end switch(message) return 0; }// ebd #WndProc()
Schritt 5: ExecuteNTalkCommand
Jetzt? Nein, wir können es immer noch nicht testen … aber bald :). Wie der Aufmerksame Leser bemerkt hat fehlt immernoch einige Funktionen. 'ExecuteNTalkCommand(char*)' sei als nächstes an der Reihe. Diese Funktion wertet den von NetTalk übergebenen String aus und ruft dann die entsprechenden (eigenen) Funktionen auf.
LRESULT ExecuteNTalkCommand(char *command){ if(command==NULL) return -1; // <-- für den Fall der Fälle // Kommando finden vector<string> parameter; string strCommand; char *endPointer; endPointer=strchr(command,' '); if(endPointer==NULL) { // Nur Kommando, keine Parameter strCommand=string(command); }else{ // Kommando UND Parameter *endPointer=0; strCommand=string( command ); *endPointer=' '; endPointer++; command=endPointer; // Parameter finden tokenizeString(parameter, command, (char)0x1b); } // Kommandos auswerten if(strCommand=="unload"){ // Plugin soll beendet werden SendPlain("LaberPlugin unload"); DestroyWindow(hPluginHwnd); return -1; } if(strCommand=="show"){ // Plugin soll seinen Optionsdialog anzeigen //ShowWindow(hPluginHwnd,SW_SHOW); // <- nur nötig wenn es Optionen gibt return -1; } if(strCommand=="chan_msg"){ // Irgendwer hat in irgendeinem Channel was gesagt if(parameter.size()!=5) return -1; return Chan_Msg(parameter[0],parameter[1],parameter[2],atoi(parameter[3].c_str()),atoi(parameter[4].c_str())); } return -1; }// end #ExecuteNTalkCommand()
Schritt 6: tokenizeString
Ja genau. Jetzt kommt die Funktion, welche die einzelnen Parameter aufsplittet. Sie erkennt Worte in der Zeichenkette 'plainStr', welche durch das Zeichen 'seperator' getrennt sind. Jedes gefundene Wort wird dem Voktor 'tokenList' hinzugefügt
void tokenizeString(vector<string> &tokenList, char *plainStr, char seperator){ int len; char *start, *end; len=strlen(plainStr); start=plainStr; while(1){ end=strchr(start,seperator); if(end==NULL) break; if(end==start){ start++; continue; } *end=0; tokenList.push_back(std::string(start)); *end=seperator; start=end; } if(*start!=0) tokenList.push_back(std::string(start)); }// end #tokenizeString()
Schritt 7: Chan_Msg
So, jetzt kommen wir endlich zu 'Chan_Msg()'. Da wir uns bis hier her so viel Mühe gemacht haben, brauchen wir uns hier kaum noch um etwas zu kümmern, nur darum was denn geschehen soll wenn einer was im Channel sagt. Da es aber ziemlich nervend wäre wenn das Plugin auf jeden Text reagieren würde, soll es in diesem Beispiel nur reagieren wenn ein Bestimmter Nick etwas sagt (Olli935).
LRESULT Chan_Msg(string Text, string Nick, string Channel, int FrameId, int ConnId){ // Wörter/Parameter parsen if(Nick=="Olli935"){ SendText("Schaut mal alle her! Olli hat was gesagt!", FrameId); return -1; } return -1; }// end #Chan_Msg()
Schritt 8: Das Script
An dieser Stelle kann man es schon kompilieren uns ins NetTalk-Verzeichnis kopieren. Denkt aber dran der Datei die Endung '.plg' zu geben (Aus „LaberPlugin.exe“ wird „LaberPlugin.plg“)! Alles was jetzt noch fehlt ist ein Script. Dazu einfach im NetTalk-Menü auf 'Script/Bearbeiten' gehen und folgendes eintippen. Hier gilt zu beachten das ihr den Namen eures Plugins (LaberPlugin) vollständig in Kleinbuchstaben eingeben müsst (laberplugin).
Sub PluginEvent(Plugin, EventId, Data) if Plugin = "laberplugin" AND EventId = 3 then pos=Instr(1,data,",") if pos>0 then FrameId=left(data,pos-1) Data=mid(data,pos+1) Send Data, FrameId end if end if end sub
Fertig. Das ganze macht zwar nicht viel (wohl eher wenig) aber ich denke das ihr jetzt in der Lage sein solltet eigene Plugins für NetTalk zu schreiben. Ebenso müsstet ihr nun auch mit anderen Events arbeitet können, geht dabei einfach genauso vor wie es hier für 'Chan_Msg' gemacht wurde … oder macht es ganz anders wenn es euch nicht gefällt ;). Viel Spaß!
Sie befinden sich hier: start » nettalk » scripting » cppplugintutorial