Jump to content

c++ anfang


c-o-m-m-a-n-d-e-r

Recommended Posts

Moin Leute,

kann mir mal jemand eben die Basics sagen wie ich mit C++ anzufangen habe?

 

Irgendwie ist mir noch nciht klar wie ich auf das COM Object zugreifen kann.

Das ganze ist ja wohl die DVBViewer.exe. Ich nutz Visual Studio 6. Wie mache ich ihm das denn jetzt bekannt ?

 

Steh total auf dem Schlauch. Hab jetzt den ganzen abend damit verbracht hier im board zu gucken und versucht die Beispiele etc. zu verstehen aber ohne Erfolg.

 

Vielen Dank für Hilfe!

 

Hab folgendes probiert:

#include "stdafx.h"
#include <windows.h>
#include <objbase.h>
#include <comutil.h>
#include <stdio.h>
#include <time.h>
#include <locale.h>
#include "initguid.h"


BOOL APIENTRY DllMain( HANDLE hModule, 
				   DWORD  ul_reason_for_call, 
				   LPVOID lpReserved
				 )
{
HRESULT hr = GetActiveObject(CLSID_DVBViewer, NULL, (IUnkown**) (&pUnk));

return true;
}

extern "C"
{
char* __stdcall Copyright()  { return "Christian Brauwers"; }
char* __stdcall Version()	{ return "1.0.0.0"; }
char* __stdcall LibTyp()	 { return "Linux4Media USB Display"; }
char* __stdcall PluginName() { return "Linux4Media Display"; }
}

 

Aber er kennt z.B CLSID_DVBViewer nicht :bye:

Link to comment
Moin Leute,

kann mir mal jemand eben die Basics sagen wie ich mit C++ anzufangen habe?

 

Irgendwie ist mir noch nciht klar wie ich auf das COM Object zugreifen kann.

Das ganze ist ja wohl die DVBViewer.exe. Ich nutz Visual Studio 6. Wie mache ich ihm das denn jetzt bekannt ?

 

Steh total auf dem Schlauch. Hab jetzt den ganzen abend damit verbracht hier im board zu gucken und versucht die Beispiele etc. zu verstehen aber ohne Erfolg.

 

Vielen Dank für Hilfe!

 

Hab folgendes probiert:

#include "stdafx.h"
#include <windows.h>
#include <objbase.h>
#include <comutil.h>
#include <stdio.h>
#include <time.h>
#include <locale.h>
#include "initguid.h"
BOOL APIENTRY DllMain( HANDLE hModule, 
				   DWORD  ul_reason_for_call, 
				   LPVOID lpReserved
				 )
{
HRESULT hr = GetActiveObject(CLSID_DVBViewer, NULL, (IUnkown**) (&pUnk));

return true;
}

extern "C"
{
char* __stdcall Copyright()  { return "Christian Brauwers"; }
char* __stdcall Version()	{ return "1.0.0.0"; }
char* __stdcall LibTyp()	 { return "Linux4Media USB Display"; }
char* __stdcall PluginName() { return "Linux4Media Display"; }
}

 

Aber er kennt z.B CLSID_DVBViewer nicht :bye:

 

1. Du must unterscheiden zwischen Plugin-SDK oder COM-ZUgriff . Beim Plugin wird eine dll erzeugt und Du brauchst Copyright(), Version(), LibTyp() etc. Dies SDK enthält leider kein C++ spezifisches Zeug. Deshalb hier meine DVBViewer.h

#ifndef _DVBVIEWER_H
#define _DVBVIEWER_H

#pragma once


#if ! defined( __cplusplus )
#error C - Compilation! C++ - Compilation required!
#endif

/*
 P. Erward * 28.09.2004, 10.07.07, 15.07.08

 Consts and interfaces for DVBViewer-plugins.

 PAY ATTENTION: This header-file makes use of 'CONSTANT-FOLDING' under C++

*/


#include <windows.h>
#include <time.h> 




const UINT WM_DVBVIEWER		 = 0xB2C2;

// wParam
const WPARAM MSG_REMOTE		 = 0x0815;
const WPARAM MSG_VERSION		= 0x1018;
const WPARAM MSG_INIREFRESH	 = 0x1020;
const WPARAM MSG_EPGSAVE		= 0x1030;
const WPARAM MSG_EPGCHANNEL	 = 0x1031;
const WPARAM MSG_STARTOSD	   = 0x2000;
const WPARAM MSG_STOPOSD		= 0x2001;
const WPARAM MSG_DRAWBLOCK	  = 0x2002;
const WPARAM MSG_SETFONT		= 0x2003;
const WPARAM MSG_SETTEXT		= 0x2004;
const WPARAM MSG_SETSID		 = 0x2100;
const WPARAM MSG_GETSID		 = 0x2101;
const WPARAM MSG_STARTFILTER	= 0x2120;
const WPARAM MSG_STOPFILTER	 = 0x2130;
const WPARAM MSG_GETVTPAGE	  = 0x2200;
const WPARAM MSG_ADDTSCALL	  = 0x2210;
const WPARAM MSG_DELTSCALL	  = 0x2211;
const WPARAM MSG_ADDRAWTSCALL   = 0x2214;
const WPARAM MSG_DELRAWTSCALL   = 0x2215;
const WPARAM MSG_GETCOMPLETEEPG = 0x2216;

const WPARAM MSG_VCR_GETCOUNT   = 0x2300;
const WPARAM MSG_VCR_GETITEM	= 0x2301;
const WPARAM MSG_VCR_SETITEM	= 0x2302;
const WPARAM MSG_VCR_DELETEITEM = 0x2303;

const WPARAM MSG_STARTPIDCALLBACK = 0x45104;
const WPARAM MSG_STOPPIDCALLBACK  = 0x45105;

// lParam
// action codes for MSG_REMOTE 
const LPARAM acPause			= 100;
const LPARAM acOnTop			= 101; 
const LPARAM acHideMenu		 = 102;
const LPARAM acShowStatusbar	= 103;
const LPARAM acToolbar		  = 104;
const LPARAM acFullscreen	   = 105;
const LPARAM acExit			 = 106;
const LPARAM acChannellist	  = 107;
const LPARAM acChanMinus		= 108;
const LPARAM acChanPlus		 = 109;
const LPARAM acChanSave		 = 110;
const LPARAM acFav1			 = 111;
const LPARAM acFav2			 = 112;
const LPARAM acFav3			 = 113;
const LPARAM acFav4			 = 114;
const LPARAM acFav5			 = 115;
const LPARAM acFav6			 = 116;
const LPARAM acFav7			 = 117;
const LPARAM acFav8			 = 118;
const LPARAM acFav9			 = 119;
const LPARAM acStationMinus	 = 120;
const LPARAM acStationPlus	  = 121;
const LPARAM acAspect		   = 122;
const LPARAM acZoom			 = 123;
const LPARAM acOptions		  = 124;
const LPARAM acMute			 = 125;
const LPARAM acVolUp			= 126;
const LPARAM acVolDown		  = 127;
const LPARAM acDisplay		  = 128;
const LPARAM acZoom50		   = 129;
const LPARAM acZoom100		  = 130;
const LPARAM acZoom200		  = 131;
const LPARAM acDesktop		  = 132;
const LPARAM acRecordSettings   = 133;
const LPARAM acRecord		   = 134;
const LPARAM acTeletext		 = 135;
const LPARAM acMiniEPG		  = 136;
const LPARAM acEPG			  = 137;
const LPARAM acFav0			 = 138;
								   // ?? = 139
const LPARAM acChannel0		 = 140;
const LPARAM acChannel1		 = 141;
const LPARAM acChannel2		 = 142;
const LPARAM acChannel3		 = 143;
const LPARAM acChannel4		 = 144;
const LPARAM acChannel5		 = 145;
const LPARAM acChannel6		 = 146;
const LPARAM acChannel7		 = 147;
const LPARAM acChannel8		 = 148;
const LPARAM acChannel9		 = 149;
const LPARAM acTimeShift		= 150;
const LPARAM acTimeShiftWindow  = 151;
const LPARAM acTimeshiftStop	= 152;
const LPARAM acRebuildGraph	 = 153;
const LPARAM acTitlebarHide	 = 154;
const LPARAM acBrightnessUp	 = 155;
const LPARAM acBrightnessDown   = 156;
const LPARAM acSaturationUp	 = 157;
const LPARAM acSaturationDown   = 158;
const LPARAM acContrastUp	   = 159;
const LPARAM acContrastDown	 = 160;
const LPARAM acHueUp			= 161;
const LPARAM acHueDown		  = 162;
const LPARAM acLastChannel	  = 163;
const LPARAM acPlaylist		 = 164;
const LPARAM acPlaylistFirst	= 165;
const LPARAM acPlaylistNext	 = 166;
const LPARAM acPlaylistPrevious = 167;
const LPARAM acPlaylistLast	 = 168;
const LPARAM acPlaylistStop	 = 169;
const LPARAM acPlaylistRandom   = 170;
const LPARAM acHideAll		  = 171;
const LPARAM acAudioChannel	 = 172;
const LPARAM acEnter			= 173;
const LPARAM acRed			  = 174;
const LPARAM acGreen			= 175;
const LPARAM acYellow		   = 176;
const LPARAM acBlue			 = 177;
const LPARAM acOSDUp			= 178;
const LPARAM acOSDDown		  = 179;
const LPARAM acOSDFirst		 = 180;
const LPARAM acOSDLast		  = 181;
const LPARAM acOSDPrevious	  = 182;
const LPARAM acOSDNext		  = 183;
const LPARAM acOSDClose		 = 184;
const LPARAM acOSDPositioning   = 185;


enum TLanguage	{ ENG, ITA, DEU, FRA, SPA, CRO, CZE, SWE, POL, TUR, ROM, RUS, HEB };

enum TShutdown	{ NONE, POWEROFF, STANDBY, HIBERNATE, CLOSE, PLAYLIST, DVBVSTANDBY };

enum TTimerAction { RECORD, TUNE, AUDIOPLUGIN, VIDEOPLUGIN, EPGUPDATE };

enum TEvent	   { UNLOAD = 0, INITCOMPLETE = 3, REMOVECHANNEL = 998, TUNECHANNEL = 999 };

enum TTunerType   { CABLE, SATELLITE, TERRESTICAL, ATSC, IPTV };


typedef char TTunerLanguage[ 3 ];


#pragma pack(1)
struct TMSG_PIDCallback {
 WORD   Pid;
 void * Callback;
 BOOL   Priority;
};
#pragma pack()


#pragma pack(1)
struct TTransCallData {
 void * TransCall;
 DWORD  Param;
};
#pragma pack()


#pragma pack(1)
struct TTStream {
 BYTE Header[ 4 ];
 BYTE Data[ 184 ];
};
#pragma pack()

// Tuner * TODO ungetestet
#pragma pack(1)
struct TTunerOld {
 char  TunerType; 
 DWORD Frequency;
 DWORD SymbolRate;
 DWORD LNB;
 UINT  ECM;
 char  Unused;
 char  AC3;
 DWORD FEC;
 DWORD Polarity; //| Modulation |GuardInterval
 DWORD LNBSelection;
 DWORD DiSEqC;
 DWORD AudioPid;
 DWORD VideoPid;
 DWORD TelePid;
 DWORD ServiceID;
};
#pragma pack()



#pragma pack(1)
struct TTuner {
 BYTE  TunerType;
 BYTE  Group;
 BYTE  _unused1;
 BYTE  Flags;
 DWORD Frequency;
 DWORD SymbolRate;
 WORD  LOF;
 WORD  PMT;
 BYTE  Volume;
 BYTE  _unused2;
 BYTE  SatModulation;
 BYTE  AVFormat;
 BYTE  FEC;
 BYTE  AudioChannel;
 WORD  _unused3;
 BYTE  Polarity;
 BYTE  _unused4;
 WORD  _unused5;
 BYTE  Tone;
 BYTE  EPGFlag;
 WORD  DiSEqCValue;
 BYTE  DiSEqC;
 TTunerLanguage Language;
 WORD  AudioPID;
 BYTE  NetworkNr;
 BYTE  Favourite;
 WORD  VideoPID;
 WORD  TransportStreamID;
 WORD  TelePID;
 WORD  NetworkID;
 WORD  SID;
 WORD  PCRPID;
};
#pragma pack()



// Videotext
/*
 Verwendung:

 TVTPage VTPage;

 VTPage.page	= atoi( _page );
 VTPage.subpage = atoi( _subpage );;

 memset( VTPage.Buffer, 0, sizeof( VTPage.Buffer ) );

 SendMessage( _DVBHandle, WM_DVBVIEWER, MSG_GETVTPAGE, ( LPARAM ) & VTPage ); 
*/
#pragma pack(1)
struct TVTPage {
 WORD page;
 WORD subpage;
 char found;
 char Buffer[ 1000 ];
 TLanguage language;
};
#pragma pack()


// Timereinträge
#pragma pack(1)
struct TVCRStruct_V2 {
 int		  ItemNr;
 char		 Description[ 256 ];
 char		 Channel[ 256 ]; // channel ID
 double	   StartTime;	  // without date, only fractional part!
 double	   EndTime;		// without date, only fractional part!
 double	   Date;
 char /*TShutdown*/	Shutdown;
 char		 _unused;
 char /*TTimerAction*/ TimerAction;
 char		 Days;
 bool		 Enabled;
 bool		 DisableAV;
 char		 _reserved[ 256 ];
 int		  _reserved1; 
 int		  _reserved2;
 int		  _reserved3;
};
#pragma pack()


// EPG
/*
 Verwendung:

 TEPG epg;

 epg.Count   = 0;
 epg.Entries = NULL;

 SendMessage( _DVBHandle, WM_DVBVIEWER, MSG_GETCOMPLETEEPG, ( LPARAM ) & epg );  

 epg.Entries = ( TEPGEntry * ) malloc( epg.Count * sizeof( TEPGEntry ) );

 SendMessage( _DVBHandle, WM_DVBVIEWER, MSG_GETCOMPLETEEPG, ( LPARAM ) & epg );

*/
#pragma pack(1)
struct TEPGEntry {
 unsigned short EventID;
 double		 Date;			 // TDateTime2time_t macht daraus time_t
 double		 Duration;		 // TDateTime2time_t macht daraus time_t
 char		   PresentFollowing;
 char *		 Event;
 char *		 Title;
 char *		 Description;
 int			Charset;
 char		   Content;
 unsigned long  ServiceID;
 unsigned long  Frequency;
};
#pragma pack()


#pragma pack(1)
struct TEPG {
 int	   Count;
 TEPGEntry * Entries;
};
#pragma pack()





// Umwandlung von DVBViewer DateTime-Datentypen (=Delphi) nach time_t
/*
The TDateTime type holds a date and time value.

It is stored as a Double variable, with the date as the integral part,
and time as fractional part.
The date is stored as the number of days since 30 Dec 1899.
Quite why it is not 31 Dec is not clear. 01 Jan 1900 has a days value of 2.

Because TDateTime is actually a double, you can perform calculations on it as if it were a number.
This is useful for calculations such as the difference between two dates.
*/
inline time_t TDateTime2time_t( double DateTime )
{
 const int BASEDELTA	= 25569;		// Days between TDateTime basis (12/30/1899) and time_t basis (1/1/1970)
 const int SECS_PER_DAY = 24 * 60 * 60; // Seconds per day

 // the ( time_t )-cast separates the integer part; fractions of seconds are not returned
 return ( time_t ) ( ( DateTime - BASEDELTA ) * SECS_PER_DAY );
}


extern "C" char * __stdcall Version();
extern "C" char * __stdcall Copyright();
extern "C" char * __stdcall LibTyp();
extern "C" char * __stdcall PluginName();
extern "C" void   __stdcall SetAppHandle( HWND Handle, void * RebuildFunc );
extern "C" void   __stdcall SetMenuHandle( HMENU Menu );
extern "C" void   __stdcall MenuItemClick( int nr );
extern "C" int	__stdcall EventMsg( TEvent Event, void * Data );


#endif // _DVBVIEWER_H

//=== EOF ======================================================================

 

2. Wenn Du über COM gehst kann dies auch eine separate .exe sein. Hier ein Ausriss von mir:

   

 ...

 // Translate server ProgID into a CLSID. ClsidFromProgID
 // gets this information from the registry.
 //
 CLSID clsid;
 CLSIDFromProgID( L"DVBViewerServer.DVBViewer", & clsid );

 IUnknown * pIUnknown; //TODO Freigabe

 HRESULT hr = GetActiveObject( clsid, NULL, & pIUnknown );
 if ( FAILED( hr ) ) {
MessageBox( NULL, "GetActiveObject", "ERROR", MB_OK );
return 2;
 }

 IDispatch * pIDispatch;
 hr = pIUnknown->QueryInterface( IID_IDispatch, ( void * * ) & pIDispatch );
 pIUnknown->Release();
 if ( FAILED( hr ) ) {
MessageBox( NULL, "QueryInterface( IID_IDispatch ... )", "ERROR", MB_OK );
return 3;
 }


 // from IDispatch pointer for IDVBViewer
 //  ... get IDispatch pointer for ITimerCollection as TimerManager-property
 //
 IDispatch * pDispatchTimerManager;
 hr = getItemProperty(  pIDispatch, L"TimerManager", VT_DISPATCH, & pDispatchTimerManager  );
 if ( FAILED( hr ) ) {
MessageBox( NULL, "ITimerCollection - TimerManager", "ERROR", MB_OK );
return 4;
 }

 hr = getItemProperty(  pIDispatch, L"DataManager", VT_DISPATCH, & pDispatchDataManager  );
 if ( FAILED( hr ) ) {
MessageBox( NULL, "IDataManager - DataManager", "ERROR", MB_OK );
return 4;
 }


 // now all needed from IDVBViewer-Interface is available and pIDispatch can be released
 //
 pIDispatch->Release();


 LPOLESTR szMeth = L"Value";
 hr =  pDispatchDataManager->GetIDsOfNames( IID_NULL, & szMeth, 1, LOCALE_SYSTEM_DEFAULT, & dispIDValue );
 if ( FAILED( hr ) ) {
MessageBox( NULL, _com_error( hr ).ErrorMessage(), "", MB_OK );
DebugOut( "Value1 ERROR");
return 5;
 }

 ...

 

Der Code ist insofern _schmutzig_ dass im Fehlerfalle mit return reagiert wird - ohne die ausstehenden Release().

 

Viel Erfolg. erwin

Link to comment

Danke für die vielversprechenden und interessanten Code Beispiele. Ich versuche ebenfalls, von C/C++ aus auf den DVBViewer zuzugreifen. Für allgemeine Dinge (SendCommand etc.) funktioniert das auch.

 

Du hast nicht zufällig auch ein Beispiel, wie man die COM-Events in C/C++ abfangen kann?

Link to comment
Du hast nicht zufällig auch ein Beispiel, wie man die COM-Events in C/C++ abfangen kann?

 

Auf die Schnelle leider nicht.

 

Nachtrag zum obigen Code:

 

HRESULT getItemProperty( IDispatch * pIDispatchItem, LPOLESTR property, VARTYPE vt, void * buffer )
{
 HRESULT	hr;
 DISPID	 dispID;
 DISPPARAMS Param = { NULL, NULL, 0, 0 }; 

 hr = pIDispatchItem->GetIDsOfNames( IID_NULL, & property, 1, LOCALE_SYSTEM_DEFAULT, & dispID );
 if ( FAILED( hr ) ) {
MessageBox( NULL, "GetIDsOfNames - FKT", "ERROR", MB_OK );
return hr;
 }

 VARIANT retVal;
 hr = pIDispatchItem->Invoke( dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, & Param, & retVal, NULL, NULL );
 if ( FAILED( hr ) ) {
MessageBox( NULL, "Invoke - FKT", "ERROR", MB_OK );
return hr;
 }

 switch ( vt ) {

case VT_BSTR: {
  BSTR val = retVal.bstrVal;
  if ( val ) {
	strcpy( ( char * ) buffer, _bstr_t( val ) );
	SysFreeString( val );
  }
  else {
	* ( char * ) buffer = '';
  }

  break;
}

case VT_I4: {
  * ( int * ) buffer = retVal.intVal;
  break;
}

case VT_DATE: {
  * ( double * ) buffer = retVal.date;
  break;
}

case VT_DISPATCH: {
  * ( IDispatch * * ) buffer = retVal.pdispVal;
  break;
}


 }
}

 

mfg erwin

Link to comment

Das sieht doch schon mal nach was aus :bye:

Vielen Dank schonmal....

 

 

Verstehe ich das richtig, wenn ich ein Plugin im Sinne einer DLL schreiben möchte, brauche ich die Plugin SDK und als Einstieg wäre deine header file zu gebrauchen!

 

Ich kann aber auch eine eigenständige Applikation schreiben (Exe) und diese greift über das COM auf den DVBViewer zu? Richtig ?

 

Steige ich mit dem Code wie von dir geschrieben unter 2 in das Object ein oder wie? Muss ich nicht noch irgendwie das DVBViewer com object in meiner Umgebung / Projekt (Visual Studio 6) hinzufügen?

 

Aber danke schon mal, mir wird langsam einiges klarer ;)

Link to comment
Verstehe ich das richtig, wenn ich ein Plugin im Sinne einer DLL schreiben möchte, brauche ich die Plugin SDK und als Einstieg wäre deine header file zu gebrauchen!

Nun ja, Plugin-SDK ist vielleicht etwas übertrieben. Das SDK sind im wesentlichen die Informationen und das Wissen (auch das undokumentierte) wie man auf DVBV-Funktionalität zugreift. Dies geschieht über SendMessage/PostMessage. Was man nun noch braucht sind deren Parameter. Diese Informationen habe ich in C/C++-Syntax im Headerfile zusammengefasst. Zusätzliche Software/Bibliotheken braucht man nicht.

 

Ich kann aber auch eine eigenständige Applikation schreiben (Exe) und diese greift über das COM auf den DVBViewer zu? Richtig ?

Vollkommen. Wobei der COM-Zugriff zwar in der Realisierung komplizierter ist, aber wesentlich mehr Möglichkeiten bietet und zudem auch ganz gut dokumentiert - ist im Unterschied zur Plugin-Schnittstelle.

 

Steige ich mit dem Code wie von dir geschrieben unter 2 in das Object ein

Ja.

 

 

Muss ich nicht noch irgendwie das DVBViewer com object in meiner Umgebung / Projekt (Visual Studio 6) hinzufügen?

Wenn wir von unmagned-code also stinknormalen C/C++ sprechen (und darauf beziehen sich die Codebeispiele), dann nicht. Managed C/C++ habe ich noch nicht auspobiert, sollte aber mehr Komfort bei der Entwicklung bieten. Vieles vom obigen Code ist dann überflüssig, z.B. der ganze IDispatch-Kram.

 

mfg erwin

Link to comment

lol hab mal eben das C# 2k8 Express installiert (bzw noch drauf gehabt) und mal die Referenz hinzugefügt.

 

Bin hiernach vorgegangen : http://lakeuk.wordpress.com/tag/DVBViewer/

 

Und schwupp hab ich ne Kommunikation mit dem DVBViewer....wieso ist das da soooo leicht :bye:;)

Ich wollte jetzt eigentlich noch nicht mit C# anfangen *lach*

Link to comment
Und schwupp hab ich ne Kommunikation mit dem DVBViewer....wieso ist das da soooo leicht :bye:;)

Ja erst wenn manns mal in purem C gesehen hat, weis man, was alles in der C#-IDE hinter den Kulissen abgeht. Für den COM-Zugriff werden z.B Wrapper generiert (diese Interop-Assemblies).

 

Ich wollte jetzt eigentlich noch nicht mit C# anfangen *lach*

Ich glaub Du bist bereits übergelaufen ;-) Ein Zwischending wäre managed C++ ( /clr-Schalter in der Konfiguration setzen). Ist C++ plus (MS-) Spracherweiterung plus die wie bei C# gesehene Komfortablität beim Einbinden von COM. Aber C# ist schon OK!

 

mfg erwin

Link to comment

hehe ... ja also was den komfort angeht ist es mit C# bislang echt recht einfach und ich denke ich werde schneller mit C# sein und das umgesetzt bekommen was ich vorhab als mit C++ was ich an und für sich aber schade finde...

 

Ich denke ich werd mal ein Auge mit auf C# haben, würde mich über ein C++ Lösung ebensofreuen (also wenn noch jemand mehr Tipps hat).

 

Ich hatte gestern irgendwas von IPL Dateien oder so gelesen und gestern abend daheim hätte ich die auch speichern können, jetzt find ich das im VS aber nicht wieder, weil ich gesehen habe ich könnte die dann importieren, evtl ist es genau das was ich Suche fürs C++...

Link to comment
hehe ... ja also was den komfort angeht ist es mit C# bislang echt recht einfach und ich denke ich werde schneller mit C# sein und das umgesetzt bekommen was ich vorhab als mit C++ was ich an und für sich aber schade finde...

 

Ich denke ich werd mal ein Auge mit auf C# haben, würde mich über ein C++ Lösung ebensofreuen (also wenn noch jemand mehr Tipps hat).

 

Ich hatte gestern irgendwas von IPL Dateien oder so gelesen und gestern abend daheim hätte ich die auch speichern können, jetzt find ich das im VS aber nicht wieder, weil ich gesehen habe ich könnte die dann importieren, evtl ist es genau das was ich Suche fürs C++...

 

Wenn Du Visual Studio nutzt (nicht das Express) kannst Du Dir automatisch Wrapper Klassen für die COM-Schnittstellen des DVBViewer erzeugen lassen. Einfach im Projekt eine Klasse hinzufügen. Als Art eine MFC aus Typelib Class wählen. Es geht dann ein Fenster auf, in dem man zunächst das DVBViewer COM Interface auswählt und dann nochmal die einzelnen Schnittstellen, für die eine Klasse erzeugt werden soll.

 

Bei einer DVBViewer Klasse besorgst Du Dir dann erst mal wie oben beschrieben den IDispatch Pointer und rufst dann von für Dein DVBViewer Object die Methode AttachDispatch() auf. Schon steht die Verbindung zum DVBViewer. Nur das mit den COM-Events, das geht leider irgendwie nicht ...

 

CDVBViewer m_DVBViewer;   // Diese Klasse wurde automatisch erzeugt

HRESULT hr;
CLSID clsid;

IUnknown *pIUnknown;
IDispatch *pIDispatch;

CLSIDFromProgID (L"DVBViewerServer.DVBViewer", &clsid);

hr = GetActiveObject (clsid, NULL, &pIUnknown);
if (SUCCEEDED (hr))
{
  hr = pIUnknown->QueryInterface (IID_IDispatch, (void **) &pIDispatch);
  pIUnknown->Release ();

  if (SUCCEEDED (hr))
  {
  m_DVBViewer.AttachDispatch (pIDispatch);

Edited by dbraner
Link to comment
hehe ... ja also was den komfort angeht ist es mit C# bislang echt recht einfach und ich denke ich werde schneller mit C# sein und das umgesetzt bekommen was ich vorhab als mit C++ was ich an und für sich aber schade finde...

Also mit C++ geht es auch komfortabel:

 

1. Neues C++ Project

2. Properties->Configuration Properties->General->Common Language Runtime support=Common Language Runtime Support (/clr)

3. Solution Explorer->Rechte Mouse->References...->Add new Reference->COM -> DVBViewerServer

4. ACHTUNG Code aus dem Bauch heraus - ungeprüft!

 
int _tmain(int argc, _TCHAR* argv[])
{
 //DVBViewerServer::DVBViewer^ dvbv = gcnew DVBViewerServer::DVBViewer;

 // Voraussetzung: aktiver DVBV 
 DVBViewerServer::DVBViewer^ dvbv = ( DVBViewerServer::DVBViewer^ ) System::Runtime::InteropServices::Marshal::GetActiveObject( gcnew System::String("DVBViewerServer.DVBViewer") );
 DVBViewerServer::ITimerCollection^ timers = dvbv->TimerManager;
 printf( "Es gibt %d Timers\n", timers->Count );
 return 0;
}

 

mfg erwin

Edited by erwin
Link to comment

öhm also für C++ hab ich hier das Visual C++ 6 .... --------------- kann es sein das das mit VC++ nicht geht, ich depp hab immer VS geschrieben, dabei ist das ja die VC6 :bye:

 

Ich könnte mir vorstellen sobald ich das mit nem Visual Studio probiere kann ich eure Methoden und vorgehensweisen ausprobieren!

Dann werd ich da heim wohl mal das Studio raus kramen müssen, hab aus Gewohnheit immer das VC installiert gehabt ;) hatte mir das damals als Schüler mal für 230 D-Mark gekauft...ach is das lange her B)

 

@Erwin : Ist das auch für das Visual Studio gedacht?? Oder nutzt du den Borland Builder?

 

@dbraner : das liegt sicher am VC und nicht VS bei mir das das nicht so geht oder?....man könnte ich mir gerade in Arsch beissen ;)

Edited by c-o-m-m-a-n-d-e-r
Link to comment
öhm also für C++ hab ich hier das Visual C++ 6 .... --------------- kann es sein das das mit VC++ nicht geht, ich depp hab immer VS geschrieben, dabei ist das ja die VC6 :bye:

 

Ich könnte mir vorstellen sobald ich das mit nem Visual Studio probiere kann ich eure Methoden und vorgehensweisen ausprobieren!

Dann werd ich da heim wohl mal das Studio raus kramen müssen, hab aus Gewohnheit immer das VC installiert gehabt ;) hatte mir das damals als Schüler mal für 230 D-Mark gekauft...ach is das lange her B)

 

@Erwin : Ist das auch für das Visual Studio gedacht?? Oder nutzt du den Borland Builder?

 

@dbraner : das liegt sicher am VC und nicht VS bei mir das das nicht so geht oder?....man könnte ich mir gerade in Arsch beissen ;)

 

Mein Beispiel ist mit Visual Studio 2005 (oder 2004?). Jedenfalls Studio.

 

Die Version, die erwin verwendet, würde mich auch mal interessieren. Ist das auch ein Visual studio oder was anderes?

Link to comment
Die Version, die erwin verwendet, würde mich auch mal interessieren. Ist das auch ein Visual studio oder was anderes?

 

Visual Studio 2008. Aber die kostenfreie C++-Expressversion sollte dies auch leisten.

 

mfg erwin

Link to comment

Hi!

 

Wenn ich das hier versuche:

 

3. Solution Explorer->Rechte Mouse->References...->Add new Reference->COM -> DVBViewerServer

 

Habe ich in der Auswahl den DVBViewerServer nicht drinnen (in der Auswahlliste tauchen nur MS Access und das Net-Framwork auf)

 

Eine Idee, woran das liegen könnte?

 

Auch wenn ich die Variante von http://lakeuk.wordpress.com/tag/DVBViewer/ bekomme ich eine Fehlermeldung sobald ich auf den Button klicke...

Edited by schindi77
Link to comment
Hi!

 

Wenn ich das hier versuche:

Habe ich in der Auswahl den DVBViewerServer nicht drinnen (in der Auswahlliste tauchen nur MS Access und das Net-Framwork auf)

 

Eine Idee, woran das liegen könnte?

 

Auch wenn ich die Variante von http://lakeuk.wordpress.com/tag/DVBViewer/ bekomme ich eine Fehlermeldung sobald ich auf den Button klicke...

Es heist wohl nicht DVBViewerServer sonder DVBViewer COM Object oder so. Ansonsten muss der DVBV natürlich _installiert_ sein. soll heissen als solches in der Registry als COM-Server registriert sein.

 

mfg erwin

Link to comment

So schau mal einer guck :D Ich hab das Glück über die Firma alles aus der MSDN zu beziehen etc. und hab mir somit mal Visual Studio 2008 installiert und siehe da schon geht das von euch beschriebene ;)

 

Ich hab jetzt noch nicht groß rumprobiert, nur ob ich die Reference etc hinzufügen kann und hier klappts dann auch im C++ ...

 

 

Lag also wirklich am VC6 das ich das nicht konnte!

 

Jetzt kann ich wenigstens schön weiterprobieren!! Über weiter Ideen etc. würde ich mich trotzdem freuen! Ich denke hier gibts einige die den Einstieg in die Plugins via C++ so besser schaffen könnten ;)

 

@Erwin : erklärst du mir wieso du diese ^ in deinem Code hast? Das kenne ich noch nicht!

 

@dbraner : Dein Weg geht soweit auch. Reicht es wenn ich den DVBViewer nehm oder muss ich alle erstellen??

 

Ausserdem wüsste ich gern noch wofür genau

 

IUnknown und IDispatch da sind :)

 

Vielen Dank

Edited by c-o-m-m-a-n-d-e-r
Link to comment
Es heist wohl nicht DVBViewerServer sonder DVBViewer COM Object oder so. Ansonsten muss der DVBV natürlich _installiert_ sein. soll heissen als solches in der Registry als COM-Server registriert sein.

 

mfg erwin

 

Der DVBViewer ist natürlich installiert, läuft auch nebenbei.....

Link to comment
@Erwin : erklärst du mir wieso du diese ^ in deinem Code hast? Das kenne ich noch nicht!

Das gehört ja auch nicht zum normalen C++-Sprachstandard. Dies sind die oben erwähnten Microsoft-spezifischen Spracherweiterungen. ^ ist ein Handle auf ein verwaltetes Objekt. Also sowas wie ein Zeiger *, allerdings referenziert er keine direkte Speicheradresse sondern eben ein Handle. DotNet weiss dann wie es damit umgehen muss.

 

 

Ausserdem wüsste ich gern noch wofür genau

 

IUnknown und IDispatch da sind :D

Das ist jetzt COM-Basiswissen. Lässt sich nicht mit 2, 3 Worten erklären. Google/Wikipedia helfen bestimmt.

 

 

 

mfg erwin

Link to comment

Danke Erwin wieder was gelernt :D

 

 

Achso dann sind IUnknown und IDispatch jetzt nicht DVBViewerspezifisch...gut zu wissen dann kann ich mich ja einlesen ;)

Link to comment
@dbraner : Dein Weg geht soweit auch. Reicht es wenn ich den DVBViewer nehm oder muss ich alle erstellen??

 

Wenn Dir das DVBViewer-Objekt genügt, musst Du natürlich nur die Klasse für dieses Interface erstellen. Den IDispatch-Pointer brauchst Du, um ein Objekt Deiner DVBViewer-Klasse per AttachDispatch() mit dem laufenden DVBViewer zu verbinden. Die Klasse an sich ist ja nur ein Wrapper.

 

Wenn Du jetzt z.B. auf den Datamanager zugreifen willst, brauchst Du auch hierfür eine Wrapperklasse. Dann legst Du ein Objekt Deiner Datamanager-Klasse an und verbindest es mit dem Datamanager im laufenen DVBViewer:

 

CDVBViewer myDVBViewer;
CDatamanager myDatamanager;

myDVBViewer.AttachDispatch (pIDisp);

myDatamanager.AttachDispatch (myDVBViewer.get_Datamanager ());

 

Die Funktion get_Datamanager() - ich glaube die heißt so - liefert das Interface (IDispatch) des Datamanagers. Dieses Interface wird dann mit Deinem Datamanager-Objekt verbunden. Ab diesem Zeitpunkt kannst Du z.B. per

 

myDatamanager.get_Value("#channelno");

 

Auf Werte im Datamanager zugreifen (gültige Werte liefert DVBVSpy).

Link to comment

très bien très bien :D

 

Das bringt mich doch schon mal ein ganzes Stück weiter !!

Ich glaube mit dem Informationsstand sollte ich es schaffen mein Vorhaben zu realisieren!

 

Vielen Dank

Link to comment

Sooo mein erster gehversuch :D

 

Hab Erwins Ansatz mal verfolgt und hab folgenden Code geschrieben:

 

DVBViewerServer::DVBViewer^ dvbv = ( DVBViewerServer::DVBViewer^ ) System::Runtime::InteropServices::Marshal::GetActiveObject( gcnew System::String("DVBViewerServer.DVBViewer") );

DVBViewerServer::IChannelItem^ citem = dvbv->CurrentChannel;
CString cname;
cname.Format("%s",citem->Name);
MessageBox(cname,"Channel",MB_OK);

 

Und siehe da er gibt mir den aktuellen Sendernamen aus. Ich bin begeistert!

Link to comment

weiß jemand wie die senderlogo zuteilung von statten geht? :D

 

Nur Sendername geht ja irgendwie nicht, weil als Beispiel hier mit nem DVB-T Test ZDF "ZDF (deu)" heisst....

 

Sendername und EPG klappt schon hervoragend. Wenn man einmal dieses Grundgerüst gecheckt hat ist es relativ einfach!

 

Aber ich kratze vermutlich auch nur an der Spitze ;) Events etc kommen ja irgendwann auch noch ;)

Link to comment

Bei den Senderlogos wird das genommen wo der Logo Name dem Sender Namen am ähnlichsten ist. Die runden Klammern und der enthaltene Text wird ignoriert.

 

Der Pfad zum passenden Senderlogo müsste sich aber auch über das COM Interface abfragen lassen.

(kann Grade nicht mit DVB Spy nach gucken)

Link to comment

aha also doch über den sendernamen :D gut dann kann ich

 

a) probieren mir das auch daran zurecht zulegen

:) mal mit DVBSpy gucken wie ich das evtl abfragen kann

 

Korrekt?

 

Das TAG wäre dann #channellogo wenn ich das richtig sehe...Jetzt stellt sich mir nur die Frage wie komme ich an das ran? ;)

 

Geht das wie von dbraner beschrieben via dem DataManager und getValue? Ich teste das mal aus ;)

 

 

EDIT : Vermelde Erfolg !!

 

DVBViewerServer::IDataManager^ data  = dvbv->DataManager;
CString logo = data->Value["#channellogo"];

 

logo enthält dann den Pfad...sehr schön danke

Edited by c-o-m-m-a-n-d-e-r
Link to comment
Du hast nicht zufällig auch ein Beispiel, wie man die COM-Events in C/C++ abfangen kann?

Hab mal ein Beispiel, zumindest für C++/CLI, zusammengebastelt.

 

void OnClose()
{
 printf( "DVBViewer closed" ); 
}

int _tmain(int argc, _TCHAR* argv[])
{

 // Voraussetzung: aktiver DVBV 
 DVBViewerServer::DVBViewer^ dvbv = ( DVBViewerServer::DVBViewer^ )  System::Runtime::InteropServices::Marshal::GetActiveObject( gcnew System::String("DVBViewerServer.DVBViewer") );

 DVBViewerServer::DVBViewerEvents^ events = dvbv->Events;
 events->onDVBVClose += gcnew DVBViewerServer::IDVBViewerEvents_onDVBVCloseEventHandler( OnClose );

 getchar();
 return 0;
}

 

mfg erwin

 

PS: Kann man eigentlich den automatischen Zeilenumbruch innerhalb des Code-Femsters irgendwie verhindern?

Link to comment

Krass das ist alles? So kann ich auf bestimme Events vom Viewer reagieren? dann ist das ja doch einfacher als ich dachte :D

 

 

Danke Erwin

Link to comment
Hab mal ein Beispiel, zumindest für C++/CLI, zusammengebastelt.

 

void OnClose()
{
 printf( "DVBViewer closed" ); 
}

int _tmain(int argc, _TCHAR* argv[])
{

 // Voraussetzung: aktiver DVBV 
 DVBViewerServer::DVBViewer^ dvbv = ( DVBViewerServer::DVBViewer^ )  System::Runtime::InteropServices::Marshal::GetActiveObject( gcnew System::String("DVBViewerServer.DVBViewer") );

 DVBViewerServer::DVBViewerEvents^ events = dvbv->Events;
 events->onDVBVClose += gcnew DVBViewerServer::IDVBViewerEvents_onDVBVCloseEventHandler( OnClose );

 getchar();
 return 0;
}

 

mfg erwin

 

PS: Kann man eigentlich den automatischen Zeilenumbruch innerhalb des Code-Femsters irgendwie verhindern?

 

Der Hammer. Darf gar nicht daran denken, wieviel Zeit ich damit schon verbraten habe. Ab und zu ist ein Update auf eine neuere Visual Studio Version doch sinnvoll. Jetzt hoffe ich nur, dass das auch mit Express funktioniert.

 

:D

Link to comment
Jetzt hoffe ich nur, dass das auch mit Express funktioniert.

 

Melde: Funktioniert auch mit Express (Visual Studio 2008 Express Edition, um genau zu sein).

Gestern abend getestet.

Bin noch immer ganz baff, wie einfach das ging. :D

Edited by schindi77
Link to comment

Ausserdem wüsste ich gern noch wofür genau

 

IUnknown und IDispatch da sind smile.gif

Das ist jetzt COM-Basiswissen. Lässt sich nicht mit 2, 3 Worten erklären. Google/Wikipedia helfen bestimmt.

mfg erwin

Ich versuchs trotzdem mal.

 

Stell Dir ein COM-Object wie ein Geschäftshaus vor - z.B. Ullrichhaus (der Name des Objektes). Mit Arztpraxis, Rechtsanwaltkanzlei, etc - die Methoden des Objektes. Du benötigts den Arzt.

 

Los gehts!

 

Aus der Karte (Stadtplan) - der Registry - die Koordinaten bestimmen (Sector E6):

 // Translate server ProgID into a CLSID. ClsidFromProgID
 // gets this information from the registry.
 //
 CLSID clsid;
 CLSIDFromProgID( L"DVBViewerServer.DVBViewer", & clsid );

Sich dorthin begeben:

 IUnknown * pIUnknown; //TODO Freigabe
 HRESULT hr = GetActiveObject( clsid, NULL, & pIUnknown );

Die ehemals unbekannte Eingangstür - pIUnknown - ist jetzt bekannt. Ich stehe davor.

 

An jeder Eingangstür kann ich immer 3 Aktionen ausführen - das IUnknown-Interface.

Ich kann hineingehen - AddRef(). Ich kann hinausgehen - Release(). Oder ich kann das Namesschild lesen um zu wissen wer wo zuhause ist - QueryInterface(). Dort steht z.B.

0. Etage Pförtner/Dispatcher

1. Etage Müller

2. Etage Meier

3. Etage Schulze

Wenn ich weiss das der Arzt z.B. Müller heißt (IID_MUELLER ist bekannt), gehe ich in die 1. Etage und alles ist Paletti und die Methoden des Arztes werden wirksam.

 IArzt * pIArzt;
 hr = pIUnknown->QueryInterface( IID_MUELLER, ( void * * ) & pIArzt );
 pArzt->...

Wenn ich dies nicht weiss gehe ich zum Dispatcher

 IDispatch * pIDispatch;
 hr = pIUnknown->QueryInterface( IID_IDispatch, ( void * * ) & pIDispatch );

und frage ihn nach dem Arzt direkt oder wie im Codebeispiel nach dem Dispatcher des Arztes

 // from IDispatch pointer for Ullrichhaus
 //  ... get IDispatch pointer for IArzt
 //
 IDispatch * pDispatchArzt;
 hr = getItemProperty(  pIDispatch, L"ArztDispatcher", VT_DISPATCH, & pDispatchArzt );

mfg erwin

Link to comment

nicly done.... danke erwin :D

 

Hab gestern abend noch ein wenig programmiert etc. und ich bin ebenfalls noch baff wie vergleichsweise leicht das doch mit ner aktuellen Visual Studio Version geht ;)

Link to comment
und ich bin ebenfalls noch baff wie vergleichsweise leicht das doch mit ner aktuellen Visual Studio Version geht :D

Na ja ich seh das etwas zwiespältig. Mal ne kurze Manöverkritik. Einerseits schnelles Prototyping möglich. Auch für Einsteiger in COM sehr gut geeignet um erstmal schnelle (Erfolgs-)Ergebnisse zu sehen ohne sich um die Details kümmern zu müssen.

 

ABER:

Besinn Dich mal. Was wolltest Du ursprünglich tun? DVBViewer-COM unter C++ nutzen! Um COM zu nutzen ist absolut _kein_ DotNet notwendig. Für welchen Preis hast Du diese Entwicklerbequemlichkeit erreicht? Du erzeugst ein Programm welches eigentlich keine DotNet-Funktionalität benötigt dennoch aber das Framework installiert haben muss, die COM-Funktionalität zur Laufzeit in DotNet konforme Ojektklassen verpackt diese dann wieder über C++/CLI entpackt. Entwicklerschweiss in die Laufzeit verlagert! Unnötige Performanceverluste. Igit! Mein Programmiercredo ist: was zum Entwicklerzeitpunkt getan werden kann (auch wenns Mehraufwannd ist) hat in der Laufzeit nichts zu suchen. Entwickelt wird einmal, die Laufzeit ist u.U Millonenfach zu gange. (Ich kenne noch Programme/Compiler die 'i = 234 * 436;' zur Laufzeit jedesmal neu berechnet haben - mit demselben Ergebnis -, ein Tascherechner vor der Compilerung und alles wäre viel performanter). Auch die C++ -Spracherweiterungen sind so nicht mein Ding zumindest wenn sie durch kein Normierungsgremium abgenickt sind.

 

So ich hoffe Du hast mich jetzt nicht falsch verstanden. Ich will Dich auf dem eingeschlagenen Weg nicht bremsen und das dieser Umweg über DotNet falsch ist, habe ich auch nicht gesagt. Aber ein bischen Rückbesinnung schadet auch nicht bevor man in Euforie verfällt.

 

mfg erwin

Link to comment

Keine Bange, ich geb dir da auch vollkommen Recht :D Und konsturktiver Kritik stehe ich immer offen gegenüber ;)

 

Irgendwie werde ich mich auch noch mal reinfuchsen um das in C++ ohne .NET zu bewerkstelligen, aber für die eigene Zufriedenheit und dem Spieltrieb ist es schöne erstmal etwas funktionieren zu sehen ;)

 

Ich bin aber immer für jede Hilfe offen!

Link to comment
ABER:

Besinn Dich mal. Was wolltest Du ursprünglich tun? DVBViewer-COM unter C++ nutzen! Um COM zu nutzen ist absolut _kein_ DotNet notwendig. Für welchen Preis hast Du diese Entwicklerbequemlichkeit erreicht? Du erzeugst ein Programm welches eigentlich keine DotNet-Funktionalität benötigt dennoch aber das Framework installiert haben muss, die COM-Funktionalität zur Laufzeit in DotNet konforme Ojektklassen verpackt diese dann wieder über C++/CLI entpackt. Entwicklerschweiss in die Laufzeit verlagert! Unnötige Performanceverluste. Igit! Mein Programmiercredo ist: was zum Entwicklerzeitpunkt getan werden kann (auch wenns Mehraufwannd ist) hat in der Laufzeit nichts zu suchen. Entwickelt wird einmal, die Laufzeit ist u.U Millonenfach zu gange. (Ich kenne noch Programme/Compiler die 'i = 234 * 436;' zur Laufzeit jedesmal neu berechnet haben - mit demselben Ergebnis -, ein Tascherechner vor der Compilerung und alles wäre viel performanter). Auch die C++ -Spracherweiterungen sind so nicht mein Ding zumindest wenn sie durch kein Normierungsgremium abgenickt sind.

 

Ich geb Dir ja recht, soll jetzt hier auch keine Grundsatzdiskussion werden. Nur, ich habe es eben über den beschwerlichen Weg versucht (mit den COM-Events). Mit Word oder anderen COM-Servern hab ich das auch zum Laufen gebracht, nur eben nicht mit dem DVBViewer. Insofern ist die .NET-Methode besser als nichts. Jetzt kann ich endlich an meiner Toucscreen-Oberfläche weiterbauen.

Link to comment

ich probier gerade

 

events->OnChannelChange += gcnew DVBViewerServer::IDVBViewerEvents_OnChannelChangeEventHandler(&CdvbtestDlg::ChChange);

 

bekomme aber nen Compilererror

 

Error 1 error C3364: 'DVBViewerServer::IDVBViewerEvents_OnChannelChangeEventHandler' : invalid argument for delegate constructor; delegate target needs to be a pointer to a member function

 

Was mache ich falsch ?

Link to comment
ich probier gerade

 

events->OnChannelChange += gcnew DVBViewerServer::IDVBViewerEvents_OnChannelChangeEventHandler(&CdvbtestDlg::ChChange);

 

bekomme aber nen Compilererror

 

Error 1 error C3364: 'DVBViewerServer::IDVBViewerEvents_OnChannelChangeEventHandler' : invalid argument for delegate constructor; delegate target needs to be a pointer to a member function

 

Was mache ich falsch ?

Ganz auf die Schnelle. Muss gleich weg.

Der OnChannelChangeEventHandler erwartet eine bestimmte Signatur: void OnChannelChange( int nr ); Also 1 Parameter.

Memberfunktionen von Objekten übergeben unsichtbar als ersten Parameter den this-Pointer. Also ein Parameter zu viel.

Um dies zu vermeiden deklariere diese Memberfunktion als 'static'.

 

mfg erwin

Link to comment

hab die Function static gemacht, leider steigt das Programm jetzt dann an der Stelle aus!

 

*grübel*

Link to comment

also irgendwie haut das mit dem EventChange bei mir noch nicht hin...

 

irgendwie scheint der der die changes garnicht zu realisieren...der dvbspy hatte da ab und an aber auch seine probs...

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...