Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
534 changes: 268 additions & 266 deletions ColumnMode/ColumnMode.rc

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions ColumnMode/ColumnMode.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,33 @@
<ProjectGuid>{6546B3A0-08BA-43DD-A450-B2DE1262820A}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>ColumnMode</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>ColumnMode</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
Expand Down Expand Up @@ -143,6 +143,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
Expand Down
26 changes: 25 additions & 1 deletion ColumnMode/ColumnModePluginApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace ColumnMode
{
constexpr UINT c_ColumnModePluginApiVersion = 1;
constexpr UINT c_ColumnModePluginApiVersion = 2;

#pragma region ColumnModeCallbacks

Expand Down Expand Up @@ -32,6 +32,7 @@ namespace ColumnMode
typedef HRESULT(APIENTRY* PFN_PF_ONOPEN)(HANDLE, LPCWSTR);
typedef HRESULT(APIENTRY* PFN_PF_ONSAVE)(HANDLE, LPCWSTR);
typedef HRESULT(APIENTRY* PFN_PF_ONSAVEAS)(HANDLE, LPCWSTR);
typedef HRESULT(APIENTRY* PFN_PF_ONTYPINGCOMPLETE)(HANDLE, const size_t numChars, const WCHAR* pAllText);

//Plugin Life cycle
typedef HRESULT(APIENTRY* PFN_PF_ONLOADCOMPLETED)(HANDLE); //Called after OpenColumnModePlugin
Expand All @@ -45,10 +46,19 @@ namespace ColumnMode

PFN_PF_ONLOADCOMPLETED pfnOnLoadCompleted;
PFN_PF_ONSHUTDOWN pfnOnShutdown;

// API version >= 2
PFN_PF_ONTYPINGCOMPLETE pfnOnTypingComplete;
};

#pragma endregion

struct PluginDependency
{
UINT length; // number of WCHARs in pName
WCHAR* pName;
};

struct OpenPluginArgs
{
_In_ UINT apiVersion;
Expand All @@ -58,6 +68,20 @@ namespace ColumnMode
};
}

/*
Safe to not export if your plugin doesn't have run-time dll dependencies.
Called in the following pattern:
1. pCount is a valid pointer to any UINT and pDependencies is nullptr. - Plugin should set pCount to the number of dependencies.
2. pCount is a valid pointer, pDependencies is a valid pointer, each dependency's pName is nullptr. - Plugin should validate pCount is the right size, then populate the length fields of each dependency struct.
3. pCount is a valid pointer, pDependencies is a valid pointer, each dependency's pName is a valid pointer - Plugin should validate pCount, then foreach dependency: validate the length and then write the dependency name to the buffer (including extension).

Return value from each step should be S_OK if everything is valid and fields are being written as expected. If pCount or a length value is wrong, return E_INVALIDARG. Else, return E_FAIL.

Note that depenency dlls should be in the same directory as your plugin: %APPDATA%/ColumnMode/Plugins/your_plugin_name/
*/
extern "C" HRESULT WINAPI QueryColumnModePluginDependencies(_Inout_ UINT* pCount, _Inout_opt_count_(*pCount) ColumnMode::PluginDependency* pDependencies);
typedef HRESULT(WINAPI* PFN_QUERYCOLUMNMODEPLUGINDEPENDENCIES)(_Inout_ UINT* pCount, _Inout_opt_count_(*pCount) ColumnMode::PluginDependency* pDependencies);

// Required export
extern "C" HRESULT WINAPI OpenColumnModePlugin(_Inout_ ColumnMode::OpenPluginArgs* args);
typedef HRESULT(WINAPI* PFN_OPENCOLUMNMODEPLUGIN)(_Inout_ ColumnMode::OpenPluginArgs* args);
5 changes: 4 additions & 1 deletion ColumnMode/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ void MyRegisterClass(HINSTANCE hInstance)
}
{
// Document window
WNDCLASSEXW wcex;
WNDCLASSEXW wcex {};

wcex.cbSize = sizeof(WNDCLASSEX);

Expand Down Expand Up @@ -316,6 +316,9 @@ LRESULT CALLBACK TopLevelWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
case ID_THEMES_CREATENEWTHEME:
OnCreateTheme(hWnd, hInst);
break;
case ID_THEMES_EDIT:
OnEditTheme(hWnd, hInst);
break;
case ID_FILE_REFRESH:
OnRefresh(g_windowHandles);
break;
Expand Down
115 changes: 107 additions & 8 deletions ColumnMode/PluginManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,124 @@ HRESULT ColumnMode::PluginManager::ScanForPlugins()
return S_OK;
}

HRESULT LoadLibraryHelper(std::filesystem::path path, HMODULE& out_pluginModule)
{
DWORD flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR;
out_pluginModule = LoadLibraryEx(path.c_str(), NULL, flags);
if (out_pluginModule == NULL)
{
DWORD err = GetLastError();
WCHAR buff[1024];
std::swprintf(buff, 1024, _T("Plugin not found or DLL failed to load.\nError code: %d\nPath: %s"), err, path.c_str());
MessageBox(NULL, buff, L"Error loading plugin DLL", MB_OK | MB_ICONERROR);

return E_INVALIDARG;
}
return S_OK;
}

HRESULT LoadPluginDependenciesHelper(PFN_QUERYCOLUMNMODEPLUGINDEPENDENCIES pfnQueryDeps, std::filesystem::path pluginDir, HMODULE pluginModule)
{
UINT numDeps = 0;
std::wstring pluginName = pluginDir.filename(); //plugin name is the last part of the pluginDir

//query num dependencies and create dependency list
HRESULT hr = pfnQueryDeps(&numDeps, nullptr);
if(FAILED(hr))
{
MessageBoxHelper_FormattedBody(MB_ICONERROR | MB_OK, L"Failed to get dependencies", L"%s returned failure code %d when calling QueryColumnModePluginDependencies (1st call).", pluginName, hr);
return hr;
}
ColumnMode::PluginDependency defaultInit{ 0, nullptr };
std::vector<ColumnMode::PluginDependency> deps = std::vector<ColumnMode::PluginDependency>(numDeps, defaultInit);

// query sizes of dependency names and allocate space
hr = pfnQueryDeps(&numDeps, deps.data());
if (FAILED(hr))
{
MessageBoxHelper_FormattedBody(MB_ICONERROR | MB_OK, L"Failed to get dependencies", L"%s returned failure code %d when calling QueryColumnModePluginDependencies (2nd call).", pluginName, hr);
return hr;
}
for (auto depIt = deps.begin(); depIt != deps.end(); depIt++)
{
depIt->pName = (WCHAR*)malloc((depIt->length +1) * sizeof(WCHAR)); //add one extra char of padding so that we can ensure null-terminated
if (depIt->pName == nullptr)
{
hr = E_OUTOFMEMORY;
goto cleanup_and_exit;
}
}

// query dependency names
hr = pfnQueryDeps(&numDeps, deps.data());
if (FAILED(hr))
{
MessageBoxHelper_FormattedBody(MB_ICONERROR | MB_OK, L"Failed to get dependencies", L"%s returned failure code %d when calling QueryColumnModePluginDependencies (3rd call).", pluginName, hr);
goto cleanup_and_exit;
}

//Now we need to free the plugin library in case it has a load time library that does weird checks for runtime dependencies in dllmain (looking at you dxcompiler.dll)
FreeLibrary(pluginModule);
//Finally load the requested libraries
for (auto depIt = deps.begin(); depIt != deps.end(); depIt++)
{
depIt->pName[depIt->length] = L'\0'; //ensure null-terminated
std::filesystem::path depFullPath = pluginDir / depIt->pName;
HMODULE hm;
if (FAILED(LoadLibraryHelper(depFullPath, hm)))
{
hr = E_FAIL;
goto cleanup_and_exit;
//maybe unload dlls?
}
}

cleanup_and_exit:
// free allocated strings
for (auto depIt = deps.begin(); depIt != deps.end(); depIt++)
{
if (depIt->pName != nullptr)
{
free(depIt->pName);
depIt->pName = nullptr;
}
}
return hr;
}

HRESULT ColumnMode::PluginManager::LoadPlugin(LPCWSTR pluginName)
{
std::filesystem::path path(m_modulesRootPath);
path.append(pluginName) //Plugins should be in a folder of the plugin name
.append(pluginName) //Plugin is a DLL file of the plugin name
path.append(pluginName) //Plugins should be in a folder of the plugin name
.append(pluginName) //Plugin is a DLL file of the plugin name
.replace_extension(L".dll");

HMODULE pluginModule = LoadLibrary(path.c_str());
if (pluginModule == NULL)


HMODULE pluginModule;
BAIL_ON_FAIL_HR(LoadLibraryHelper(path, pluginModule));

PFN_QUERYCOLUMNMODEPLUGINDEPENDENCIES pfnQueryDeps =
reinterpret_cast<PFN_QUERYCOLUMNMODEPLUGINDEPENDENCIES>(GetProcAddress(pluginModule, "QueryColumnModePluginDependencies"));

if (pfnQueryDeps != nullptr)
{
MessageBox(NULL, path.c_str(), L"Plugin Not Found", MB_OK | MB_ICONERROR);
return E_INVALIDARG;
BAIL_ON_FAIL_HR(LoadPluginDependenciesHelper(pfnQueryDeps, m_modulesRootPath / pluginName, pluginModule));
}
BAIL_ON_FAIL_HR(LoadLibraryHelper(path, pluginModule));

PFN_OPENCOLUMNMODEPLUGIN pfnOpenPlugin =
reinterpret_cast<PFN_OPENCOLUMNMODEPLUGIN>(GetProcAddress(pluginModule, "OpenColumnModePlugin"));

PluginFunctions pluginFuncs = { 0 };
OpenPluginArgs args;
if (pfnOpenPlugin == nullptr)
{
MessageBoxHelper_FormattedBody(MB_ICONERROR | MB_OK, L"Failed to open plugin", L"%s doesn't export the OpenColumnModePlugin function.", pluginName);
return E_FAIL;
}

PluginFunctions pluginFuncs{};
ZeroMemory(&pluginFuncs, sizeof(pluginFuncs));
OpenPluginArgs args{};
args.apiVersion = c_ColumnModePluginApiVersion;
args.hPlugin = NULL;
args.pPluginFuncs = &pluginFuncs;
Expand Down
2 changes: 2 additions & 0 deletions ColumnMode/PluginManagerFunctions.inl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnOpen, (LPCWSTR))
DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnSave, (LPCWSTR))
DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnSaveAs, (LPCWSTR))
DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnTypingComplete, (const size_t, const WCHAR*))

#undef DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL

Expand All @@ -39,6 +40,7 @@ DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnSaveAs, (LPCWSTR))
DEFINE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnOpen, (LPCWSTR fileName), (p.m_hPlugin, fileName))
DEFINE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnSave, (LPCWSTR fileName), (p.m_hPlugin, fileName))
DEFINE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnSaveAs, (LPCWSTR fileName), (p.m_hPlugin, fileName))
DEFINE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnTypingComplete, (const size_t numChars, const WCHAR* pAllText), (p.m_hPlugin, numChars, pAllText))

#undef DEFINE_PLUGINMANAGER_FUNCTION_CALL_ALL

Expand Down
Loading