Repository: pythonnet/clr-loader (language: python)

Issue:
clr-loader failed to load dll if the assembly path contain Chinese
### Environment

-   Pythonnet version: 3.0.1
-   Python version: 3.8.10
-   Operating System: Windows 11
-   .NET Runtime: 

### Details
I find that everything just works fine if Python path doesn't contains any Chinese characters. But if the path contain some Chinese chars, let's said, start a python interactive shell via the following command:
```bash
D:\中文测试\CCInsight\python-3.8.10-embed-amd64\python.exe
```
And then when I run `import clr`, the following error will be raised.
```
>>> import clr
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\中文测试\CCInsight\python-3.8.10-embed-amd64\lib\site-packages\clr.py", line 6, in <module>
    load()
  File "D:\中文测试\CCInsight\python-3.8.10-embed-amd64\lib\site-packages\pythonnet\__init__.py", line 143, in load
    if func(b"") != 0:
  File "D:\中文测试\CCInsight\python-3.8.10-embed-amd64\lib\site-packages\clr_loader\types.py", line 64, in __call__
    return self._callable(ffi.cast("void*", buf_arr), len(buf_arr))
RuntimeError: cannot call null pointer pointer from cdata 'int(*)(void *, int)'
```
I delivery a software that provide a GUI depends on pythonnet, this become an annoy bug as user may choose their own installation location, which may contain Chinese chars. Hope to find a fix or a work around. 

Update: 

I try to narrow down the scope of code to 

https://github.com/pythonnet/clr-loader/blob/master/clr_loader/netfx.py#L36-L44 

It looks to me like the `_get_callable` failed to get the right callabe object by the following args
```
assembly_path:  d:\中文测试\CCInsight\python-3.8.10-embed-amd64\lib\site-packages\pythonnet\runtime\Python.Runtime.dll
typename:          Python.Runtime.Loader
funcname:          Initialize
```


Discussion / hints:
can fix by change the _get_callable() function to this
```python
import locale

def _get_callable(self, assembly_path: StrOrPath, typename: str, function: str):
    encoding = "gbk" if locale.getpreferredencoding() == "cp936" else "utf8"
    func = _FW.pyclr_get_function(
        self._domain,
        str(Path(assembly_path)).encode(encoding),
        typename.encode("utf8"),
        function.encode("utf8"),
    )
```

Fix patch:
diff --git a/netfx_loader/ClrLoader.cs b/netfx_loader/ClrLoader.cs
index e8b2767..af89cee 100644
--- a/netfx_loader/ClrLoader.cs
+++ b/netfx_loader/ClrLoader.cs
@@ -51,9 +51,9 @@ public static IntPtr CreateAppDomain(
         [DllExport("pyclr_get_function", CallingConvention.Cdecl)]
         public static IntPtr GetFunction(
             IntPtr domain,
-            [MarshalAs(UnmanagedType.LPStr)] string assemblyPath,
-            [MarshalAs(UnmanagedType.LPStr)] string typeName,
-            [MarshalAs(UnmanagedType.LPStr)] string function
+            [MarshalAs(UnmanagedType.LPUTF8Str)] string assemblyPath,
+            [MarshalAs(UnmanagedType.LPUTF8Str)] string typeName,
+            [MarshalAs(UnmanagedType.LPUTF8Str)] string function
         )
         {
             try


Test patch:
diff --git a/tests/test_common.py b/tests/test_common.py
index f42020a..250c290 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -1,3 +1,4 @@
+import shutil
 import pytest
 from subprocess import check_call
 import os
@@ -24,7 +25,7 @@ def build_example(tmpdir_factory, framework):
     return out
 
 
-def test_mono(example_netstandard):
+def test_mono(example_netstandard: Path):
     from clr_loader import get_mono
 
     mono = get_mono()
@@ -33,7 +34,7 @@ def test_mono(example_netstandard):
     run_tests(asm)
 
 
-def test_mono_debug(example_netstandard):
+def test_mono_debug(example_netstandard: Path):
     from clr_loader import get_mono
 
     mono = get_mono(
@@ -46,7 +47,8 @@ def test_mono_debug(example_netstandard):
 
     run_tests(asm)
 
-def test_mono_signal_chaining(example_netstandard):
+
+def test_mono_signal_chaining(example_netstandard: Path):
     from clr_loader import get_mono
 
     mono = get_mono(set_signal_chaining=True)
@@ -54,7 +56,8 @@ def test_mono_signal_chaining(example_netstandard):
 
     run_tests(asm)
 
-def test_mono_set_dir(example_netstandard):
+
+def test_mono_set_dir(example_netstandard: Path):
     from clr_loader import get_mono
 
     mono = get_mono(assembly_dir="/usr/lib", config_dir="/etc")
@@ -62,7 +65,8 @@ def test_mono_set_dir(example_netstandard):
 
     run_tests(asm)
 
-def test_coreclr(example_netcore):
+
+def test_coreclr(example_netcore: Path):
     from clr_loader import get_coreclr
 
     coreclr = get_coreclr(runtime_config=example_netcore / "example.runtimeconfig.json")
@@ -71,7 +75,7 @@ def test_coreclr(example_netcore):
     run_tests(asm)
 
 
-def test_coreclr_autogenerated_runtimeconfig(example_netstandard):
+def test_coreclr_autogenerated_runtimeconfig(example_netstandard: Path):
     from multiprocessing import get_context
 
     p = get_context("spawn").Process(
@@ -82,7 +86,7 @@ def test_coreclr_autogenerated_runtimeconfig(example_netstandard):
     p.close()
 
 
-def _do_test_coreclr_autogenerated_runtimeconfig(example_netstandard):
+def _do_test_coreclr_autogenerated_runtimeconfig(example_netstandard: Path):
     from clr_loader import get_coreclr
 
     coreclr = get_coreclr()
@@ -94,9 +98,24 @@ def _do_test_coreclr_autogenerated_runtimeconfig(example_netstandard):
 @pytest.mark.skipif(
     sys.platform != "win32", reason=".NET Framework only exists on Windows"
 )
-def test_netfx(example_netstandard):
+def test_netfx(example_netstandard: Path):
+    from clr_loader import get_netfx
+
+    netfx = get_netfx()
+    asm = netfx.get_assembly(example_netstandard / "example.dll")
+
+    run_tests(asm)
+
+
+@pytest.mark.skipif(
+    sys.platform != "win32", reason=".NET Framework only exists on Windows"
+)
+def test_netfx_chinese_path(example_netstandard: Path, tmpdir_factory):
     from clr_loader import get_netfx
 
+    tmp_path = Path(tmpdir_factory.mktemp("example-中国"))
+    shutil.copytree(example_netstandard, tmp_path, dirs_exist_ok=True)
+
     netfx = get_netfx()
     asm = netfx.get_assembly(os.path.join(example_netstandard, "example.dll"))