In-Proc COM object sharing across another Process - c++

Before I ask this question I would like to make it clear that I know there are libraries and techniques for Inter process communcation. This though, is a learning question about COM.
I also do know about out-of-proc servers but that's not what I am looking for.
The question:
What I want to know, because I don't know this, is it possible, and if yes how, to share an in-proc COM object (object defined in a DLL) living in one process (has been instantiated in the process) across another process? Ie, how do I obtain a pointer to the in-proc object from proces A in process B?
Thanks in advance.

Yes, it's possible. The underlying principle is the same regardless of whether you are sharing a single object instance between apartments in a single process, or between separate processes.
There's two approaches here: perhaps the simplest is to use the Running Object Table: this is essentially a workstation-wide table of named COM objects. You have one process add an object to the table with a well-known name, and have the other process look up that object.
The other approach is to use marshaling. Marshaling is the process of using a COM API to get a series of bytes that describe the location of an object. You can then copy that series of bytes to another process using any means you want to (shared memory, file, pipe, etc), and then use another COM API in the receiving process to unmarshal the object; COM then creates a suitable remoting proxy in that process that communicates back to the original one. Check out the APIs CoMarshalInterface and CoUnmarshalInterface for more details.
Note that both of these require that you have suitable remoting support in place for the object; the interfaces you are using need to be described in IDL and compiled and registered appropriately.
--
I don't have code handy for either of these cases unfortunately.
For the CoMarshalInterface approach, the process is something like:
Use CreateStreamOnHGlobal (with NULL hglobal) to create an IStream that's backed by a HGLOBAL that COM allocates as needed
Use CoMarshalInterface to marshal the interface pointer to the stream (which in turn writes it to the memory backed by the HGLOBAL)
Use GetHGlobalFromStream to get the HGLOBAL from the stream
Use GlobalLock/GlobalSize to lock the HGLOBAL and access the marhaled data (GlobalUnlock when done)
Use whatever means you want to to copy the bytes to the target process.
On the far side, use:
GlobalAlloc/GlobalLock/GlobalUnlock to create a new HGLOBAL and populate it with the marshaled data
CreateStreamOnHGlobal with your new HGLOBAL
Pass this stream to CoUnmarshalInterface
Normal COM and Windows refcounting/resource rules apply across all of this; AddRef/Release as appropriate; use GlobalFree to free any HGLOBALs that you allocate, etc.

There is also another possible solution using the window message WM_GETOBJECT:
In the application, which has the object, you simply create a window with your own class. In the handler, you need to handle the window message like this (I use IDispatch as example interface):
LRESULT WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_GETOBJECT:
{
if(lParam == OBJID_NATIVEOM)
{
return LresultFromObject(IID_IDispatch, wParam, g_MyGlobalIDispatchPointer);
}
else
{
// Not handled
break;
}
}
return 0;
}
// Default
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
The other application must find that specific window an get the object via AccessibleObjectFromWindow
In example:
HWND hWndCommunicator = FindWindow(_T("MyWindowClassOfTheOtherApplication"), _T("MyWindowTitleOfTheOtherApplication"));
if(hWndCommunicator)
{
IDispatch* poObject = nullptr;
HRESULT hr = AccessibleObjectFromWindow(hWndCommunicator, static_cast<DWORD>(OBJID_NATIVEOM), IID_IDispatch, &poWindow);
if(SUCCEEDED(hr))
{
// Do something with the object of the other process
// i. e. poObject->Invoke
}
}
Marshalling is automatically done using this solution.

That's what CoRegisterClassObject and CoGetClassObject is for.

Related

What is the `uIdSubclass` in the third `SetWindowSubclass` Parameter?

According to: http://msdn.microsoft.com/en-us/library/windows/desktop/bb762102(v=vs.85).aspx
the SetWindowSubclass prototype is:
BOOL SetWindowSubclass(
__in HWND hWnd,
__in SUBCLASSPROC pfnSubclass,
__in UINT_PTR uIdSubclass,
__in DWORD_PTR dwRefData
);
Ok, I understand hWnd, pfnSubclass, and dwRefData.
What I can not find good information on is, what do I set uIdSubclass to?
MSDN says:
The subclass ID. This ID together with the subclass procedure uniquely identify
a subclass. To remove a subclass, pass the subclass procedure and this value to the
RemoveWindowSubclass function. This value is passed to the subclass procedure in the
uIdSubclass parameter.
Ok, understood, but still, where do I get this ID? Is it something I create or do I get it someplace? If it something I create, what should it look like?
I am doing this in C++ and Win32 API, nothing else.
Thanks.
Karl E. Peterson writes about the purpose of the uIdSubclass parameter in Visual Studio Magazine 07/16/2009 Subclassing the XP Way and provides an example named HookXP
Topics discussed in Karl E. Peterson's article:
Use uIdSubclass to store a this pointer [or HWND].
Use dwRefData to pass optional data, e.g. pointer to create data.
It is important to handle WM_NCDESTROY in pfnSubclass and call
RemoveWindowSubclass() passing uIdSubclass used in call to SetWindowSubclass().
Quotes from Karl E. Peterson's article
Each subclass is identified two pieces of data along with the window's hWnd -- those being a pointer to the new message-handling procedure and a unique ID that's up to you to generate. You may also select to pass one Long value along as an extra parameter to each callback, for whatever purpose you may desire. The thought that immediately struck me was to use an ObjPtr() as the unique ID for each subclass. What object? The one that will be handling the callback!
About the only undocumented caveat I've run into is that you must be sure to unhook your subclass before a window is destroyed. If you use my technique of embedding the handler in a form or class that's destroyed during the Form_Unload method, this should never be a problem. If you really don't want to take any chances, you can insert a simple branch in your handling routine:
Select Case uiMsg
Case WM_THIS
'
Case WM_THAT
'
Case WM_NCDESTROY
Call Unhook ' !!!
End Select
The Explorer Browser Search Sample also has a example of using uIdSubclass to store a pointer to this.
void CExplorerBrowserSearchApp::_OnInitializeDialog()
{
...
// Register the searchbox icon to receive hover and mouseclick events
SetWindowSubclass(GetDlgItem(_hdlg, IDC_SEARCHIMG), s_SearchIconProc, (UINT_PTR)this, 0);
...
}
Raymond Chen's blog "The Old New Thing" article Safer subclassing also mentions the importance handling WM_NCDESTROY in pfnSubclass for the purpose of calling RemoveWindowSubclass().
Quote from Raymond Chen's article
One gotcha that isn’t explained clearly in the documentation is that you must remove your window subclass before the window being subclassed is destroyed. This is typically done either by removing the subclass once your temporary need has passed, or if you are installing a permanent subclass, by inserting a call to RemoveWindowSubclass inside the subclass procedure itself:
case WM_NCDESTROY:
RemoveWindowSubclass(hwnd, thisfunctionname, uIdSubclass);
return DefSubclassProc(...);
As a side note the Microsoft SetWindowSubclass documentation has the caveat of SetWindowSubclass() being limited to a per thread basis.
Warning You cannot use the subclassing helper functions to subclass a window across threads.

How to write simple background thread in CWorkerThread

I'm trying to asynchronously run function in my add-on for Internet Explorer (I'm writing BHO in VC++). As suggested here I'm trying to use CWorkerThread.
I've been trying to figure it out for hours but still have no idea how to do it. I don't have much experience in ATL. The lack of a good documentations or tutorials on Internet is killing me.
I'm creating class by Add->Class and choosing ATL Simple Object (that's how you add classed to ATL project right?). But how to implement this IWorkerThreadClient? I thought that choosing Add->Implement Interface in Class View would be good but there is no IWorkerThreadClient on the list.
I think I don't know ATL or COM enaugh but can't find good resource for learning this (esspessialy newest ATL7).
I even tried winapi CreateThread approach but it isn't working. I'm passing this class pointer to run static method but something is corrupting with memory later. Nevertheless if It had worked I still would rather use something else than CreateThread.
Right now I have something like this. In OnDocumentComplete there's RemoveImages(sptmlDoc) and I just want to run it asynchronously.
EDIT: What I did with CreateThread:
I tried running RemoveImages function (from here) asynchronously. I created static function in my class with signature like here. RemoveImages has parameter so I copied it to a member of a class:
if (htmlDoc2 != NULL)
{
m_tmpHtmlDocument2 = htmlDoc2;
m_hThread = CreateThread( NULL, 0, MyThreadFunction, this, 0, &m_threadId);
}
and MyThreadFunction:
static DWORD WINAPI MyThreadFunction( LPVOID lpParam )
{
CHelloWorldBHO* myClass = (CHelloWorldBHO*)lpParam;
myClass->RemoveImages(myClass->m_tmpHtmlDocument2);
return 0;
}
I get "Unhandled exception at 0x60c0da05 in iexplore.exe: 0xC0000005: Access violation reading location 0x000001b8." here in the bold line:
void CHelloWorldBHO::DontDisplayElement(CComPtr htmlElement)
{
CComPtr style;
HRESULT hr = htmlElement->get_style(&style);
if (hr == S_OK && style != NULL)
{
static const CComBSTR strNone(L"none");
style->put_display(strNone);
}
}
Your performing a naughty by trying to use a COM handle allocated in 1 thread in another. BHO environment is STA (Single Threaded Apartment) so you should be marshalling the m_tmpHtmlDocument2 object for use in your thread.
Experiance has shown that in some cases IE may let you get away with passing the Browser com object from 1 thread to another and then getting the document and elements afterwards may work. This is entirely unreliable.
Depending on IE 6/7/8 you will have different target threads to execute your actions on, thinking at the levels of per security level/frame/tab/window. basically any time IE creates a new 'Site'
Also to prevent your app from holding the pages active even after navigation away from the page, in FireFox you would use an nsWeakPointer<> , I've never found the equivelant in IE.
Suggestion: Perhaps instead of marshalling com to another thread because your interaction with the page is slow, trying to improve the way you interact with the page and improve performance in process might be a better aim.
Here is an outline using the CThreadPool which will queue up requests, and then execute them when the pool has space.
I use pvWorkerParam to tie the threads back to the site.
I have different types of ActionRequests, you could of course simplify and just pass null for the request.
Note: This doesn't resolve marshalling issues you already have
class ActionRequest{
DontDisplayElement();// define your do stuff in here
};
class ScriptWorker
{
public:
ScriptWorker(void);
virtual ~ScriptWorker(void);
public:
BOOL Initialize(void* pvWorkerParam);
void Execute(ActionRequest *request, void* pvWorkerParam, OVERLAPPED* pOverlapped){
try{
std::auto_ptr<ActionRequest> cleanupRequest(request);
request.DontDisplayElement();
} catch(...) {}
}
void Terminate(void* pvWorkerParam);
private:
boolean m_bCoUninit;
};
Site{
CThreadPool<ScriptWorker> m_scriptWorkerThread;
Site() {
void *pvWorkerParam = this;// or whatever you want to have passed to every script worker and execute in the pool.
m_scriptWorkerThread.Initialize( pvWorkerParam, 1 );
}
OnDocumentComplete() {
m_scriptWorkerThread.QueueRequest( new ActionRequest() );
}
}
and sptmlDoc - is it an IHTMLDocumet* ?
IWorkerThreadClient - never heard of it
"I even tried winapi CreateThread approach but it isn't working. I'm passing this class pointer to run static method but something is corrupting with memomory later"
Keeping it simple is the best design pattern of them all. So stick with CreateThread unless you have good reasons not to. Now, my guess is that the crash occurs because of sptmlDoc being passed to the thread for later processing. The thing is such pointers are only valid from the BeforeNavigate event until DocumentComplete event. Try to do that processing on the spot (inside your event handler) and see if it stil crashes. Some code posting would help too

Problem - TCHAR as LPARAM to a window that belongs to another process/thread

So i am playing/implementingtomyown with windows via c book examples and there is something about dll injection part that boggles me and i can't solve it.
I created a dialog that belongs to another thread/process and i am trying to send it TCHAR variable so it can then use that var in some function(both the function and tchar are in the same dll file)
So when the dialog is created and sitting well in another thread i send it a message.
First i declare tchar
TCHAR finalpath[MAX_PATH];
Then later i just fill it with info( i do this in the dll thread, not in the dialog's thread, let me also mention that i must do this in the dll thread because thats only way to fill the required tchar(i am required to get dll working directory and fill it in tchar))
So, when i get this info in my tchar i am trying to send a message to the dialog and use tchar as LPARAM(wparam is hwnd btw)
SendMessage(hWndDIPS, WM_APP, (WPARAM) lista, (LPARAM)finalpath);
Afterwards i do basic schoolwork in another threads dialog procedure loop...
INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
chHANDLE_DLGMSG(hWnd, WM_CLOSE, Dlg_OnClose);
case WM_APP:
SaveListViewItemPositions((HWND) wParam, (TCHAR)lParam);
break;
}
return(FALSE);
}
Function that is supposed to receive the parameter(this function resides in shared dll and is called by the procedure as you see above is defined as follows..
void SaveListViewItemPositions(HWND hWndLV, TCHAR sejv[]) {
...}
The compiler error i get from this is
Error 7 error C2664: 'SaveListViewItemPositions' : cannot convert parameter 2 from 'TCHAR' to 'TCHAR []'
So i have no idea why is this happening. If tchar is array then i need to use it in parameters with [] added as thats how arrays are used in parameters(not to mention that if i dont do it it gives me more errors and i cant use the parameter in function anyways)
So why is it not converting then?
If there is another solution to make this dialog to receive a tchar var then please explain.
Thanks
Even after you'll fix your type declarations and properly cast the LPARAM to a TCHAR*, your code will be incorrect. The 'parameter' you pass in to that window procedure is a pointer, and as any pointer, is only valid within a process address space. The receiver window will have to use ReadProcessMemory and copy the string from your process into its own process. Of course, this implies that the receiver process knows your process id, and has proper privileges to be able to read from your memory. And you also need to pass in the length of the string, since ReadProcessMemory cannot guess where the NULL terminator is (although I reckon that with a MAX_PATH max length, this is not a serious issue).
So you are correct, this is a headache, and more so down the road. The privilege issue may be a show stopper.
There are several IPC mechanisms you could use. An easy one is an anonymous named pipe, see Anonymous Pipe Operations. Shared memory is another, see Using Shared Memory in a Dynamic-Link Library. COM would also work (have the process you 'control' create an instance of a class that is hosted in your process server, and let the COM marshaling do the rest, see Marshaling Details). Or you could hand-marshal a COM interface between the process boundary (see CoMarshalInterface).
I think that your problem is that you're typecasting the LPARAM to a TCHAR instead of an array of TCHARs (TCHAR*). Try changing that and see if it fixes things.

Hooking DirectX EndScene from an injected DLL

I want to detour EndScene from an arbitrary DirectX 9 application to create a small overlay. As an example, you could take the frame counter overlay of FRAPS, which is shown in games when activated.
I know the following methods to do this:
Creating a new d3d9.dll, which is then copied to the games path. Since the current folder is searched first, before going to system32 etc., my modified DLL gets loaded, executing my additional code.
Downside: You have to put it there before you start the game.
Same as the first method, but replacing the DLL in system32 directly.
Downside: You cannot add game specific code. You cannot exclude applications where you don't want your DLL to be loaded.
Getting the EndScene offset directly from the DLL using tools like IDA Pro 4.9 Free. Since the DLL gets loaded as is, you can just add this offset to the DLL starting address, when it is mapped to the game, to get the actual offset, and then hook it.
Downside: The offset is not the same on every system.
Hooking Direct3DCreate9 to get the D3D9, then hooking D3D9->CreateDevice to get the device pointer, and then hooking Device->EndScene through the virtual table.
Downside: The DLL cannot be injected, when the process is already running. You have to start the process with the CREATE_SUSPENDED flag to hook the initial Direct3DCreate9.
Creating a new Device in a new window, as soon as the DLL gets injected. Then, getting the EndScene offset from this device and hooking it, resulting in a hook for the device which is used by the game.
Downside: as of some information I have read, creating a second device may interfere with the existing device, and it may bug with windowed vs. fullscreen mode etc.
Same as the third method. However, you'll do a pattern scan to get EndScene.
Downside: doesn't look that reliable.
How can I hook EndScene from an injected DLL, which may be loaded when the game is already running, without having to deal with different d3d9.dll's on other systems, and with a method which is reliable? How does FRAPS for example perform it's DirectX hooks?
The DLL should not apply to all games, just to specific processes where I inject it via CreateRemoteThread.
You install a system wide hook. (SetWindowsHookEx) With this done, you get to be loaded into every process.
Now when the hook is called, you look for a loaded d3d9.dll.
If one is loaded, you create a temporary D3D9 object, and walk the vtable to get the address of the EndScene method.
Then you can patch the EndScene call, with your own method. (Replace the first instruction in EndScene by a call to your method.
When you are done, you have to patch the call back, to call the original EndScene method. And then reinstall your patch.
This is the way FRAPS does it. (Link)
You can find a function address from the vtable of an interface.
So you can do the following (Pseudo-Code):
IDirect3DDevice9* pTempDev = ...;
const int EndSceneIndex = 26 (?);
typedef HRESULT (IDirect3DDevice9::* EndSceneFunc)( void );
BYTE* pVtable = reinterpret_cast<void*>( pTempDev );
EndSceneFunc = pVtable + sizeof(void*) * EndSceneIndex;
EndSceneFunc does now contain a pointer to the function itself. We can now either patch all call-sites or we can patch the function itself.
Beware that this all depends on the knowledge of the implementation of COM-Interfaces in Windows. But this works on all windows versions (either 32 or 64, not both at the same time).
I know this question is old, but this should work for any program using DirectX9, You are creating your own instance basically, and then getting the pointer to the VTable, then you just hook it. You will need detours 3.X btw:
//Just some typedefs:
typedef HRESULT (WINAPI* oEndScene) (LPDIRECT3DDEVICE9 D3DDevice);
static oEndScene EndScene;
//Do this in a function or whatever
HMODULE hDLL=GetModuleHandleA("d3d9");
LPDIRECT3D9(__stdcall*pDirect3DCreate9)(UINT) = (LPDIRECT3D9(__stdcall*)(UINT))GetProcAddress( hDLL, "Direct3DCreate9");
LPDIRECT3D9 pD3D = pDirect3DCreate9(D3D_SDK_VERSION);
D3DDISPLAYMODE d3ddm;
HRESULT hRes = pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm );
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp));
d3dpp.Windowed = true;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = d3ddm.Format;
WNDCLASSEX wc = { sizeof(WNDCLASSEX),CS_CLASSDC,TempWndProc,0L,0L,GetModuleHandle(NULL),NULL,NULL,NULL,NULL,("1"),NULL};
RegisterClassEx(&wc);
HWND hWnd = CreateWindow(("1"),NULL,WS_OVERLAPPEDWINDOW,100,100,300,300,GetDesktopWindow(),NULL,wc.hInstance,NULL);
hRes = pD3D->CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_DISABLE_DRIVER_MANAGEMENT,
&d3dpp, &ppReturnedDeviceInterface);
pD3D->Release();
DestroyWindow(hWnd);
if(pD3D == NULL){
//printf ("WARNING: D3D FAILED");
return false;
}
pInterface = (unsigned long*)*((unsigned long*)ppReturnedDeviceInterface);
EndScene = (oEndScene) (DWORD) pInterface[42];
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)EndScene, newEndScene);
DetourTransactionCommit();
And then your function:
HRESULT WINAPI D3D9Hook::newEndScene(LPDIRECT3DDEVICE9 pDevice)
{
//Do your stuff here
//Call the original (if you want)
return EndScene(pDevice);
}
A slightly old question I know - but in case anyone is interested in doing this with C#, here is my example on hooking the Direct3D 9 API using C#. This utilizes EasyHook an open source .NET assembly that allows you to 'safely' install hooks from managed code into unmanaged functions. (Note: EasyHook takes care of all the issues surrounding DLL injection - e.g. CREATE_SUSPENDED, ACL's, 32 vs 64-bit and so on)
I use a similar VTable approach as mentioned by Christopher via a small C++ helper dll to dynamically determine the address of the IDirect3DDevice9 functions to hook. This is done by creating a temporary window handle, and creating a throw-away IDirect3Device9 within the injected assembly before then hooking the desired functions. This allows your application to hook a target that is already running (Update: note that this is possible entirely within C# also - see comments on the linked page).
Update: there is also an updated version for hooking Direct3D 9, 10 and 11 still using EasyHook and with SharpDX instead of SlimDX

How to send a link to an application, like Spotify does

When we save a level in our editor, we create a log file of any errors it contains. These consist basically of an error message and a path that allows the user to find the erronous item in a tree view.
What I want is to make that path a link, something like
< a href="editor://path/to/gameobject" > Click to see object in editor< /a >
The SO questions I've seen regarding this seems to point to this msdn page:
http://msdn.microsoft.com/en-us/library/aa767914.aspx
But from what I can tell, it will spawn a new instance of the application. What I want to do is to simply "call" our editor somehow. One way to do it, I guess, is to spawn it, and in the beginning check if there's already an instance running, and if so, send the commandline to it.
Is that the best way to do it? If so, any ideas on how to do it best? What are otherwise some ways that this could be done?
Also: does the msdn solution work across browsers? Our editor runs in Windows only, but people use IE, Fx, GC and Opera.
If you need the link to work in any viewer, yes, registering a protocol handler is the best way.
As for launching the editor, you could implement it as an out-of-process COM server, but if you've already got command line parsing sorted, you might as well use a window message or named pipe to pass that to the editor. If you're sending a window message, you could use FindWindow (with a unique class name) to check for a running instance.
Sounds like you solved it already, by checking for a previous instance.
I would be surprised if the OS takes upon it to somehow "stamp" the association with data telling it to separate programs that should run multiple times from programs that should not.
Here's how I solved it. Basically, there are two parts. Or three.
First, the app needs to register itself in the registry, like this. It took some googling to find out how to use the windows register functions, but they were pretty straightforward. By adding this to the registry, your application will launch when a link with your custom url protocol is clicked.
Secondly, the app needs to detect that it's been started from a browser. Obviously quite trivial, just check the command line for "/uri" or however you chose to customize it.
Third, you don't actually want to start your application - it should already be running! Instead, when you've detected that you got started from a hyperlink, you need to detect if another instance of the application is already running. After that, you need to pass the command line to it. Here's how I did it:
bool ShouldContinueStartEditor( const std::string& command_line )
{
// Check if this instance was spawned from a web browser
if ( command_line.find( "/uri" ) != std::string::npos )
{
// Try to find other instance of JustEdit
HWND wnd = FindWindow( "AV_MainFrame", NULL );
if ( wnd )
{
COPYDATASTRUCT cds;
NEditorCopyData::SCommandLine data_to_copy;
strncpy( data_to_copy.m_CommandLine, command_line.c_str(), sizeof(data_to_copy.m_CommandLine) - 2 );
cds.dwData = NEditorCopyData::ECommandLine; // function identifier
cds.cbData = sizeof( data_to_copy ); // size of data
cds.lpData = &data_to_copy; // data structure
SendMessage( wnd, WM_COPYDATA, NULL, (LPARAM) (LPVOID) &cds );
}
return false;
}
return true;
}
"AV_Mainframe" is the name of the hwnd. If you happen to be using WTL, you can declare it like this.
DECLARE_FRAME_WND_CLASS("AV_MainFrame", IDR_MAINFRAME)
Now, in your window class, you need to handle the WM_COPYDATA message like this:
MESSAGE_HANDLER(WM_COPYDATA, OnCopyData);
LRESULT OnCopyData(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
LRESULT CMainFrame::OnCopyData(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
{
PCOPYDATASTRUCT cds = (PCOPYDATASTRUCT) lParam;
if ( cds->dwData == NEditorCopyData::ECommandLine )
{
NEditorCopyData::SCommandLine* command_line = static_cast( cds->lpData );
const char* internal_path = strstr( command_line->m_CommandLine, "/uri" );
if ( internal_path != NULL )
{
// Do your thang
}
}
return 0;
}
And that's pretty much all there's to it. Oh, this is what the copy data namespace looks like:
namespace NEditorCopyData
{
enum ECopyDataMessages
{
ECommandLine = 0
};
struct SCommandLine
{
char m_CommandLine[512];
};
}