Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Enable support DllImport a native assembly whose name contains '.'#17505

Merged
jkotas merged 12 commits intodotnet:masterfrom
luqunl:#17150
Apr 17, 2018
Merged

Enable support DllImport a native assembly whose name contains '.'#17505
jkotas merged 12 commits intodotnet:masterfrom
luqunl:#17150

Conversation

@luqunl
Copy link
Copy Markdown

@luqunl luqunl commented Apr 10, 2018

Currently, In Windows, if the name your native dll specified in DllImport contains '.', such as Test.Foo, DllImport will only search for file Test.Foo and it won't search for file Test.Foo.dll

The fix is to append ".dll" if the native dll name specified in DllImport doesn't contain ".dll" .

Fix #17150

@luqunl luqunl changed the title [WIP]Enable support DllImport a native assembly whose name contains '.' Enable support DllImport a native assembly whose name contains '.' Apr 10, 2018
@jkotas
Copy link
Copy Markdown
Member

jkotas commented Apr 11, 2018

Test?

@danmoseley
Copy link
Copy Markdown
Member

Ideally a test in Corefx so we can ensure it is fixed in Mono, CoreRT.

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Apr 11, 2018

CoreCLR has better setup for interop tests. And Mono and CoreRT can run CoreCLR tests too.

@mikedn
Copy link
Copy Markdown

mikedn commented Apr 11, 2018

The fix is to append ".dll" if the native dll name specified in DllImport doesn't contain ".dll" .

So what happens if the specified name is "foo.exe"? Will it search for "foo.exe.dll"?

@ghost
Copy link
Copy Markdown

ghost commented Apr 11, 2018

I totally missed your PR. This approach of using DetermineLibNameVariations for both Windows and Unix is most probably neater than what I was experimenting with https://github.com/kasper3/coreclr/commit/edb45878f6915bf7443d74f7da2d17dd1dae5ac9, except for missing .exe check.
Also, does it account for casing .dll, .DLL etc.?

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Apr 12, 2018

@luqunl Could you please rebase away the commits not related to your change?

@luqunl
Copy link
Copy Markdown
Author

luqunl commented Apr 12, 2018

Let me try to rebase my changes

@luqunl
Copy link
Copy Markdown
Author

luqunl commented Apr 12, 2018

Could you please rebase away the commits not related to your change?

Fixed

Test?

Fixed

So what happens if the specified name is "foo.exe"? Will it search for "foo.exe.dll"?

Thanks for catching this. Fixed

Also, does it account for casing .dll, .DLL etc.?

Thanks for catching this. Fixed

containsSuffix = it == libName.End() || *it == (WCHAR)'.';
}
#else
if (libName.EndsWithCaseInsensitive(PLATFORM_SHARED_LIB_SUFFIX_W) || libName.EndsWithCaseInsensitive(WINDOWS_SHARED_EXE_SUFFIX_W))
Copy link
Copy Markdown
Member

@jkotas jkotas Apr 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand why we are special casing .exe suffix here. .exe suffix is non-standard suffix for shared libraries. If I want to, I can use any other non-standard suffix (e.g. .dat) for my .dlls and LoadLibrary will load them too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason is to avoid to load/search .exe.dll file. if it is possible that someone name his file as a.exe.dll, then we can remove this special case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am more worried about that cases that used to work fine before this change, but do not work anymore after this change. Consider:

[DllImport("mydll.dat")]
extern static int sum(int a, int b);

static void Main() {
    Console.WriteLine(sum(1,2).ToString());
}

And mydll.dat that is renamed mydll.dll to mydll.dat. It works fine before this change (mydll.dat is passed to LoadLibrary), but it does not work after this change (mydll.dat.dll is passed to LoadLibrary).

Copy link
Copy Markdown
Author

@luqunl luqunl Apr 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this case, try both name itself and name itself+extension.

' static const char* const SuffixFirst[] =
{
"%.0s%s%s", // name+suffix
#ifdef FEATURE_PAL
"%s%s%s", // prefix+name+suffix
#endif
"%.0s%s", // name
#ifdef FEATURE_PAL
"%s%s%.0s" // prefix+name
#endif
};
'

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can check // name first, then //name + suffix to be more comp with current behavior.

add_library (DllFileProbe SHARED ${SOURCES})
target_link_libraries(DllFileProbe ${LINK_LIBRARIES_ADDITIONAL})

#get_cmake_property(_variableNames VARIABLES)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these commented out blocks?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove these commented lines..just copy & paste..

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed these comments

@jeffschwMSFT jeffschwMSFT added this to the 2.2.0 milestone Apr 13, 2018
Copy link
Copy Markdown
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To summarize, I think there are 4 interesting cases on Windows:

  • The name does not contain dot: Try the name only. No other suffixes. LoadLibrary will append .dll suffix internally.
  • The name ends with a dot: Try the name only. No other suffixes. Maintains the behavior documented in MSDN.
  • The name ends with .dll (or .exe too?): Try the name only. No point in trying .dll.dll.
  • Otherwise: Try name + .dll suffix first, and then try name without .dll suffix.

@@ -6078,9 +6096,13 @@ static void DetermineLibNameVariations(const char* const** libNameVariations, in
static const char* const SuffixFirst[] =
{
"%.0s%s%s", // name+suffix
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For name without any dots, this is going to probe twice now:

  • First time, we append the .dll
  • Second time, we pass in name without .dll suffix and LoadLibrary appends it internally.

It would be nice to avoid this duplication (e.g. just let LoadLibrary take care of appending .dll to the simple names).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

static const char* const SuffixFirst[] =
{
"%.0s%s%s", // name+suffix
#ifdef FEATURE_PAL
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar for names that end with a .. We should not do any probing for them on Windows - to maintain the behavior documented for them on MSDN.

#else // !FEATURE_PAL
#define PLATFORM_SHARED_LIB_SUFFIX_A ".dll"
#define PLATFORM_SHARED_LIB_PREFIX_A ""
#define PLATFORM_SHARED_LIB_SUFFIX_W W(".dll")
Copy link
Copy Markdown
Member

@jkotas jkotas Apr 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I do not think we need to have defines for these since they are used just once.

*numberOfVariations = COUNTOF(NameOnly);
}
}
#else
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It would be nice to add // FEATURE_PAL comment here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

*numberOfVariations = COUNTOF(SuffixLast);
}
}
#endif
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

bool containsSuffix = libName.EndsWithCaseInsensitive(W(".dll")) || libName.EndsWithCaseInsensitive(W(".exe"));
if (endWithDot || notContainDot || containsSuffix || !libNameIsRelativePath)
{
// file name end with '.'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not very useful. It is a literal rewrite of the above condition in plain English.

Instead, it would be useful to have comment that describes the reasons for this logic.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more comments

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Apr 14, 2018

Build errors:

src\vm\dllimport.cpp(6110): error C2220: warning treated as error - no 'object' file generated 
src\vm\dllimport.cpp(6110): warning C4800: 'BOOL': forcing value to bool 'true' or 'false' (performance warning)

@luqunl
Copy link
Copy Markdown
Author

luqunl commented Apr 14, 2018

Fixed build break due to compiler warning about BOOL to bool conversation.

@ghost
Copy link
Copy Markdown

ghost commented Apr 14, 2018

tests/src/Interop/DllImportAttribute/FileExtensionProbe.cs is encoded as UTF16LE (file --mime-encoding FileExtensionProbe.cs), and github is showing it as a binary file in diff view. You can fix it by changing it to ASCII using notepad file>saveAs or bash: iconv -f utf-16le -t US_ASCII FileExtensionProbe.cs > FileExtensionProbe.cs_new then delete the old and rename _new one.

bool containsSuffix = libName.EndsWithCaseInsensitive(W(".dll")) || libName.EndsWithCaseInsensitive(W(".exe"));

// The purpose of following code is to workaround LoadLibrary limitation:
// LoadLibray won't append extension if filename itself contains '.'. Thus It will break the following scenario:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: It -> it

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

#else // FEATURE_PAL
static void DetermineLibNameVariations(const char* const** libNameVariations, int* numberOfVariations, const SString& libName, bool libNameIsRelativePath)
{
bool endWithDot = libName.EndsWith(W(".")) == TRUE;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bad practice to compare BOOL to TRUE. BOOL is actually integer and any non-zero value can mean true. TRUE is defined as 1. So comparing BOOL to TRUE assumes that the non-zero true value is 1 that is not always the case.

Use either !!libName.EndsWith(W(".")) or libName.EndsWith(W(".")) != FALSE.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. Previous I had searched the implementation for SString.EndWith and it always return TRUE or FALSE. I will use !! instead.

@luqunl
Copy link
Copy Markdown
Author

luqunl commented Apr 14, 2018

tests/src/Interop/DllImportAttribute/FileExtensionProbe.cs is encoded as UTF16LE (file --mime-encoding FileExtensionProbe.cs), and github is showing it as a binary file in diff view. You can fix it by changing it to ASCII using notepad file>saveAs or bash: iconv -f utf-16le -t US_ASCII FileExtensionProbe.cs > FileExtensionProbe.cs_new then delete the old and rename _new one.

Thanks a lot for this info. I saw this issue but didn't realize that this is file encoding issue.

@luqunl
Copy link
Copy Markdown
Author

luqunl commented Apr 14, 2018

Typo in loadlibrary
Fixed


public class FileExtensionProbe
{
private static int Fails = 0;
Copy link
Copy Markdown

@ghost ghost Apr 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s_failures

@ghost
Copy link
Copy Markdown

ghost commented Apr 14, 2018

@jkotas, is there a chance to include this in 2.1 release?

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Apr 14, 2018

@jkotas, is there a chance to include this in 2.1 release?

There would need to be a good justification why this change is important to include in 2.1 release, and @luqunl would have to take this change for review with shiproom (a group of people who are driving the release).

#else // FEATURE_PAL
static void DetermineLibNameVariations(const char* const** libNameVariations, int* numberOfVariations, const SString& libName, bool libNameIsRelativePath)
{
bool endWithDot = !!libName.EndsWith(W("."));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I am looking at this again ... caching these in bool variables makes this less efficient than it needs to be. If you put these directly into the if statement without caching them in locals like this:

if (!libName.Find(libName.Begin(), W('.')) ||
    libName.EndsWith(W(".")) ||
    libName.EndsWithCaseInsensitive(W(".dll")) || libName.EndsWithCaseInsensitive(W(".exe"))
{

It will be more efficient because of it will stop at !libName.Find(libName.Begin(), W('.')) for names without dot; and the remaining two statements won't be executed at all.

It also avoids the !! wart.

Copy link
Copy Markdown
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo the one last nit.

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Apr 14, 2018

@jkotas, is there a chance to include this in 2.1 release?

@kasper3 Why do you think it is important to include this in 2.1 release?

@ghost
Copy link
Copy Markdown

ghost commented Apr 14, 2018

There would need to be a good justification why this change is important to include in 2.1

In 2.0 and older versions, we are using build time conditions:

#if WINNT
[DllImport("shared.library.dll")]
#else
[DllImport("shared.library")]
#endif

With this change, it will be reduced to [DllImport("shared.library")]. The sooner this inconsistency goes away, the better.

libName.EndsWith(W(".")) ||
libName.EndsWithCaseInsensitive(W(".dll")) ||
libName.EndsWithCaseInsensitive(W(".exe")) ||
!libNameIsRelativePath)
Copy link
Copy Markdown
Member

@jkotas jkotas Apr 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libNameIsRelativePath should be first because of it is the cheapest condition to evaluate?

Copy link
Copy Markdown
Member

@jkotas jkotas Apr 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is the relative path condition really correct here? libNameIsRelativePath is going to be true for a paths like:

XXX\YYY\library

or

\XXX\YYY\library.

On Unix, we will end up trying paths like:

libXXX\YYY\library.so

or

lib\XXX\YYY\library.so.

Copy link
Copy Markdown
Author

@luqunl luqunl Apr 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move libNameIsRelativePath to the first one.

is the relative path condition really correct here?

This is incorrect. since this is an old issue and I will create another issue to track and solve this problem

{
LPCWSTR currLibNameVariation = wszLibName;
#endif
currLibNameVariation.Printf(prefixSuffixCombinations[i], PLATFORM_SHARED_LIB_PREFIX_A, name, PLATFORM_SHARED_LIB_SUFFIX_A);
Copy link
Copy Markdown
Member

@jkotas jkotas Apr 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have converted the name from UTF8 to Unicode above once already.

The PrintF is going to convert it again; but I do not think it is going to use UTF8 on Windows. I think this needs to use the converted name to Unicode wszLibName; not the UTF8 name.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update all of these argument to WCHAR* instead of char*

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Apr 15, 2018

include this in 2.1 release

There seems to be way too many subtle issues in this change. I do not think we should be considering this for 2.1 release.

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Apr 16, 2018

LGTM

@ghost
Copy link
Copy Markdown

ghost commented Apr 17, 2018

@jkotas, if merge here indicates that we had confidence in implementation, is it now an good candidate for 2.1 if it is sent it to the shiproom for review?

@jkotas
Copy link
Copy Markdown
Member

jkotas commented Apr 17, 2018

master is open for 2.2 now. Merge means that we believe it is a good change for 2.2.

It would be up to @luqunl to present it to the shiproom to consider it for 2.1. My guess is that it would not meet the bar.

@luqunl luqunl deleted the #17150 branch April 17, 2018 05:53
@sdmaclea
Copy link
Copy Markdown

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Not possible to DllImport a native assembly with a "." in its file name

7 participants