Skip to content

Commit 36be4ce

Browse files
committed
Enables cross-platform DLL build of the shell extension (for MSI installs)
1 parent 89afccf commit 36be4ce

File tree

5 files changed

+102
-32
lines changed

5 files changed

+102
-32
lines changed

_msbuild.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,31 @@ def launcher_exe(name, platform, windowed=False):
148148
)
149149

150150

151+
def pyshellext(ext='.exe', **props):
152+
link_opts = ItemDefinition(
153+
'Link',
154+
AdditionalDependencies=Prepend('RuntimeObject.lib;'),
155+
)
156+
if ext != '.exe':
157+
link_opts.options['ModuleDefinitionFile'] = '$(SourceRootDir)src\\pyshellext\\pyshellext.def'
158+
159+
160+
return CProject(f"pyshellext{ext.rpartition('.')[0]}",
161+
VersionInfo(
162+
FileDescription='Python shell extension',
163+
OriginalFilename=f'pyshellext{ext}',
164+
),
165+
ItemDefinition('ClCompile', LanguageStandard='stdcpp20'),
166+
link_opts,
167+
Manifest('default.manifest'),
168+
CSourceFile('shellext.cpp'),
169+
ResourceFile('pyshellext.rc'),
170+
source='src/pyshellext',
171+
StaticLibcppLinkage=True,
172+
**props,
173+
)
174+
175+
151176
PACKAGE = Package('python-manager',
152177
PyprojectTomlFile('pyproject.toml'),
153178
# MSIX manifest
@@ -206,23 +231,9 @@ def launcher_exe(name, platform, windowed=False):
206231
main_exe("python3"),
207232
mainw_exe("pythonw3"),
208233

209-
CProject("pyshellext",
210-
VersionInfo(
211-
FileDescription="Python shell extension",
212-
OriginalFilename="pyshellext.exe",
213-
),
214-
Property('StaticLibcppLinkage', 'true'),
215-
ItemDefinition('ClCompile', LanguageStandard='stdcpp20'),
216-
ItemDefinition('Link',
217-
AdditionalDependencies=Prepend("RuntimeObject.lib;"),
218-
SubSystem='WINDOWS',
219-
),
220-
Manifest('default.manifest'),
221-
CSourceFile('shellext.cpp'),
222-
ResourceFile('pyshellext.rc'),
223-
source='src/pyshellext',
224-
ConfigurationType='Application',
225-
),
234+
pyshellext(".exe", ConfigurationType="Application"),
235+
pyshellext("-64.dll", Platform="x64"),
236+
pyshellext("-arm64.dll", Platform="ARM64"),
226237
)
227238

228239

make-msix.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,19 @@
5353
"/mf", "appx"])
5454

5555
# Clean up non-shipping files from LAYOUT
56+
preserved = [
57+
*LAYOUT.glob("pyshellext*.dll"),
58+
]
59+
60+
for f in preserved:
61+
print("Preserving", f, "as", TEMP / f.name)
62+
copyfile(f, TEMP / f.name)
63+
5664
unlink(
5765
*LAYOUT.rglob("*.pdb"),
5866
*LAYOUT.rglob("*.pyc"),
5967
*LAYOUT.rglob("__pycache__"),
68+
*preserved,
6069
)
6170

6271
# Package into DIST
@@ -122,3 +131,9 @@ def patch_appx(source):
122131
with zipfile.ZipFile(DIST_MSIXUPLOAD, "w") as zf:
123132
zf.write(DIST_STORE_MSIX, arcname=DIST_STORE_MSIX.name)
124133
zf.write(DIST_APPXSYM, arcname=DIST_APPXSYM.name)
134+
135+
136+
for f in preserved:
137+
print("Restoring", f, "from", TEMP / f.name)
138+
copyfile(TEMP / f.name, f)
139+
unlink(TEMP / f.name)

src/pymanager/msi.wxs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@
2727
<ComponentRef Id="ProductComponent" />
2828
<ComponentRef Id="RuntimeComponent" />
2929
<ComponentRef Id="TemplatesComponent" />
30+
<!-- TODO: Platform-specific components for pyshellext.
31+
Ensure only one is installed based on the _real_ processor type!
32+
33+
<File Source="pyshellext-64.dll">
34+
<Class Id="{C7E29CB0-9691-4DE8-B72B-6719DDC0B4A1}" Advertise="no" Context="InProcServer32" />
35+
</File>
36+
<File Source="pyshellext-arm64.dll">
37+
<Class Id="{C7E29CB0-9691-4DE8-B72B-6719DDC0B4A1}" Advertise="no" Context="InProcServer32" />
38+
</File>
39+
40+
-->
3041
</Feature>
3142

3243
<util:BroadcastEnvironmentChange />
@@ -44,18 +55,6 @@
4455
<File Source="vcruntime140.dll" />
4556
<File Source="vcruntime140_1.dll" />
4657

47-
<!--
48-
When installed via MSIX, this extension must be out of proc.
49-
Apparently, when installed via MSI, it must be in proc. Which means we'd
50-
need to compile DLLs for each platform just for the MSI, as well as the
51-
EXE that we use for regular installs.
52-
Right now, not worth it.
53-
54-
<File Source="pyshellext.exe">
55-
<Class Id="{C7E29CB0-9691-4DE8-B72B-6719DDC0B4A1}" Advertise="no" Context="LocalServer32" />
56-
</File>
57-
-->
58-
5958
<File Source="version.txt" />
6059
<Environment Id="PATH" Action="set" Name="PATH" Part="last" System="yes" Value="[INSTALLFOLDER]" />
6160

src/pyshellext/pyshellext.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
EXPORTS
2+
DllGetClassObject PRIVATE
3+
DllCanUnloadNow PRIVATE

src/pyshellext/shellext.cpp

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,11 +316,12 @@ class DECLSPEC_UUID(CLSID_COMMAND_ENUMERATOR) CommandEnumerator
316316

317317

318318
class DECLSPEC_UUID(CLSID_IDLE_COMMAND) IdleCommand
319-
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand>
319+
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand, IObjectWithSite>
320320
{
321321
std::vector<IdleData> idles;
322322
std::wstring iconPath;
323323
std::wstring title;
324+
ComPtr<IUnknown> _site;
324325
public:
325326
IdleCommand() : title(L"Edit in &IDLE")
326327
{
@@ -432,12 +433,28 @@ class DECLSPEC_UUID(CLSID_IDLE_COMMAND) IdleCommand
432433
).Detach();
433434
return S_OK;
434435
}
436+
437+
IFACEMETHODIMP GetSite(REFIID riid, void **ppvSite)
438+
{
439+
if (_site) {
440+
return _site->QueryInterface(riid, ppvSite);
441+
}
442+
*ppvSite = NULL;
443+
return E_FAIL;
444+
}
445+
446+
IFACEMETHODIMP SetSite(IUnknown *pSite)
447+
{
448+
_site = pSite;
449+
return S_OK;
450+
}
435451
};
436452

437453

438454
CoCreatableClass(IdleCommand);
439455

440456
#ifdef PYSHELLEXT_TEST
457+
441458
IExplorerCommand *MakeLaunchCommand(std::wstring title, std::wstring exe, std::wstring idle)
442459
{
443460
IdleData data = { .title = title, .exe = exe, .idle = idle };
@@ -449,10 +466,34 @@ IExplorerCommand *MakeIdleCommand(HKEY hive, LPCWSTR root)
449466
{
450467
return Make<IdleCommand>(hive, root).Detach();
451468
}
452-
#endif
453469

470+
#elif defined(_WINDLL)
471+
472+
#pragma comment(linker, "/export:DllGetClassObject")
473+
#pragma comment(linker, "/export:DllCanUnloadNow")
474+
475+
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv)
476+
{
477+
return Module<InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
478+
}
479+
480+
481+
STDAPI DllCanUnloadNow()
482+
{
483+
return Module<InProc>::GetModule().Terminate() ? S_OK : S_FALSE;
484+
}
485+
486+
STDAPI_(BOOL) DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*)
487+
{
488+
if (reason == DLL_PROCESS_ATTACH) {
489+
hModule = hinst;
490+
DisableThreadLibraryCalls(hinst);
491+
}
492+
return TRUE;
493+
}
494+
495+
#else
454496

455-
#ifndef PYSHELLEXT_TEST
456497
class OutOfProcModule : public Module<OutOfProc, OutOfProcModule>
457498
{ };
458499

@@ -475,4 +516,5 @@ int WINAPI wWinMain(
475516
CoUninitialize();
476517
return 0;
477518
}
519+
478520
#endif

0 commit comments

Comments
 (0)