How do I implement a `DllMain`?

Please review the documentation for withUnsafePointer(to:).

Let me try to explain it another way: @convention(c) (HINSTANCE, DWORD, LPVOID) -> BOOL is actually a pointer already. withUnsafePointer<T>(to: DllMain) will indeed product a local/temporary value of type UnsafePointer<@convention(c) (HINSTANCE, DWORD, LPVOID) -> BOOL>, which is a pointer to a function pointer. :slightly_smiling_face:

I don’t need to reread any documentation, I am quite confident in my understanding of how this works. And the REPL agrees with me:

  1> func f() { }
  2> withUnsafePointer(to: f) { print(type(of: $0)) }
UnsafePointer<() -> ()>

Note the lack of a second UnsafePointer. Unlike C, Swift does not conflate expressions of function type with pointers to functions. The runtime representation of a value of function type might be a simple pointer to the first instruction of the function. But Swift does not leak this runtime representation into the user-visible type system. The type of this value is () -> Void, not UnsafePointer<() -> Void>.

This does not guarantee that the pointee of this pointer has the same bit pattern as GetProcAddress(hInst, "DllMain"). And it’s not legal to escape the pointer from the closure passed to withUnsafePointer(to:) anyway, so the point is moot.

(Rereading the above, it seems like we are actually in violent agreement?)

Firstly, we're talking about a @convention(c) function here, not a Swift function. @convention(c) functions are function pointers, and we do document them as such:

  • The c argument indicates a C function reference. The function value carries no context and uses the C calling convention.

Apple's documentation more clearly states that @convention(c) is equivalent to a C function pointer:

C function pointers are imported into Swift as closures with the C function pointer calling convention, denoted by the @convention(c) attribute. For example, a function pointer that has the type int (*)(void) in C is imported into Swift as @convention(c) () -> Int32.

Secondly, we're fundamentally talking about how to pass a reference to DllMain to a WinAPI function, which is a C function with C semantics, and which takes a function pointer that must be cast to a data pointer. In the context of this thread, it is important to understand that passing the pointer yielded by withUnsafePointer(to: DllMain) will not produce the intended result because it will produce a pointer to the function reference (which, again, is actually a function pointer and whose bit pattern is the actual value you want to pass to the WinAPI function.)

We just like arguing with each other, no?

2 Likes

Yes, I agree with this. I think what threw me off is when you described the result as a pointer to a pointer to the function, which mixes and matches Swift and C worldviews. A pointer to a pointer to a function, in Swift, would be spelled UnsafePointer<UnsafePointer<FnType>>. What this function passes the closure is a pointer to a value of function type. The fact that the value happens to take the form of a single word whose bit pattern can be interpreted as the address of the first instruction of the function’s body is an implementation detail of the runtime. This isn‘t even necessarily the “true” address of the function—the OS is free to define its own semantics there.

That's fair, although the whole idea of writing DllMain in Swift is already dragging us into the sordid world of language mixology.

I've said elsewhere (somewhere?) that Swift's story for C function interop is incomplete. We should have some mechanism to say "cast a function pointer to/from a raw data pointer" without having to resort to unsafeBitCast()[1]. Then the fact that they're function pointers in trenchcoats would no longer matter because the API can abstract the ABI away. As it stands, it is necessary for an @convention(c) function to be represented as a function pointer lest the entire world fall apart at our feet as soon as we try to do anything with the OS involving a callback.

I'm admittedly ignoring the possibility of OS-side thunks and whatnot here, but I don't think they're relevant to this thread?


  1. I am not going to try to design that API today. ↩

On WinNT Itanium, for example, function pointers were not the address of the first instruction.

Yeah, Itanium is dead but I am just making the general suggestion to use the platform’s ABI abstractions instead of making assumptions.

Why this doesn’t exist today
  • It would have to be generic (variadic generic, even)

  • 
but Swift generics are not monomorphized, so you end up with a C function pointer type with generics in it at runtime
  • 
unless the compiler is taught (along with the runtime?) that a C function pointer with generics in it is fine, it just has a consistent representation and you just can’t get the generics back out from a plain value.
  • 
which, unfortunately, I believe is hard (but haven’t directly looked into even when I was on the compiler team).

One thing that could work is a magic function like withoutActuallyEscaping, which has compiler support to make sure the outer and inner types match up and is guaranteed to be inlined away. But I don’t know if we want more of those for this relatively esoteric operation, especially one that is only guaranteed by POSIX (and Windows), not inherent to all platforms. (Is it true for wasm? I forget.)

Never said it was easy! And I'm not trying to design it here. But it remains a soft spot that I would hope we can address someday. Swift Testing needs to cast function pointers pretty frequently!

Function pointers are representable as data pointers in Wasm, yeah, although just in the sense that they fit in a word, so it's not like you can read bytes from them.

In general, conversion between data and function pointers is undefined in the C standard, but almost all 32-bit and wider systems do define it, and more importantly the (non-Embedded) Swift ABI depends on it!

1 Like

Okay, the issue is largely solved. This seems to work:

nonisolated(unsafe) private var hInstance: HINSTANCE? = nil

@_cdecl("DllMain")
public func DllMain(hModule: HINSTANCE, reasonForCall: DWORD, lpReserved: LPVOID) -> WindowsBool {
	if (reasonForCall == DLL_PROCESS_ATTACH) {
		hInstance = hModule
	}
	return true
}

Additionally, an unrelated issue I had was that another exported function was taking a struct, which I assumed meant it was being passed by value inside the stack. But apparently structs are automatically converted to reference, or perhaps a reference to a copy. The fact that @_cdecl prevents mapping the contents of the struct with a tuple, even though the actual parameter is just a pointer, is a bit annoying. But I can always use a raw pointer and then cast.

The final remaining issue I have is that for some reason the DLL would not unload when attempting to close the application, forcing me to kill the application instead. Something gets stuck during unload, and it gets stuck even if I stub out all the exports. It wasn't happening with stubbed out exports in Swift 5.10, so I can only conclude that this is something inherent to the Swift 6 runtime. However, I am at a complete loss as to how to debug such a thing.

I’d start by attaching windbg and figuring out which threads are still alive (~ command) and then getting a stack trace for those threads (~«thread id» k command).

Hmm, so what seems the most related is FreeLibrary is calling Foundation!$s10Foundation6NSUUIDC20_toCustomAnyHashables0eF0VSgyF (There has to be a decent way to demangle this), which ends up stuck in KERNELBASE!CompareObjectHandles. It's somewhat of an issue that I have no idea what this is supposed to do, or why it runs when attempting to free the library, let alone what's causing it to become stuck.

Diving into Foundation source code would be somewhat of a leap in difficulty.

You can use swift demangle to demangle Swift symbols. For example:

$ swift demangle '$s10Foundation6NSUUIDC20_toCustomAnyHashables0eF0VSgyF'
$s10Foundation6NSUUIDC20_toCustomAnyHashables0eF0VSgyF ---> Foundation.NSUUID._toCustomAnyHashable() -> Swift.AnyHashable?

(Tested on macOS. If you're using the Windows command line, make sure to handle escape characters appropriately.)

Swift does not support unloading modules containing Swift metadata, so you may need to just stub out your unloading logic to be a no-op.

I can't "stub out" FreeLibrary from a different application. And there's no stubbing out a process trying to exit.

You might need to artificially increase the ref count of your DLL. But this may not be safe to do from DllMain since that is called with the loader lock held. :confused:

I am aware. However, whatever logic you might have in your unload callback may need to be stubbed out or rewritten (if you have any.)

The only logic in the “unload callback” (aka DllMain with reasonForCall==DLL_PROCESS_DETACH) is return true:

I don’t think Swift’s “thou shalt never unload” stance is sustainable, especially on Windows.

1 Like

Did you build Foundation from source? I think that the stack is likely misleading as it would only show exported functions without the debug information (which would requre a debug buld). It isn't currently feasible to distribute the symbols in the installer since they amount to ~20G.

1 Like

Do we make the pdb’s from the official toolchain available?