Island RTL Bug? DllMain on Windos platform is NEVER hooked up

DllMain on Windos platform is NEVER hooked up. This problem happens when:
1). the DLL is dynamically loaded using WIN32 LoadLibrary, as well as
2). the DLL is statically loaded.

The DLL code is here:

namespace IslandDll;
uses rtl;

  [SymbolName('__elements_dll_main')]
  method DllMain(aModule: rtl.HMODULE; aReason: rtl.DWORD; aReserved: ^Void): Boolean;
  begin
    OutputDebugString('I am now inside DllMain, Buddy!');
    case aReason of 
      DLL_PROCESS_ATTACH: ;
      DLL_PROCESS_DETACH: ;
      DLL_THREAD_ATTACH : ;
      DLL_THREAD_DETACH : ;
    end; 
    exit true;
  end;

  [SymbolName('Sum'),  DLLExport, CallingConvention(CallingConvention.Stdcall)] 
  method Sum(a, b: Integer): Integer;
  begin
    OutputDebugString('I am now inside Sum, Buddy!');
    exit (a + b);
  end;
end.

The testing projects are attached below, which include both the dynamic DLL loading case, and the static DLL loading case.

IslandDllMainTest.7z (971.0 KB)

Again, Carlo will know more about how this works. But as I understand DllMain is something Windows knows to call automatically on its own as the dll is loaded, right? as such, I’d expect the __elements_dll_main symbol name is probably wrong? Also, you probably need a DllExport attribute as well, for the symbol to become public…

As I recall it was @ck who suggested __elements_dll_main in another post. But yes, without DllExport it won’t be public. You need to add DllExport (or Export). [DllExport(‘__elements_dll_main’)] should also work, if you don’t want to use two attributes.

Yeah, I know. I’m not sure what the mechanics here are. MS’s docs state

DllMain is a placeholder for the library-defined function name. You must specify the actual name you use when you build your DLL. For more information, see the documentation included with your development tools.

so I guess maybe there’s some compiler/linker magic involved to map __elements_dll_main to what Windows actually needs to locate this entry point…

I’ll check with @ck and make sure we doc this properly, Tuesday.

I think the “Compiler Magic” is here, IslandRTL/WindowsHelpers.pas, line 1175-1184

method DllMainCRTStartup(aModule: rtl.HMODULE; aReason: rtl.DWORD; aReserved: ^Void): Boolean;
begin
	var lMain: ^DllMainType := @_dllmain;  // Hook up here to lMain.
	ExternalCalls.fModuleHandle := aModule;
	if lMain^ = nil then exit true;  // lMain^ always = 0, meaning the user defined DllMain will not be called!!!
	exit lMain^(aModule, aReason, aReserved);
end;

Then at line 317-322, _dllmain is declared as a public variable.

[SymbolName('__elements_dll_main'), &Weak]
method DllMain(aModule: rtl.HMODULE; aReason: rtl.DWORD; aReserved: ^Void): Boolean; external;
    // This is needed by anything msvc compiled; it's the offset in fs for the tls array
    var
    	_dllmain: DllMainType := @DllMain;public;

Most likely the BUG is a compiler bug, that doesn’t assign a method pointer pointer below:
At line 322 , _dllmain: DllMainType := @DllMain; public; // <== At this point, _dllmain is initialized to @DllMain, which is defined in the user code. The compiler is supposed to get the user-implemented method address, but it FAILS to do so.

Hence, in the following DllMainCRTStartUp, which is the standard DLL entry point expected by Windows,
var lMain: ^DllMainType := @_dllmain;, lMain^ would be always be nil. Therefore user defined DllMain is never entered.

method DllMainCRTStartup(aModule: rtl.HMODULE; aReason: rtl.DWORD; aReserved: ^Void): Boolean;
begin
	var lMain: ^DllMainType := @_dllmain; 
	ExternalCalls.fModuleHandle := aModule;
	if lMain^ = nil then exit true;
	exit lMain^(aModule, aReason, aReserved);
end;

Seems the linker got a bit more strict here. What you can do in the next build is this:
[SymbolName('__elements_dll_main', ReferenceFromMain := true)]

(On the dllmain method), then it works.

Thanks, logged as bugs://82757

bugs://82757 got closed with status fixed.

Thank you. If not for dllmain but just for a global function, can I use the same trick? That is, I ‘d like to have a function declared external in one unit file but implemented in another unit?

You don’t really need this cross units? The elements compiler doesn’t care about the order of functions. (Unless I misunderstand your request). Etiher way yes, you can use this trick to tricky the compiler.

Understand

I do want this trick so I can have a global function declared in one name space as a common library, but implemented in a different namespace by user.

Namespaces should still be fine btw. The only time you need a trick like this is when it’s cross-library, and the other side is optional.

[Used, StaticallyInitializedField]
_dllmain: DllMainType := @DllMain;public;

Should I also specify the same attributes as _dllmain, if I want to use the same trick with other non DllMain function cross library?

PS what does “Used” mean in this situation?

Used doesn’t really do anything here but normally it forces the linker to link it in. Statically initialize means it hardcodes the value instead of using a static ctor (limits the options ofc)