Marshal error when RegisterStreamReadCallback - c++

I'm using C# to call methods from an dll file to record video.
This is the Marshal methods file
http://pastebin.com/YrVvBfZ9
This is my CameraUtilities file
http://pastebin.com/0AZNtnhk
This is my camera file
http://pastebin.com/ZE3HD1zq
When I call StartRecord method (in camera file) to start Record video.
public void StartRecord()
{
if (this.ListCameras != null)
{
bool bRes = true;
try
{
int tmpError = -1;
m_tmpContext = new IntPtr();
m_handle = new GCHandle();
m_callBackChannels = new ulong[m_ListCameras.Length];
for (int i = 0; i < m_ListCameras.Length; i++)
{
m_callBackChannels[i] = 10;
cameraCurrentIndex = 0;
IntPtr channelHandle = T1800.T18_ChannelOpen(cameraCurrentIndex);
m_ListCameras[cameraCurrentIndex].ChannelHandle = channelHandle;
T1800.T18_CaptureIFrame(m_ListCameras[i].ChannelHandle);
m_ListCameras[i].BeginRecord();
if (AllowRecordVideo)
{
m_del = new T1800.STREAM_READ_CALLBACK(StreamReadCallBack);
m_tmpContext = m_ListCameras[i].ChannelHandle;
tmpError = T1800.T18_RegisterStreamReadCallback(m_del, ref m_tmpContext);
}
}
if (tmpError == -1) bRes = false;
}
catch (Exception ex)
{
Log.Logger.Error(ex);
bRes = false;
}
}
}
It throw an exception
- System.Runtime.InteropServices.MarshalDirectiveException: Invalid PInvoke calling convention.
Thiscall requires that the first parameter is present and can be enregistered.
at System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegateInternal(Delegate d)
at System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(Delegate d)
at TH.Parking.Wrapper.HBCamera.T1800.dll_T18_RegisterStreamReadCallback(STREAM_READ_CALLBACK STREAM_READ_CALLBACK, IntPtr& context)
at TH.Parking.Wrapper.HBCamera.CameraUtilities.StartRecord() in d:\TH.Parking\TH.Parking\Wrapper\HBCamera\CameraUtilities.cs:line 93
I can't find any reason for this error. Can somebody help me to fix this.

I think the callback you pass to create the STREAM_READ_CALLBACK object should be static. Otherwise, the thispointer is lost during the marshalling.
Instead of:
private int StreamReadCallBack(ulong channelHandle, IntPtr context)
{
...
}
try:
private static int StreamReadCallBack(ulong channelHandle, IntPtr context)
{
...
}
And you'll need to somehow put your instance of CameraUtility into the context parameter.

Related

Rapidjson returning reference to Document Value

I'm having some trouble with the following method and I need some help trying to figure out what I am doing wrong.
I want to return a reference to a Value in a document. I am passing the Document from outside the function so that when I read a json file into it I don't "lose it".
const rapidjson::Value& CTestManager::GetOperations(rapidjson::Document& document)
{
const Value Null(kObjectType);
if (m_Tests.empty())
return Null;
if (m_current > m_Tests.size() - 1)
return Null;
Test& the_test = m_Tests[m_current];
CMyFile fp(the_test.file.c_str()); // non-Windows use "r"
if (!fp.is_open())
return Null;
u32 operations_count = 0;
CFileBuffer json(fp);
FileReadStream is(fp.native_handle(), json, json.size());
if (document.ParseInsitu<kParseCommentsFlag>(json).HasParseError())
{
(...)
}
else
{
if (!document.IsObject())
{
(...)
}
else
{
auto tests = document.FindMember("td_tests");
if (tests != document.MemberEnd())
{
for (SizeType i = 0; i < tests->value.Size(); i++)
{
const Value& test = tests->value[i];
if (test["id"].GetInt() == the_test.id)
{
auto it = test.FindMember("operations");
if (it != test.MemberEnd())
{
//return it->value; is this legitimate?
return test["operations"];
}
return Null;
}
}
}
}
}
return Null;
}
Which I am calling like this:
Document document;
auto operations = TestManager().GetOperations(document);
When I inspect the value of test["operations"] inside the function I can see everything I would expect (debug code removed from the abode code).
When I inspect the returned value outside the function I can see that it's an array (which I expect). the member count int the array is correct as well, but when print it out, I only see garbage instead.
When I "print" the Value to a string inside the methods, I get what I expect (i.e. a well formated json), but when I do it outside all keys show up as "IIIIIIII" and values that aren't strings show up correctly.
rapidjson::StringBuffer strbuf2;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer2(strbuf2);
ops->Accept(writer2);
As this didn't work I decided to change the method to receive a Value as a parameter and do a deep copy into it like this
u32 CTestManager::GetOperationsEx(rapidjson::Document& document, rapidjson::Value& operations)
{
(...)
if (document.ParseInsitu<kParseCommentsFlag>(json).HasParseError())
{
(...)
}
else
{
if (!document.IsObject())
{
(...)
}
else
{
auto tests = document.FindMember("tests");
if (tests != document.MemberEnd())
{
for (SizeType i = 0; i < tests->value.Size(); i++)
{
const Value& test = tests->value[i];
if (test["id"].GetInt() == the_test.id)
{
const Value& opv = test["operations"];
Document::AllocatorType& allocator = document.GetAllocator();
operations.CopyFrom(opv, allocator); //would Swap work?
return operations.Size();
}
}
}
}
}
return 0;
}
Which I'm calling like this:
Document document;
Value operations(kObjectType);
u32 count = TestManager().GetOperationsEx(document, operations);
But... I get same thing!!!!
I know that it's going to be something silly but I can't put my hands on it!
Any ideas?
The problem in this case lies with the use of ParseInSitu. When any of the GetOperations exist the CFileBuffer loses scope and is cleaned up. Because the json is being parsed in-situ when the buffer to the file goes, so goes the data.

winrt c++/cx concurrency access violation exception

What I'm trying to do is check for the existence of a file in the local folder and then copy it there if it isn't found (the file was previously added to the project as an asset).
Here is the code:
Windows::Storage::StorageFile^ MainPage::GetCustomFileAsync(Platform::String^ fileName)
{
using Windows::Storage::StorageFile;
using Windows::Storage::StorageFolder;
auto localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
auto localTask = concurrency::create_task(localFolder->GetFileAsync(fileName));
StorageFile^ retVal = nullptr;
localTask.then([&](StorageFile^ t){
retVal = t;
}).then([](concurrency::task<void> t)
{
try
{
t.get();
OutputDebugString(L"Found\n");
}
catch (Platform::COMException^ e)
{
OutputDebugString(e->Message->Data());
}
}).wait();
return retVal;
}
StorageFile^ fileVar;
if ((fileVar = this->GetCustomFileAsync("somefile.txt")) == nullptr)
{
String^ path = Windows::ApplicationModel::Package::Current->InstalledLocation->Path + "\\Assets";
concurrency::create_task(Windows::Storage::StorageFolder::GetFolderFromPathAsync(path)).then([](StorageFolder^ folder){
return (folder->GetFileAsync("somefile.txt"));
}).then([](StorageFile^ file){
return (file->CopyAsync(Windows::Storage::ApplicationData::Current->LocalFolder));
}).then([&](StorageFile^ file){
fileVar = file;
OutputDebugString(file->DisplayName->Data());
});
}
What happens is that I get an access violation exception at the point where "file" is being assigned to "fileVar" (because of cross-thread access perhaps?). How to fix this?
Edit: I can't do all the processing there because the file will be accessed many times. In short I need to know when it has been successfully copied and get a handle to it. Here is the code that works
Windows::Storage::StorageFile^ GetFile(Platform::String^ fileName)
{
using Windows::Storage::StorageFile;
using Windows::Storage::StorageFolder;
using Windows::Foundation::AsyncOperationCompletedHandler;
using Windows::Foundation::AsyncStatus;
using Windows::Foundation::IAsyncOperation;
using Platform::String;
auto localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
bool completed = false;
StorageFile^ retVal = nullptr;
localFolder->GetFileAsync(fileName)->Completed = ref new AsyncOperationCompletedHandler<StorageFile^>([&completed, &retVal, &fileName](IAsyncOperation<StorageFile^>^ fileOperation, AsyncStatus status)
{
if (status == AsyncStatus::Error)
{
String^ path = Windows::ApplicationModel::Package::Current->InstalledLocation->Path + "\\Assets";
Windows::Storage::StorageFolder::GetFolderFromPathAsync(path)->Completed = ref new AsyncOperationCompletedHandler<Windows::Storage::StorageFolder^>(
[&completed, &retVal, &fileName](IAsyncOperation<Windows::Storage::StorageFolder^>^ folderOperation, AsyncStatus status)->void{
auto assetFolder = folderOperation->GetResults();
assetFolder->GetFileAsync(fileName)->Completed = ref new AsyncOperationCompletedHandler<Windows::Storage::StorageFile^>([&completed, &retVal, &fileName](IAsyncOperation<Windows::Storage::StorageFile^>^ fileOperation, AsyncStatus status)->void{
auto file = fileOperation->GetResults();
file->CopyAsync(Windows::Storage::ApplicationData::Current->LocalFolder)->Completed = ref new AsyncOperationCompletedHandler<Windows::Storage::StorageFile^>
([&completed, &retVal, &fileName](IAsyncOperation<Windows::Storage::StorageFile^>^ fileOperation, AsyncStatus status)->void {
retVal = fileOperation->GetResults();
completed = true;
});
});
});
}
else
{
retVal = fileOperation->GetResults();
completed = true;
}
});
while (completed == false);
return retVal;
}
Rather than passing a delegate as an argument and returning void, make your method return task<StorageFile^> and then the caller can do a .then() to continue working once the operation has succeeded.
Or if this is exposed as a public WinRT method (not an internal / private C++ method) then use IAsyncOperation<StorageFile^>^ as the return type, and wrap the whole thing in create_async():
IAsyncOperation<StorageFile^>^ DoStuff(params)
{
return concurrency::create_async([params]
{
// function body goes here
});
}
Here's a solution I put together. Two things that are important to know:
When executing an asynchronous operation using concurrency::create_task the async operation(s) can still be executing when the parent function returns. So the captured variables MUST outlive the context of the parent function. Which obviously won't happen if they are being passed by reference. It took a while to realize this.
WinRT imposes certain restrictions on the concurrency runtime. Calling concurrency::task::get() or concurrency::task::wait() will throw an exception in an STA thread, unless the call is in a task continuation.
More information in this post:
http://social.msdn.microsoft.com/Forums/windowsapps/en-US/ae54980b-41ce-4337-a059-2213b549be4b/concurrencyinvalidoperation-when-calling-tasktget?forum=winappswithnativecode
In that case how to know when the function has finished doing it's job? I opted to pass in a callback (AKA delegate).
delegate void FileOperation(Windows::Storage::StorageFile^ file);
void GetFileConcurrency(Platform::String^ fileName, FileOperation^ fileOp)
{
using Windows::Storage::StorageFile;
using Windows::Storage::StorageFolder;
using Platform::String;
auto localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
String^ assetFolderPath = Windows::ApplicationModel::Package::Current->InstalledLocation->Path + "\\Assets";
auto localFolderTask = concurrency::create_task(localFolder->GetFileAsync(fileName));
localFolderTask.then([localFolder, assetFolderPath, fileName, fileOp](concurrency::task<StorageFile^> theTask){
try
{
StorageFile^ theFile = theTask.get();
fileOp(theFile);
}
catch (Platform::Exception^ e)
{
OutputDebugString(e->Message->Data());
auto assetFolderTask = concurrency::create_task(StorageFolder::GetFolderFromPathAsync(assetFolderPath));
assetFolderTask.then([localFolder, assetFolderPath, fileName, fileOp](StorageFolder^ assetFolder){
auto assetFileTask = concurrency::create_task(assetFolder->GetFileAsync(fileName));
assetFileTask.then([localFolder, assetFolderPath, fileName, fileOp](StorageFile^ file){
auto copyFileTask = concurrency::create_task(file->CopyAsync(localFolder));
copyFileTask.then([localFolder, assetFolderPath, fileName, fileOp](StorageFile^ file){
OutputDebugString(file->Path->Data());
fileOp(file);
});
});
});
}
});
}

Calling an external function with GetProcAddress crashes the app

I've been trying to call an external function with the GetProcAddress function but everytime i call the function it crashes the console, ive been looking but in every post i get the same final solution but when i try it in my DLL it crashes the app.
Here's the code:
#include <Windows.h>
#include <vector>
#include "SDK\plugin.h"
typedef void (*logprintf_t)(char* format, ...);
logprintf_t logprintf;
// static void* m_AMXExports[44];
typedef bool (PLUGIN_CALL *ServerPluginLoad_t)(void **data);
typedef void (PLUGIN_CALL *ServerPluginUnload_t)();
typedef unsigned int (PLUGIN_CALL *ServerPluginSupports_t)();
typedef void (PLUGIN_CALL *ServerPluginProcessTick_t)();
typedef int (PLUGIN_CALL *ServerPluginAmxLoad_t)(AMX *amx);
typedef int (PLUGIN_CALL *ServerPluginAmxUnload_t)(AMX *amx);
struct Plugins
{
void* AppData[256];
SUPPORTS_FLAGS FlagSupport;
HMODULE Module;
ServerPluginLoad_t LOAD;
ServerPluginUnload_t UNLOAD;
ServerPluginSupports_t SUPPORTS;
ServerPluginProcessTick_t PROCESSTICK;
// AMX Plugin Interface
ServerPluginAmxLoad_t AMXLOAD;
ServerPluginAmxUnload_t AMXUNLOAD;
};
Plugins* ServerPlugins;
void **ppPluginData ;
extern void *pAMXFunctions;
//native LoadLibrary(libraryname[]);
static cell AMX_NATIVE_CALL my_LoadLibrary(AMX* amx, cell* params)
{
bool validfunc = false;
char *path;
amx_StrParam(amx, params[1], path);
logprintf("Loading plugin %s", path);
ServerPlugins = new Plugins();
ServerPlugins->Module = LoadLibraryA(path);
if (ServerPlugins->Module == NULL)
{
delete ServerPlugins;
logprintf("Failed loading plugin %s (Error: %d)", path, GetLastError());
return 0;
}
logprintf("NULL");
ServerPlugins->LOAD = (ServerPluginLoad_t)GetProcAddress(ServerPlugins->Module, "Load");
ServerPlugins->UNLOAD = (ServerPluginUnload_t)GetProcAddress(ServerPlugins->Module, "Unload");
ServerPlugins->SUPPORTS = (ServerPluginSupports_t)GetProcAddress(ServerPlugins->Module, "Supports");
if (ServerPlugins->LOAD == NULL || ServerPlugins->SUPPORTS == NULL || ServerPlugins->UNLOAD == NULL)
{
logprintf(" Plugin doesnt conform to architecture");
FreeLibrary(ServerPlugins->Module);
delete ServerPlugins;
return false;
}
logprintf("NULL 1");
ServerPlugins->FlagSupport = (SUPPORTS_FLAGS)ServerPlugins->SUPPORTS();
if ((ServerPlugins->FlagSupport & SUPPORTS_VERSION_MASK) > SUPPORTS_VERSION)
{
logprintf("Unsupported Version; unloading.");
FreeLibrary(ServerPlugins->Module);
delete ServerPlugins;
return false;
}
logprintf("NULL 2");
if ((ServerPlugins->FlagSupport & SUPPORTS_AMX_NATIVES) > SUPPORTS_VERSION)
{
ServerPlugins->AMXLOAD = (ServerPluginAmxLoad_t)GetProcAddress(ServerPlugins->Module, "AmxLoad");
ServerPlugins->AMXUNLOAD = (ServerPluginAmxUnload_t)GetProcAddress(ServerPlugins->Module, "AmxUnload");
}
else
{
ServerPlugins->AMXLOAD = NULL;
ServerPlugins->AMXUNLOAD = NULL;
logprintf("Any Abstract Machine has been loaded");
}
logprintf("NULL 3");
if ((ServerPlugins->FlagSupport & SUPPORTS_PROCESS_TICK) != 0)
{
ServerPlugins->PROCESSTICK = (ServerPluginProcessTick_t)GetProcAddress(ServerPlugins->Module, "ProcessTick");
}
else
{
ServerPlugins->PROCESSTICK = NULL;
}
logprintf("NULL 4"); //debugging
ServerPlugins->AppData[PLUGIN_DATA_AMX_EXPORTS] = pAMXFunctions;
ServerPlugins->AppData[PLUGIN_DATA_LOGPRINTF] = &logprintf;
if (!(ServerPlugins->LOAD)(ServerPlugins->AppData)) //i didnt put it as &ServerPlugins->AppData because it causes an error
{
logprintf("Initialized failed loading plugin %s", path);
FreeLibrary(ServerPlugins->Module);
logprintf("NULL 5");
delete ServerPlugins;
return false;
}
logprintf("Plugin %s loaded", path);
return true;
}
//native UnloadLibrary(libraryname[]);
static cell AMX_NATIVE_CALL my_UnloadLibrary(AMX*amx, cell*params)
{
char *path;
amx_StrParam(amx, params[1], path);
ServerPlugins->Module = GetModuleHandle((LPCTSTR)path);
if (ServerPlugins->Module != NULL)
{
ServerPlugins->UNLOAD = (ServerPluginUnload_t)GetProcAddress(ServerPlugins->Module, "Unload");
if (ServerPlugins->UNLOAD != NULL)
{
ServerPlugins->UNLOAD();
FreeLibrary(GetModuleHandleA(path));
logprintf("Library %s has been unloaded correctly", path);
return 1;
}
else
{
logprintf("Unloading library %s failed (Error: %d)", GetLastError());
return 0;
}
}
return 1;
}
PLUGIN_EXPORT bool PLUGIN_CALL Load(void **ppData)
{
pAMXFunctions = ppData[PLUGIN_DATA_AMX_EXPORTS];
logprintf = (logprintf_t)ppData[PLUGIN_DATA_LOGPRINTF];
return 1;
}
PLUGIN_EXPORT void PLUGIN_CALL Unload()
{
}
PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports()
{
return SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES;
}
AMX_NATIVE_INFO projectNatives[] =
{
{ "LoadLibrary", my_LoadLibrary },
{ "UnloadLibrary", my_UnloadLibrary }
};
PLUGIN_EXPORT int PLUGIN_CALL AmxLoad(AMX *amx)
{
return amx_Register(amx, projectNatives, -1);
}
PLUGIN_EXPORT int PLUGIN_CALL AmxUnload(AMX *amx)
{
return AMX_ERR_NONE;
}
You have a memory leak in convertCharArrayToLPCWSTR(). You are never freeing the wchar_t* that you allocate. The convertCharArrayToLPCWSTR() function itself is not needed, you can simply pass the char* path as-is to LoadLibraryA() instead:
char *path;
amx_StrParam(amx, params[1], path);
...
ServerPlugins->Module = LoadLibraryA(path);
You are not checking if ServerPlugins->UNLOAD is successfully loaded by GetProcAddress("Unload") or not.
You are using GetProcAddress("Load") for both ServerPlugins->LOAD and ServerPlugins->AMXLOAD, and GetProcAddress("Unload") for both ServerPlugins->UNLOAD and ServerPlugins->AMXUNLOAD. That is very fishy to me. Does the DLL really use the same exports for AMX and non-AMX entry points? If so, that is very bad design, considering that ServerPluginLoad_t has a very different signature than ServerPluginAmxLoad_t, and the same for ServerPlugin(Amx)Unload_t. That is a corrupted call stack waiting to happen. It would be much safer to have the DLL export separate AmxLoad() and AmxUnload() functions instead.
For that matter, the SUPPORTS_AMX_NATIVES and SUPPORTS_PROCESS_TICK flags are redundant, since GetProcAddress() would tell you if those exports are available or not.
As for the crash when calling ServerPlugins->LOAD, I do not see you initializing ppData with any data before passing it to Load(). Certainly not the PLUGIN_DATA_AMX_EXPORTS and PLUGIN_DATA_LOGPRINTF slots, at least:
ppData[PLUGIN_DATA_AMX_EXPORTS] = pAMXFunctions;
ppData[PLUGIN_DATA_LOGPRINTF] = &logprintf;
if (!(ServerPlugins->LOAD)(ppData))
So even if the call to Load() itself did not crash, the DLL would still likely crash at a later time when it tries to use its local pAMXFunctions and logprintf pointers that were assigned in Load().
For that matter, why are you passing things like that as a void* array instead of a struct? That would have been much safer, eg:
struct PluginInitData
{
void* pAMXFunctions;
logprintf_t logprintf;
...
};
typedef bool (__stdcall *ServerPluginLoad_t)(PluginInitData* data);
PluginInitData pInitData;
pInitData.pAMXFunctions = pAMXFunctions;
pInitData.logprintf = &logprintf;
...
if (!(ServerPlugins->LOAD)(&pInitData))
extern "C" bool __stdcall Load(PluginInitData* data)
{
pAMXFunctions = data->pAMXFunctions;
logprintf = data->logprintf;
...
return true;
}
Update: you have fixed most of the issues I mentioned, but now I see that your my_UnloadLibrary() function is implemented wrong. DO NOT call GetModuleHandle() or GetProcAddress() at all, use the existing Module and UNLOAD pointers that were initialized earlier in my_LoadLibrary().
static cell AMX_NATIVE_CALL my_LoadLibrary(AMX* amx, cell* params)
{
char *path;
amx_StrParam(amx, params[1], path);
...
ServerPlugins->Path = path;
...
}
static cell AMX_NATIVE_CALL my_UnloadLibrary(AMX*amx, cell*params)
{
if (ServerPlugins)
{
if (ServerPlugins->UNLOAD != NULL)
ServerPlugins->UNLOAD();
if (ServerPlugins->Module != NULL)
{
FreeLibrary(ServerPlugins->Module);
ServerPlugins->Module = NULL;
}
logprintf("Library %s has been unloaded", ServerPlugins->Path);
delete ServerPlugins;
ServerPlugins = NULL;
}
return 1;
}
If you are still having problems with Load() crashing, then you are just going to have to use your compiler's debugger to find out what is actually happening at run-time. The code shown so far should not be crashing, so either you have a calling convention mismatch, or a data alignment mismatch, or corrupted memory, or something like that. We can't run you debugger for you.

How to write and read Stream using indy 10.5.5 c++

Hi I have try to read Stream from the server with this code
void __fastcall TForm1::Edit1KeyPress(TObject *Sender, wchar_t &Key)
{
//TMemoryStream *TMS = new TMemoryStream;
TStringStream *TSS = new TStringStream;
AnsiString A,B;
TStream *TS;
INT64 Len;
try
{
if (Key == VK_RETURN)
{
Beep(0,0);
if(Edit1->Text == "mystream")
{
TCPClient1->IOHandler->WriteLn("mystream");
Len = StrToInt(TCPClient1->IOHandler->ReadLn());
TCPClient1->IOHandler->ReadStream(TS,Len,false);
TSS->CopyFrom(TS,0);
RichEdit1->Lines->Text = TSS->DataString;
Edit1->Clear();
}
else
{
TCPClient1->IOHandler->WriteLn(Edit1->Text);
A = TCPClient1->IOHandler->ReadLn();
RichEdit1->Lines->Add(A);
Edit1->Clear();
}
}
}
__finally
{
TSS->Free();
}
}
and every times client try to read stream from the server, compiler says.
First chance exception at $75D89617. Exception class EAccessViolation with message 'Access violation at address 500682B3 in module 'rtl140.bpl'. Read of address 00000018'. Process Project1.exe (6056)
How to handle this?
You are not instantiating your TStream object before calling ReadStream(). Your TS variable is completely uninitialized. ReadStream() does not create the TStream object for you, only writes to it, so you have to create the TStream yourself beforehand.
Given the code you have shown, you can replace the TStream completely by using the ReadString() method instead:
void __fastcall TForm1::Edit1KeyPress(TObject *Sender, wchar_t &Key)
{
if (Key == VK_RETURN)
{
Beep(0,0);
if (Edit1->Text == "mystream")
{
TCPClient1->IOHandler->WriteLn("mystream");
int Len = StrToInt(TCPClient1->IOHandler->ReadLn());
RichEdit1->Lines->Text = TCPClient1->IOHandler->ReadString(Len);
}
else
{
TCPClient1->IOHandler->WriteLn(Edit1->Text);
String A = TCPClient1->IOHandler->ReadLn();
RichEdit1->Lines->Add(A);
}
Edit1->Clear();
}
}

Shell Extension DLL - how to capture the folder path, if user clicks inside the folder empty area?

Using shell extension dll, how to capture the folder path, if user clicks inside the folder empty area?
If you're implementing a shell extension dll, then you get the path in your IShellExtInit::Initialize() method as the pidlFolder parameter.
To make sure your extension is also registered for folder backgrounds, you have to create the appropriate entries also under HKCR\Directory\Background\shellex\ContextMenuHandlers
With VC++ language please reference Winmerge souce code
http://sourceforge.net/p/winmerge/code/HEAD/tree/trunk/ShellExtension/
With C# please reference this article
http://www.codeproject.com/Articles/174369/How-to-Write-Windows-Shell-Extension-with-NET-Lang
and update some place bellow:
At FileContextMenuExt.cs file:
...............
#region Shell Extension Registration
[ComRegisterFunction()]
public static void Register(Type t)
{
try
{
ShellExtReg.RegisterShellExtContextMenuHandler(t.GUID, "Directory",
"CSShellExtContextMenuHandler.FileContextMenuExt Class");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message); // Log the error
throw; // Re-throw the exception
}
}
[ComUnregisterFunction()]
public static void Unregister(Type t)
{
try
{
ShellExtReg.UnregisterShellExtContextMenuHandler(t.GUID, "Directory");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message); // Log the error
throw; // Re-throw the exception
}
}
#endregion
...............
public void Initialize(IntPtr pidlFolder, IntPtr pDataObj, IntPtr hKeyProgID)
{
if (pDataObj == IntPtr.Zero && pidlFolder == IntPtr.Zero)
{
throw new ArgumentException();
}
FORMATETC fe = new FORMATETC();
fe.cfFormat = (short)CLIPFORMAT.CF_HDROP;
fe.ptd = IntPtr.Zero;
fe.dwAspect = DVASPECT.DVASPECT_CONTENT;
fe.lindex = -1;
fe.tymed = TYMED.TYMED_HGLOBAL;
STGMEDIUM stm = new STGMEDIUM();
try
{
if (pDataObj != IntPtr.Zero)
{
// The pDataObj pointer contains the objects being acted upon. In this
// example, we get an HDROP handle for enumerating the selected files
// and folders.
IDataObject dataObject = (IDataObject)Marshal.GetObjectForIUnknown(pDataObj);
dataObject.GetData(ref fe, out stm);
// Get an HDROP handle.
IntPtr hDrop = stm.unionmember;
if (hDrop == IntPtr.Zero)
{
throw new ArgumentException();
}
// Determine how many files are involved in this operation.
uint nFiles = NativeMethods.DragQueryFile(hDrop, UInt32.MaxValue, null, 0);
// This code sample displays the custom context menu item when only
// one file is selected.
if (nFiles == 1)
{
// Get the path of the file.
StringBuilder fileName = new StringBuilder(260);
if (0 == NativeMethods.DragQueryFile(hDrop, 0, fileName,
fileName.Capacity))
{
Marshal.ThrowExceptionForHR(WinError.E_FAIL);
}
this.selectedFile = fileName.ToString();
}
else
{
Marshal.ThrowExceptionForHR(WinError.E_FAIL);
}
}
if (pidlFolder != IntPtr.Zero) {
StringBuilder folderName = new StringBuilder(260);
if (0 == NativeMethods.SHGetPathFromIDList(pidlFolder, folderName))
{
Marshal.ThrowExceptionForHR(WinError.E_FAIL);
}
this.selectedFile = folderName.ToString();
}
}
finally
{
NativeMethods.ReleaseStgMedium(ref stm);
}
}
At ShellExtLib.cs file Add folowing source:
[DllImport("shell32.dll")]
public static extern Int32 SHGetPathFromIDList(
IntPtr pidl, // Address of an item identifier list that
// specifies a file or directory location
// relative to the root of the namespace (the
// desktop).
StringBuilder pszPath); // Address of a buffer to receive the file system
And update RegisterShellExtContextMenuHandler and UnregisterShellExtContextMenuHandler function at ShellExtLib.cs file
public static void RegisterShellExtContextMenuHandler(Guid clsid,
string fileType, string friendlyName)
{
if (clsid == Guid.Empty)
{
throw new ArgumentException("clsid must not be empty");
}
if (string.IsNullOrEmpty(fileType))
{
throw new ArgumentException("fileType must not be null or empty");
}
// If fileType starts with '.', try to read the default value of the
// HKCR\<File Type> key which contains the ProgID to which the file type
// is linked.
if (fileType.StartsWith("."))
{
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey(fileType))
{
if (key != null)
{
// If the key exists and its default value is not empty, use
// the ProgID as the file type.
string defaultVal = key.GetValue(null) as string;
if (!string.IsNullOrEmpty(defaultVal))
{
fileType = defaultVal;
}
}
}
}
else {
// Create the key HKCR\<File Type>\shellex\ContextMenuHandlers\{<CLSID>}.
string keyName1 = string.Format(#"{0}\Background\shellex\ContextMenuHandlers\{1}",
fileType, clsid.ToString("B"));
using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(keyName1))
{
// Set the default value of the key.
if (key != null && !string.IsNullOrEmpty(friendlyName))
{
key.SetValue(null, friendlyName);
}
}
}
// Create the key HKCR\<File Type>\shellex\ContextMenuHandlers\{<CLSID>}.
string keyName = string.Format(#"{0}\shellex\ContextMenuHandlers\{1}",
fileType, clsid.ToString("B"));
using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(keyName))
{
// Set the default value of the key.
if (key != null && !string.IsNullOrEmpty(friendlyName))
{
key.SetValue(null, friendlyName);
}
}
}
public static void UnregisterShellExtContextMenuHandler(Guid clsid,
string fileType)
{
if (clsid == null)
{
throw new ArgumentException("clsid must not be null");
}
if (string.IsNullOrEmpty(fileType))
{
throw new ArgumentException("fileType must not be null or empty");
}
// If fileType starts with '.', try to read the default value of the
// HKCR\<File Type> key which contains the ProgID to which the file type
// is linked.
if (fileType.StartsWith("."))
{
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey(fileType))
{
if (key != null)
{
// If the key exists and its default value is not empty, use
// the ProgID as the file type.
string defaultVal = key.GetValue(null) as string;
if (!string.IsNullOrEmpty(defaultVal))
{
fileType = defaultVal;
}
}
}
}
else {
// Remove the key HKCR\<File Type>\shellex\ContextMenuHandlers\{<CLSID>}.
string keyName1 = string.Format(#"{0}\Background\shellex\ContextMenuHandlers\{1}",
fileType, clsid.ToString("B"));
Registry.ClassesRoot.DeleteSubKeyTree(keyName1, false);
}
// Remove the key HKCR\<File Type>\shellex\ContextMenuHandlers\{<CLSID>}.
string keyName = string.Format(#"{0}\shellex\ContextMenuHandlers\{1}",
fileType, clsid.ToString("B"));
Registry.ClassesRoot.DeleteSubKeyTree(keyName, false);
}