Skip to content

Commit 29613ca

Browse files
authored
Adds shell extension DLLs to MSI installer (#78)
1 parent 9c81127 commit 29613ca

File tree

5 files changed

+132
-33
lines changed

5 files changed

+132
-33
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: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@
2727
<ComponentRef Id="ProductComponent" />
2828
<ComponentRef Id="RuntimeComponent" />
2929
<ComponentRef Id="TemplatesComponent" />
30+
<ComponentRef Id="PyShellExt64Component" />
31+
<ComponentRef Id="PyShellExtARM64Component" />
3032
</Feature>
3133

3234
<util:BroadcastEnvironmentChange />
35+
<util:QueryNativeMachine />
3336

3437
<Component Id="ProductComponent" Directory="INSTALLFOLDER" Guid="8BEC1259-B220-499B-9656-DC59B7F5BE24">
3538
<File KeyPath="yes" Source="py-manager.exe" Name="python.exe" Id="python.exe" />
@@ -44,18 +47,6 @@
4447
<File Source="vcruntime140.dll" />
4548
<File Source="vcruntime140_1.dll" />
4649

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-
5950
<File Source="version.txt" />
6051
<Environment Id="PATH" Action="set" Name="PATH" Part="last" System="yes" Value="[INSTALLFOLDER]" />
6152

@@ -115,5 +106,21 @@
115106
<File Source="templates\launcherw-32.exe" Name="launcherw-32.exe" />
116107
</Component>
117108

109+
<!-- IMAGE_FILE_MACHINE_AMD64 = 0x8664 = 34404 -->
110+
<Component Id="PyShellExt64Component" Directory="INSTALLFOLDER" Guid="D1946F6C-FB98-4395-BE63-D714A221A590"
111+
Condition="WIX_NATIVE_MACHINE = 34404">
112+
<File Source="pyshellext-64.dll">
113+
<Class Id="{C7E29CB0-9691-4DE8-B72B-6719DDC0B4A1}" Advertise="no" Context="InprocServer32" />
114+
</File>
115+
</Component>
116+
117+
<!-- IMAGE_FILE_MACHINE_ARM64 = 0xAA64 = 43620 -->
118+
<Component Id="PyShellExtARM64Component" Directory="INSTALLFOLDER" Guid="D1946F6C-FB98-4395-BE63-D714A221A591"
119+
Condition="WIX_NATIVE_MACHINE = 43620">
120+
<File Source="pyshellext-arm64.dll">
121+
<Class Id="{C7E29CB0-9691-4DE8-B72B-6719DDC0B4A1}" Advertise="no" Context="InprocServer32" />
122+
</File>
123+
</Component>
124+
118125
</Package>
119126
</Wix>

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: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ HRESULT ReadAllIdleInstalls(std::vector<IdleData> &idles, HKEY hive, LPCWSTR roo
167167
}
168168

169169
class DECLSPEC_UUID(CLSID_LAUNCH_COMMAND) LaunchCommand
170-
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand>
170+
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand, IObjectWithSite>
171171
{
172172
std::wstring title;
173173
std::wstring exe;
@@ -269,6 +269,26 @@ class DECLSPEC_UUID(CLSID_LAUNCH_COMMAND) LaunchCommand
269269
*ppEnum = NULL;
270270
return E_NOTIMPL;
271271
}
272+
273+
// IObjectWithSite
274+
private:
275+
ComPtr<IUnknown> _site;
276+
277+
public:
278+
IFACEMETHODIMP GetSite(REFIID riid, void **ppvSite)
279+
{
280+
if (_site) {
281+
return _site->QueryInterface(riid, ppvSite);
282+
}
283+
*ppvSite = NULL;
284+
return E_FAIL;
285+
}
286+
287+
IFACEMETHODIMP SetSite(IUnknown *pSite)
288+
{
289+
_site = pSite;
290+
return S_OK;
291+
}
272292
};
273293

274294

@@ -316,7 +336,7 @@ class DECLSPEC_UUID(CLSID_COMMAND_ENUMERATOR) CommandEnumerator
316336

317337

318338
class DECLSPEC_UUID(CLSID_IDLE_COMMAND) IdleCommand
319-
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand>
339+
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand, IObjectWithSite>
320340
{
321341
std::vector<IdleData> idles;
322342
std::wstring iconPath;
@@ -432,12 +452,33 @@ class DECLSPEC_UUID(CLSID_IDLE_COMMAND) IdleCommand
432452
).Detach();
433453
return S_OK;
434454
}
455+
456+
// IObjectWithSite
457+
private:
458+
ComPtr<IUnknown> _site;
459+
460+
public:
461+
IFACEMETHODIMP GetSite(REFIID riid, void **ppvSite)
462+
{
463+
if (_site) {
464+
return _site->QueryInterface(riid, ppvSite);
465+
}
466+
*ppvSite = NULL;
467+
return E_FAIL;
468+
}
469+
470+
IFACEMETHODIMP SetSite(IUnknown *pSite)
471+
{
472+
_site = pSite;
473+
return S_OK;
474+
}
435475
};
436476

437477

438478
CoCreatableClass(IdleCommand);
439479

440480
#ifdef PYSHELLEXT_TEST
481+
441482
IExplorerCommand *MakeLaunchCommand(std::wstring title, std::wstring exe, std::wstring idle)
442483
{
443484
IdleData data = { .title = title, .exe = exe, .idle = idle };
@@ -449,10 +490,31 @@ IExplorerCommand *MakeIdleCommand(HKEY hive, LPCWSTR root)
449490
{
450491
return Make<IdleCommand>(hive, root).Detach();
451492
}
452-
#endif
453493

494+
#elif defined(_WINDLL)
495+
496+
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv)
497+
{
498+
return Module<InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
499+
}
500+
501+
502+
STDAPI DllCanUnloadNow()
503+
{
504+
return Module<InProc>::GetModule().Terminate() ? S_OK : S_FALSE;
505+
}
506+
507+
STDAPI_(BOOL) DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*)
508+
{
509+
if (reason == DLL_PROCESS_ATTACH) {
510+
hModule = hinst;
511+
DisableThreadLibraryCalls(hinst);
512+
}
513+
return TRUE;
514+
}
515+
516+
#else
454517

455-
#ifndef PYSHELLEXT_TEST
456518
class OutOfProcModule : public Module<OutOfProc, OutOfProcModule>
457519
{ };
458520

@@ -475,4 +537,5 @@ int WINAPI wWinMain(
475537
CoUninitialize();
476538
return 0;
477539
}
540+
478541
#endif

0 commit comments

Comments
 (0)