Consider the following solution structure: Windows Store C# application + 2 native libraries PInvokeServer and PInvokeServer1. Native libraries code:
// PInvokeServer1
PINVOKESERVER1_API int TestFunction1(void)
{
return 5;
}
// PInvokeServer
PINVOKESERVER_API int TestFunction(void)
{
return TestFunction1();
}
Both functions are extern C. PInvokeServer depends on PInvokeServer1 (using linker dependencies). PInvokeServer.dll and PInvokeServer1.dll are added to C# project with build action Content, so they are part of the application package. C# declarations:
const string dllName = #"Native\PInvokeServer.dll";
const string dllName1 = #"Native\PInvokeServer1.dll";
[System.Runtime.InteropServices.DllImport(dllName, CallingConvention = CallingConvention.Cdecl)]
public static extern int TestFunction();
[System.Runtime.InteropServices.DllImport(dllName1, CallingConvention = CallingConvention.Cdecl)]
public static extern int TestFunction1();
Case 1, doesn't work (module not found):
TestFunction();
Case 2, works:
TestFunction1();
Case 3, works:
TestFunction1();
TestFunction();
Case 1: When PInvoke tries to load PInvokeServer.dll, it cannot resolve native runtime dependency, PInvokeServer1.dll is not loaded, and I get Module not found exception. Placing PInvokeServer1.dll, for example, to System32 directory doesn't help.
Case 2: PInvoke is able to load PInvokeServer1.dll directly.
Case 3. Once PInvokeServer1.dll is loaded, PInvokeServer.dll can be loaded successfully as well.
Im my real program I have native C library depending on several other libraries. All these libraries are added to C# Store application package. But high-level library cannot be loaded, because PInvoke fails to load dependencies. The only way I can think about is to load low-level libraries using LoadLibrary PInvoke call, and finally use PInvoke call to high-level library. Is there better way?
In a desktop application you could use AddDllDirectory or SetDllDirectory to modify the search path. But in a Windows Store application these functions are not available to you. So I see two options:
Put the two DLLs in the same directory as the executable. This is, by some distance, the simplest and safest solution. The executable's directory is always the first location searched and so you can be sure that the right DLLs will be loaded.
Before calling any function in either DLL, call LoadLibrary passing the absolute path to the DLLs to load them into the process. Load PInvokeServer1 before PInvokeServer. Change your p/invoke declarations to specify just the DLL filename. That is, remove the Native directory from the p/invoke declaration. By calling LoadLibrary explicitly, you make sure that the two DLLs are loaded into the process. Then subsequent calls to p/invoke functions will result in the already loaded DLLs being used.
Related
I have a 32bit Software written in C# who Communicates with many different devices.
Because of memory problems i upgraded to a 64bit System, no big Deal for the C# Software itself.
But for some of the implemented devices i had to change the way of implementation, it worked for all expect for one!
There i have a DLL which is written in C# 32bit, who is responsible for the communication to the device. Made by the supplier. I asked the supplier for a 64bit version but unfortunately its not possible for them to give me one.
My Idea was to create an Wrapper to make the DLL available to my 64bit Software.
I have seen that there are where many other people with a similar problem, but they always hat to Wrap an unmanaged DLL.
So my tried to write an wrapper who use COM to get the work done. But failed while implementing the C# COM Server into my C# Client. Here is the Idea what is tried:
https://blog.mattmags.com/2007/06/30/accessing-32-bit-dlls-from-64-bit-code/
Another Idea was to use a open source Project who can this topic with DLLImports:
https://github.com/CodefoundryDE/LegacyWrapper
There i could load it with DLLImport but i could not find the EntryPoint to my needed functions.
The question here is, if i'm completly on the wrong way and i can not load a managed DLL into C# with DLLImport or if i missed something and i just need to adjust the parameters.
Here is the Code i used so far (i changed the Names of the DLL)
[LegacyDllImport(#"C:\Program Files (x86)\Company\Device\API.dll")]
public interface IClassA : IDisposable
{
[LegacyDllMethod(CallingConvention = CallingConvention.Winapi)]
bool MethodA();
}
As addition to the #CyberMochi answer.
In particular case of x86-server and x64-client we don't need to call CoCreateInstance explicitly, as in case of bitness missmatch the standard .net COM interop mechanisms can do all the things automatically for us.
So, with the same sample (https://github.com/dotnet/samples/tree/main/core/extensions/OutOfProcCOM) I done the following:
(optional) Migrated to net6
Explicitly set bitnes for client (<PlatformTarget>x64</PlatformTarget>) and server (ExeServer.csproj: <PlatformTarget>x86</PlatformTarget>)
Built and registered server with "<path-to-samples>\ExeServer\bin\Debug\net6.0\ExeServer" /regserver (this need to be executed from elevated/admin command line)
Removed IServer declaration from the client project as the all information needed will be extracted from a tlb.
Reference the Server.Contract.tlb (from \ExeServer\bin\Debug\net6.0 folder) in the client project - "Add COM reference" into solution explorer. This added the following into csproj
<ItemGroup>
<COMReference Include="ServerLib">
<VersionMinor>0</VersionMinor>
<VersionMajor>0</VersionMajor>
<Guid>46f3feb2-121d-4830-aa22-0cda9ea90dc3</Guid>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
</ItemGroup>
Use the server from the client with the code
var server = new ServerLib.Server();
var pi = server.ComputePi();
Console.WriteLine(pi);
Some additional notes:
You can use per-user registration of the COM server. This will not require admin priveleges. But you will need to rewrite registration code - see here for details: Registering a COM without Admin rights
Instead of COM reference you can manually create COM interop DLL from the tlb with a tlbimp tool and reference this interop dll. This will speedup a build process.
I used the example: https://learn.microsoft.com/de-de/samples/dotnet/samples/out-of-process-com-server/
Here is the Git Link: https://github.com/dotnet/samples/tree/main/core/extensions/OutOfProcCOM
to create an out of process com server to handle the bitness difference.
i "only" had to get familiar with the concept and had to change the the call of the Server a bit. I changed the value of "CLSCTX_LOCAL_SERVER" from 0x4 to x40004 to force the call of a x86 Server!
private class Ole32
{
// https://docs.microsoft.com/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx
public const int CLSCTX_LOCAL_SERVER = 0x40004;//0x4;
// https://docs.microsoft.com/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance
[DllImport(nameof(Ole32))]
public static extern int CoCreateInstance(
[In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
IntPtr pUnkOuter,
uint dwClsContext,
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
[MarshalAs(UnmanagedType.IUnknown)] out object ppv);
}
I am new to PInvoke, I wanted to update my system volume using C#, I got one C++ dll and I have to just Import that DLL and use the methods to update the volume.
In C# I have written :
public const String DllName = "ChangeVolumeWindows.dll";
[DllImport(DllName, CharSet = CharSet.Auto)]
public static extern void SetSystemVolume(double newVolume, VolumeUnit vUnit);
I have put my dll into the exe path.
It is working fine for my pc , but when I package the app and send it to another machine I getting error " DLL not found "
I have observed that DLLImport is looking for path of my pc, which is not correct
For example:
I have put my dll in C:/User/ABC/Source/App/bin/debug/ChangeVolumeWindows.dll
in some another machine also it is looking for the same path instead of taking dll from C:/User/AnotherMachineUser/Source/App/bin/debug/ChangeVolumeWindows.dll
Can some one suggest where my code is going wrong.
I have tried to put my dll inside the project and the rebuild. Still I am facing the same issue.
Try this:
My.Application.Info.DirectoryPath("\folders\ChangeVolumeWindows.dll")
Above is my folder structure. I have a Cordova app and a Windows Runtime Component - IBscanUltimate. The include folder has the C# code calling into the unmanaged IBscanUltimate.dll. Class1.cs is like this:
using System;
namespace IBscanUltimate
{
public sealed class Class1
{
public static String getSDK()
{
IBscanUltimate.DLL.IBSU_SdkVersion a = new DLL.IBSU_SdkVersion();
IBscanUltimate.DLL._IBSU_GetSDKVersion(ref a);
return "SDKVersion: " + a;
}
}
The IBScanUltimateApi.cs & _IBSU_GetSDKVersion look something like this:
internal partial class DLL
{
[DllImport("IBScanUltimate.DLL")]
private static extern int IBSU_GetSDKVersion(ref IBSU_SdkVersion pVerinfo);
public static int _IBSU_GetSDKVersion(ref IBSU_SdkVersion pVerinfo)
{
int nRc = IBSU_STATUS_OK;
nRc = IBSU_GetSDKVersion(ref pVerinfo);
return nRc;
}
}
I have placed the DLL in many locations to see if it'll get picked up and they all have the above properties. But when I try to run my app, it says unable to locate the IBScanUltimate.DLL
This is how the output is coming:
I am not sure what is it that I am doing wrong and why the DLLImport cannot find my dll. Thank you for your help.
Exact error is:
System.DllNotFoundException: Unable to load DLL 'IBScanUltimate.DLL': The specified module could not be found. (Exception from HRESULT: 0x8007007E)
Update #1:
I have come across https://msdn.microsoft.com/en-us/library/windows/desktop/hh447159(v=vs.85).aspx This article is explaining that LoadPackagedLibrary function can be used to load the dll. I am not seeing any example on how to use this in C#.
Update #2:
Specify the search path for DllImport in .NET Mentions that SetDllDirectory or AddDllDirectory can be used. He has a code snippet for SetDllDirectory, but the argument is string[] paths. How would I specify the relative argument?
Update #3:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
public static bool setPath(String path)
{
//Windows.Storage.
//return SetDllDirectory("ms-appx:///");
return SetDllDirectory(path);
}
I tried calling the SetDllDirectory(path) method with various locations that my app should have access to but I am keep getting "false". Few examples that I have tried:
NativeMethods.setPath(Package.Current.InstalledLocation.Path.ToString());
StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
StorageFolder folder = Windows.Storage.KnownFolders.MusicLibrary;
This is where my app is installed:
C:\Users\AAA\App\hello\platforms\windows\build\windows\Debug\x64\AppX
and I can see that my DLL is there. But still I'm getting the exception that DLL cannot be found. Do I have to put something on the manifest regarding this?
Update #4:
I ran a dumpbin on the DLL and i see the below DLL in the dumpbin:
WINUSB.DLL
mfc90.dll
MSVCR90.dll
KERNEL32.dll
USER32.dll
GDI32.dll
VERSION.dll
MSVCP90.dll
SETUPAPI.dll
I guess I'd like to check on each dll above separately to see if my windows runtime can pick it? One of them could be the culprit that's not being loaded?
Update #5:
Upon seeing the answer from Peter Torr - MSFT, and googling for MFC I came across this article https://msdn.microsoft.com/en-us/library/d06h2x6e.aspx Which states:
The MFC classes and their members cannot be used in applications that execute in the Windows Runtime.
I guess to conclude this wild hunt now. I would close this up that the library I tried to load is dependent on libraries not available for Windows Runtime.
I had this feeling because Windows form application would run but the the code converted to Windows Runtime would give the error that the DLL is not being found. Thanks to Peter for guiding in the right direction.
The DLL you are trying to load was clearly built for desktop apps (it has User, GDI, and MFC imports) and will not work as a UWP binary. I suspect also that the DLL does not have the AppContainer flag set (an option you pass to the linker). You will need to find another way to accomplish what you need (if necessary, please make any feature requests via the Windows Platform UserVoice.
I suspect that it can find your DLL just fine, but it fails to find one or more of its dependencies. Unfortunately, both of these cases result in extremely generic DllNotFoundException that mentions the DLL your try to P/Invoke to.
There is an easy way to figure out what's missing! Windows contains a feature called "loader snaps", which, when enabled, will log all the things that Windows DLL loader does for your process. That means it will also print what DLLs it fails to load. To enable it, run this in admin command prompt:
gflags.exe -i "<executableName>.exe" +sls
Where executable name is just the name of your executable without the folder. To see the output, you will also need to enable either native or mixed mode debugger. You can do that in your project properties debugging tab in Visual Studio.
You can read more about load snaps here:
https://blogs.msdn.microsoft.com/junfeng/2006/11/20/debugging-loadlibrary-failures/
I'm trying to create an .sav file programmatically without having to use SPSS automation (the SPSS.BackendAPI library) in order to free up more SPSS licenses. I found this library at CodePlex that uses the 32-bit I/O module without requiring a license, which is good.
The problem is that I need to build the application as x64 in order to gain access to the extra addressable memory in my own application. Thus, I need to use the 64-bit libraries as well. Has anyone had luck using the 64-bit libraries in managed code?
You can use that library from CodePlex, but you'll have to modify it a bit to work with the spssio64.dll that's included with the I/O Module. In the SpssThinWrapper.cs file, you'll need to change the DLL that's being imported. You'll also have to change some of the entry points. To get the names of the entry points in the 64-bit DLL, you'll want to run dumpbin /exports spssio64.dll. If you do this you'll see that the the 64-bit and 32-bit entry points are basically the same, except that some of the 32-bit ones have an # sign and a number after them, whereas none of the 64-bit entry points do. Do change all of those along with the DLL in the DllImport attribute. For example:
[DllImport("spssio32.dll", EntryPoint="spssCloseAppend#4", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
public static extern ReturnCode spssCloseAppend(int handle);
becomes
[DllImport("spssio64.dll", EntryPoint = "spssCloseAppend", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
public static extern ReturnCode spssCloseAppend(int handle);
and so on.
After doing this you'll want to make sure you're using the correct DLLs. Copy the spssio64.dll, icudt32.dll, icuin32.dll, and icuuc32.dll from the win64 folder of the I/O Module into the Resources folder from the SPSS .NET library from CodePlex. This will overwrite the existing 32-bit dlls, so if you need both 32-bit and 64-bit you'll have to do something different, but it sounds like you just need 64-bit, so this should work.
As an example of how easy it is to create an .sav with this library:
using (SpssDataDocument spssDoc = SpssDataDocument.Create("test.sav")) {
SpssVariable v = new SpssNumericVariable();
v.Name = "gender";
v.Label = "What is your gender?";
v.ValueLabels.Add(1, "Male");
v.ValueLabels.Add(2, "Female");
doc.Variables.Add(v);
doc.CommitDictionary();
SpssCase c = doc.Cases.New();
c["gender"] = 1;
c.Commit();
}
The library handles all of the spss* calls for you, and makes sure they're in the right order and everything.
Why don't you just use the SPSS Statistics i/o dll available via the SPSS Community site (www.ibm.com/developerworks/spssdevcentral)? It is free and comes in 32 and 64 bit versions for all the supported SPSS platforms. It does not require an SPSS license.
I have a c++ function with the following signature
TCHAR *DXGetErrorDescription9(HRESULT hr);
I have
[DllImport("dxerr9.dll",EntryPoint="GetDXErrorString9")]
static public extern string GetDXErrorString9(int DXError);
How do I marshall string to TCHAR*? Thanks
After doing a little digging, it turns out that the directx error routines are not shipped as dlls, they're shipped as libs that you statically link into your executable. I should have figured this based on the size of dxerr9.lib (it's 5M, way larger than any of the other stub libs).
So to get this to work I did the following:
Make a "wrapper" native dll that wraps all the error handling lib functions. Since there are only 3 (DXGetErrorDescription, DXGetERrorString and DXTrace) this isn't that big a deal.
Have .NET call the wrapper functions.
You can do this in visual studio by creating a Win32 project (change application settings so that it's a dll and so that it exports symbols). I called min "DirectXErrWrapper".
Inside the wrapper project add a passthrough for the DXGetErrorDescription. I called mine DXGetErrDescPassThrough():
extern "C" {
DIRECTXERRWRAPPER_API TCHAR * DXGetErrDescPassThrough(HRESULT hr)
{
return (TCHAR *)DXGetErrorDescription(hr);
}
}
The extern "C" block is important (has to do with demangling the C++ names). This project will need to #include "dxerr.h" and statically link dxerr.lib so you'll have to add the directx sdk include dir to your include path and the directx sdk lib dir to your lib path.
Then have your managed code import the call from your wrapper dll:
[DllImport("DirectXErrWrapper.dll", CharSet=CharSet.Unicode)]
static public extern string DXGetErrDescPassThrough(int DXError);
In 2005 native projects use unicode by default so the DllImport needed to specify unicode. Lastly, call it from managed code via:
Debug.WriteLine("DXGettErrDescPassThrough=" + DXGetErrDescPassThrough(0));
In this case the call prints "The function completed successfully" to the debug device since 0 is success.
(previous post follows)
That looks like one of the DirectX functions (C++ not C# AFAIK). I'm pretty sure it ships with unicode and ansi versions.
TCHAR will map to unicode when compiled on unicode (the T is a mnemonic for type; unicode on unicode, multibyte characterset on mbcs, ansi otherwise).
You can usually use CharSet=CharSet.Auto but if that causes compatability problems (e.g., you've gotta use ansi strings because of some struct that you've already ported requires it) you can use whichever is consistent with your project (either Charset.Unicode if you're all unicode or Charset.Ansi) you should have no problem marshaling it into a C# string. You may need to decorate it as [OUT] though the return value may be OUT implicitly.
I too wish DirectX shipped w/ TLBs following the standard [out, retval] convention!