Using 64-Bit SPSS Libraries in C# - c#

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.

Related

64bit C# .Net Wrapper for 32bit C# .Net DLL

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);
}

Does cross-platform C++ require building separately on every single platform?

My question actually has two variants, but for some context, I have a C++ *.DLL that I use with Unity using P/Invoke. It's built on Windows 10 with MSBuild (platform toolset: ClangCL).
And now for my questions,
Does the size of a particular type stay constant after the DLL has been built? Or does it need to be rebuilt again on the target platform (say, Linux or Mac)?
My code looks a bit like this:
typedef wchar_t wchar;
static_assert(sizeof(wchar) == 2, "WideChar size-test failed.");
After I have recompiled the source on Windows, if I build the Unity project for the Android platform, will the size of wchar remain the same on there as well? Or am I in for a nasty surprise when my string-handling code just stops working?
Are some of the other language features maintained after the DLL is built, or do I need to change my workflow to account for something else?
In particular, let's look at something super-basic, like extern methods.
#define CALLING_CONVENTION __stdcall
#if _WIN32
#define DLLEXPORT(type) extern "C" __declspec(dllexport) type CALLING_CONVENTION
#else
#define DLLEXPORT(type) extern "C" type CALLING_CONVENTION
#endif
DLLEXPORT(void) DoSomething(const wchar* ManagedString);
// expands to
// extern "C" __declspec(dllexport) void __stdcall DoSomething(const wchar* ManagedString)
// on Windows
And for managed,
[DllImport("DllName", CharSet = CharSet.Unicode)]
public static extern void DoSomething([MarshalAs(UnmanagedType.LPWStr)] string input);
Will this maintain interoperability for other platforms as well? Or do I need to go out of my way to build the C++ DLL on Linux and then use that for an Android platform?
Each platform usually has a different ABI. For example, long on Windows is 32-bit (from memory), but long on Linux is 64-bit. In your example, wchar_t on Windows is 16-bit, but on Linux it is 32-bit. The way that C++ names are mangled is also platform-specific.
Both Windows and Linux have different syscalls (standard library functions like malloc usually end up calling platform-specific syscalls). But not only that; the way that each platform stores and loads executable code (and how it dynamically links to other executable code) is also different, and without some sort of translator in between (such as Wine), it's typically not possible to run executable code that was compiled for a different platform.
So in general, you will need to compile your code for each platform you intend to support. See also this post in the Unity forums.

Resolving native libraries inside WinRT package

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.

DllNotFoundException with HRESULT 0x8007007E when loading 64-bit dll

I downloaded zlib and compiled the library as both Windows 32-bit and Windows 64-bit dll. I now have zlibwapi.dll and zlibwapi64.dll.
The dlls are copied into my application folder and are referenced as follows:
[DllImport(#"zlibwapi.dll", EntryPoint = "uncompress", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = false)]
private static extern int uncompress32(
IntPtr dest,
ref uint destLen,
[In(), MarshalAs(UnmanagedType.LPArray)] byte[] source,
uint sourceLen
);
[DllImport(#"zlibwapi64.dll", EntryPoint = "uncompress", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = false)]
private static extern int uncompress64(
IntPtr dest,
ref uint destLen,
[In(), MarshalAs(UnmanagedType.LPArray)] byte[] source,
uint sourceLen
);
At runtime I check whether I'm 32-bit or 64-bit, and call appropriate version.
This works fine if I'm 32-bit, but the 64-bit version gives
Cannot load DLL "zlibwapi64.dll": Module not found. (HRESULT exception: 0x8007007E)
I've found many similar questions on the Internet, and the suggested reason was that the library depends on some other libraries, and it is those libraries that may not be found.
This does not seem to the case:
zlibwapi64.dll only depends on Kernel32.dll and MSVCR90.dll. I do have VS2008 C++ runtimes installed, both 32 and 64 bit.
When I try to load zlibwapi64.dll from a non-managed C++ application, it loads no problem. It is C# that fails to load it.
I have tried setting absolute path to the 64-bit dll, it does not help.
How do I make it work?
It is a fairly basic "file not found" kind of error, unfortunately it doesn't tell you explicitly what DLL it couldn't find. You already know about the issue with dependent DLLs. Note that you can avoid the irksome dependency on msvcr90.dll by compiling the code with /MT
You'll need to debug the problem and that requires getting insight in where it is looking for DLLs. One good tool is SysInternals' ProcMon utility, it shows you exactly where your program is looking for files. You should see it probing for the DLL, searching through the directories of the PATH and failing to find the file.
Unfortunately ProcMon is a bit chatty and has a habit of drowning you in the data. A more dedicated tool is GFlags.exe, a tool available from the Debugging Tools for Windows package. These days included with the Windows SDK. Stored in c:\program files (x86)\debugging tools for windows\gflags.exe after you installed it. You can turn on the "Show loader snaps" option. On later Windows versions, that tells the Windows loader to generate debugging messages when it is searching for DLLs. They'll appear in the Output window when you enable unmanaged debugging.
Try ProcMon first, much easier to get going.
And of course consider pure managed solutions so you are not fighting these kind of install problems. Good ones are DotNetZip and SharpZipLib, take their first google hit.
Another good tool for examining Dll dependencies is Dependency walker (depends). It looks at the file in a static way so a little easier than using process monitor.
http://www.dependencywalker.com/

BadImageFormatException: PInvoke ImportDll with hdf5dll.dll

Ok, I have the HDF5 library downloaded from the official site, and I have a few DLLs, including hdf5dll.dll, and hdf5_hldll.dll.
I have what I think to be some wrappers around the native calls, in my classes H5, H5LT, H5F, and H5T. Example from H5.cs:
namespace HDF5
{
using hid_t = System.Int32;
using herr_t = System.Int32;
using hsize_t = System.UInt64;
using size_t = System.UInt32;
// hbool_t is 0:false, +:true
using hbool_t = System.UInt32;
// htri_t is 0:false, +:true, -:failure
using htri_t = System.Int32;
public class H5
{
const CharSet StringMarshallingType = CharSet.Ansi;
const string DLLNAME = "hdf5dll.dll";
///* Functions in H5.c */
//H5_DLL herr_t H5open(void);
[DllImport(DLLNAME,
CharSet = StringMarshallingType)]
public static extern herr_t H5open();
And in Program.cs, I use H5.H5open();, but I get a BadImageFormatException. Do I need a different DLL? Does the method signature look wrong?
I'd like to, as the next step, get this in C#: http://www.hdfgroup.org/HDF5/Tutor/h5lite.html .
OS: Windows 7 64 bit
Environment: Visual Studio 2008 Professional
Update: I don't know if this will be related, and I don't remember if my environment is VS2008 SP1, but this question may hold a key to solving the mystery. I am as of now trying to repeat the scenario on 32 bit VS 2010 at home.
That happens when you're trying to run P/Invoke operations on a dll meant for x86 architecture from within an x64 process or vice versa. I'd check all of that and if they're out of sync, consider targeting the processor that HDF5 targets with your application, or checking if a processor-specific version is available.
Looking at the documentation from here, the function prototype is:
herr_t H5open(void);
And also the DLLNAME is disallowed, you must explicitly specify the dll name - no questions asked.
The proper signature is:
[DllImport("hdf5dll.dll")]public static extern herr_t H5open();
Make sure you have the type herr_t defined...
Let the runtime take care of the marshalling for you....
Also make sure, the DLL is present in the same path as where the compiled .EXE (your code) is generated.
Edit: Thanks to the OP for pointing out my blooper....
On x64 operatingsystems .net programs usually run in x64 mode.
Just set your target processor architecture to x86 and try again.
Just in Visual studio open your "Solution Configuration"-Manager and add a new target Platform.

Categories