Skip to content

Commit 3a17ab0

Browse files
authored
Fix void* params in friendly methods (#1520)
1 parent 5e02e18 commit 3a17ab0

9 files changed

Lines changed: 115 additions & 19 deletions

File tree

src/CsWin32Generator/Program.cs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,11 @@ private unsafe bool ProcessNativeMethodsFile(SuperGenerator superGenerator, File
491491
int processedCount = 0;
492492
int skippedCount = 0;
493493
int errorCount = 0;
494+
int lineNumber = 0;
494495

495496
foreach (string line in lines)
496497
{
498+
lineNumber++;
497499
string name = line.Trim();
498500
if (string.IsNullOrWhiteSpace(name) || name.StartsWith("//", StringComparison.Ordinal) || name.StartsWith("-", StringComparison.Ordinal))
499501
{
@@ -511,7 +513,7 @@ private unsafe bool ProcessNativeMethodsFile(SuperGenerator superGenerator, File
511513
int matches = superGenerator.TryGenerateAllExternMethods(moduleName, CancellationToken.None);
512514
if (matches == 0)
513515
{
514-
this.ReportWarning($"No methods found under module '{moduleName}'");
516+
this.ReportWarning($"No methods found under module '{moduleName}'", nativeMethodsTxt.FullName, lineNumber);
515517
}
516518
else
517519
{
@@ -526,33 +528,33 @@ private unsafe bool ProcessNativeMethodsFile(SuperGenerator superGenerator, File
526528

527529
foreach (string declaringEnum in redirectedEnums)
528530
{
529-
this.ReportWarning($"Using the name of the enum that declares this constant: {declaringEnum}");
531+
this.ReportWarning($"Using the name of the enum that declares this constant: {declaringEnum}", nativeMethodsTxt.FullName, lineNumber);
530532
}
531533

532534
switch (matchingApis.Count)
533535
{
534536
case 0:
535-
this.ReportError($"Method, type or constant '{name}' not found");
537+
this.ReportError($"Method, type or constant '{name}' not found", nativeMethodsTxt.FullName, lineNumber);
536538
errorCount++;
537539
break;
538540
case 1:
539541
this.InfoWriteLine($"Generated: {name}");
540542
processedCount++;
541543
break;
542544
case > 1:
543-
this.ReportError($"The API '{name}' is ambiguous. Please specify one of: {string.Join(", ", matchingApis.Select(api => $"\"{api}\""))}");
545+
this.ReportError($"The API '{name}' is ambiguous. Please specify one of: {string.Join(", ", matchingApis.Select(api => $"\"{api}\""))}", nativeMethodsTxt.FullName, lineNumber);
544546
errorCount++;
545547
break;
546548
}
547549
}
548550
catch (PlatformIncompatibleException)
549551
{
550-
this.ReportError($"API '{name}' is not available for the target platform");
552+
this.ReportError($"API '{name}' is not available for the target platform", nativeMethodsTxt.FullName, lineNumber);
551553
errorCount++;
552554
}
553555
catch (Exception ex)
554556
{
555-
this.ReportError($"'{name}': {this.ErrorChainToString(ex)}");
557+
this.ReportError($"'{name}': {this.ErrorChainToString(ex)}", nativeMethodsTxt.FullName, lineNumber);
556558
errorCount++;
557559
}
558560
}
@@ -562,7 +564,7 @@ private unsafe bool ProcessNativeMethodsFile(SuperGenerator superGenerator, File
562564
}
563565
catch (Exception ex)
564566
{
565-
this.ReportError($"Failed to process NativeMethods.txt file: {this.ErrorChainToString(ex)}");
567+
this.ReportError($"Failed to process NativeMethods.txt file: {this.ErrorChainToString(ex)}", nativeMethodsTxt.FullName);
566568
return false;
567569
}
568570
}
@@ -624,14 +626,36 @@ private bool GenerateAndWriteFiles(SuperGenerator superGenerator, DirectoryInfo
624626
}
625627
}
626628

627-
private void ReportError(string message)
629+
private void ReportError(string message, string? file = null, int? line = null)
628630
{
629-
this.error.WriteLine($"CsWin32 : error : {message}");
631+
if (file is not null && line is not null)
632+
{
633+
this.error.WriteLine($"{file}({line}): CsWin32 error : {message}");
634+
}
635+
else if (file is not null)
636+
{
637+
this.error.WriteLine($"{file}: CsWin32 error : {message}");
638+
}
639+
else
640+
{
641+
this.error.WriteLine($"CsWin32 : error : {message}");
642+
}
630643
}
631644

632-
private void ReportWarning(string message)
645+
private void ReportWarning(string message, string? file = null, int? line = null)
633646
{
634-
this.output.WriteLine($"CsWin32 : warning : {message}");
647+
if (file is not null && line is not null)
648+
{
649+
this.output.WriteLine($"{file}({line}): CsWin32 warning : {message}");
650+
}
651+
else if (file is not null)
652+
{
653+
this.output.WriteLine($"{file}: CsWin32 warning : {message}");
654+
}
655+
else
656+
{
657+
this.output.WriteLine($"CsWin32 : warning : {message}");
658+
}
635659
}
636660

637661
private void InfoWriteLine(string message)

src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,10 @@ private IEnumerable<MethodDeclarationSyntax> DeclareFriendlyOverload(MethodDefin
222222
}
223223
else if (!countParamIndex.HasValue && !isComOutPtr)
224224
{
225-
// void* param with no size annotations and without [ComOutPtr] is assumed to be
226-
// arbitrary memory block which we represent as Span<byte>
227-
isArray = true;
228-
projectAsSpanBytes = true;
225+
// void* param with no size annotations and without [ComOutPtr] can't really be improved,
226+
// so leave it alone.
229227
}
230-
else
228+
else if (countParamIndex.HasValue)
231229
{
232230
// If it's void* but annotated with a count-of-elements (like OfferVirtualMemory or TokenBindingGenerateMessage) then
233231
// just leave it as raw pointer because it's not clear what the developer meant and projecting as Span<byte> will require

test/CsWin32Generator.Tests/CsWin32GeneratorFullTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public async Task FullGeneration(string tfm, LanguageVersion langVersion)
2222
this.fullGeneration = true;
2323
this.compilation = this.starterCompilations[tfm];
2424
this.parseOptions = this.parseOptions.WithLanguageVersion(langVersion);
25+
this.nativeMethodsJson = "NativeMethods.EmitSingleFile.json";
2526
await this.InvokeGeneratorAndCompile($"FullGeneration_{tfm}_{langVersion}", TestOptions.None);
2627
}
2728
}

test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,15 @@ public async Task TestGenerateCoCreateableClass()
162162
["SetupDiGetDeviceInterfaceDetail", "SetupDiGetDeviceInterfaceDetail", "SafeHandle DeviceInfoSet, in winmdroot.Devices.DeviceAndDriverInstallation.SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, Span<byte> DeviceInterfaceDetailData, out uint RequiredSize, ref winmdroot.Devices.DeviceAndDriverInstallation.SP_DEVINFO_DATA DeviceInfoData"],
163163
// Optional params omitted
164164
["SetupDiGetDeviceInterfaceDetail", "SetupDiGetDeviceInterfaceDetail", "SafeHandle DeviceInfoSet, in winmdroot.Devices.DeviceAndDriverInstallation.SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, Span<byte> DeviceInterfaceDetailData"],
165-
["WinHttpReadData", "WinHttpReadData", "Span<byte> hRequest, Span<byte> lpBuffer, ref uint lpdwNumberOfBytesRead"],
165+
["WinHttpReadData", "WinHttpReadData", "void* hRequest, Span<byte> lpBuffer, ref uint lpdwNumberOfBytesRead"],
166166
["IsTextUnicode", "IsTextUnicode", "ReadOnlySpan<byte> lpv, ref winmdroot.Globalization.IS_TEXT_UNICODE_RESULT lpiResult"],
167167
// Omitted ref param
168168
["IsTextUnicode", "IsTextUnicode", "ReadOnlySpan<byte> lpv"],
169-
["GetAce", "GetAce", "in winmdroot.Security.ACL pAcl, uint dwAceIndex, Span<byte> pAce"],
169+
["GetAce", "GetAce", "in winmdroot.Security.ACL pAcl, uint dwAceIndex, out void* pAce"],
170170
// Optional and MemorySize-d struct params, optional params included
171171
["SetupDiGetClassInstallParams", "SetupDiGetClassInstallParams", "SafeHandle DeviceInfoSet, winmdroot.Devices.DeviceAndDriverInstallation.SP_DEVINFO_DATA? DeviceInfoData, Span<byte> ClassInstallParams, out uint RequiredSize"],
172172
["IEnumString", "Next", "this winmdroot.System.Com.IEnumString @this, Span<winmdroot.Foundation.PWSTR> rgelt, out uint pceltFetched"],
173+
["PSCreateMemoryPropertyStore", "PSCreateMemoryPropertyStore", "in global::System.Guid riid, out void* ppv"],
173174
];
174175

175176
[Theory]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "..\\..\\..\\src\\Microsoft.Windows.CsWin32\\settings.schema.json",
3+
"emitSingleFile": true
4+
}

test/GenerationSandbox.BuildTask.Tests/COMTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
using Windows.Win32.System.WinRT.Composition;
2222
using Windows.Win32.UI.Shell;
2323

24+
namespace GenerationSandbox.BuildTask.Tests;
25+
2426
[Trait("WindowsOnly", "true")]
2527
public partial class COMTests
2628
{

test/GenerationSandbox.BuildTask.Tests/NativeMethods.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "..\\..\\src\\Microsoft.Windows.CsWin32\\settings.schema.json",
3-
"emitSingleFile": false,
3+
"emitSingleFile": true,
44
"allowMarshaling": true,
55
"public": false,
66
"comInterop": {

test/GenerationSandbox.BuildTask.Tests/NativeMethods.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,8 @@ SHGetFileInfo
4242
SHGFI_FLAGS
4343
FILE_FLAGS_AND_ATTRIBUTES
4444
InitializeAcl
45+
DeviceIoControl
46+
SetupDiCreateDeviceInterface
47+
GetFileVersionInfoSizeEx
48+
GetFileVersionInfoEx
49+
VerQueryValue
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Globalization;
5+
using System.Runtime.InteropServices;
6+
using Windows.Win32.Storage.FileSystem;
7+
using static Windows.Win32.PInvoke;
8+
9+
#pragma warning disable SA1201, CS0649
10+
11+
namespace GenerationSandbox.BuildTask.Tests;
12+
13+
[Trait("WindowsOnly", "true")]
14+
public partial class SpanOverloadTests
15+
{
16+
[Fact]
17+
public void CanCallGetFileVersionApis()
18+
{
19+
var appName = GetAppName("C:\\windows\\notepad.exe");
20+
Assert.True(appName is not null);
21+
}
22+
23+
internal struct LANGANDCODEPAGE
24+
{
25+
public ushort Language;
26+
public ushort Codepage;
27+
}
28+
29+
internal static unsafe string GetAppName(string processPath)
30+
{
31+
uint infoSize = GetFileVersionInfoSizeEx(GET_FILE_VERSION_INFO_FLAGS.FILE_VER_GET_NEUTRAL, processPath, out uint handle);
32+
if (infoSize > 0)
33+
{
34+
// Try whatever language is supported
35+
// https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-verqueryvaluew
36+
Span<byte> versionInfo = new byte[infoSize];
37+
if (GetFileVersionInfoEx(GET_FILE_VERSION_INFO_FLAGS.FILE_VER_GET_NEUTRAL, processPath, versionInfo))
38+
{
39+
fixed (void* pvVersionInfo = versionInfo)
40+
{
41+
if (VerQueryValue(pvVersionInfo, @"\VarFileInfo\Translation", out void* pvLangCodePage, out uint cbTranslate) &&
42+
pvLangCodePage is not null &&
43+
cbTranslate >= sizeof(LANGANDCODEPAGE))
44+
{
45+
LANGANDCODEPAGE* langCodePage = (LANGANDCODEPAGE*)pvLangCodePage;
46+
string path = $@"\StringFileInfo\{langCodePage->Language.ToString("x4", CultureInfo.InvariantCulture.NumberFormat)}{langCodePage->Codepage.ToString("x4", CultureInfo.InvariantCulture.NumberFormat)}\FileDescription";
47+
if (VerQueryValue(pvVersionInfo, path, out void* descPtr, out uint desLen) &&
48+
descPtr is not null &&
49+
desLen > 0)
50+
{
51+
return Marshal.PtrToStringAuto(new(descPtr)) ?? string.Empty;
52+
}
53+
}
54+
}
55+
}
56+
}
57+
58+
// Fallback to the process name
59+
return Path.GetFileNameWithoutExtension(processPath);
60+
}
61+
}

0 commit comments

Comments
 (0)