I have a dll that exports not only functions, but also values. The dll I'm interested in is from the R Project (http://www.r-project.org/), it's in the R.dll that contains the r language runtime. The declaration in the header file is:
LibExtern SEXP R_GlobalEnv;
And when when I run dumpbin /exports I can see:
194 C1 00326C08 R_GlobalEnv
But I can't seem to find any examples of how to bind such a value from C# or F#. Can anyone enlightenment me to how I might get a reference to it?
I don't known if there is a way directly from C# or F#. But I think that a C++/CLI wrapper should work.
After Nick pointed me in the direction of R.NET I was able to look and see how they solved this problem.
First they use the win32 api to load the library (or equivalent on other platforms):
#if MAC || LINUX
private static IntPtr LoadLibrary(string filename)
{
const int RTLD_LAZY = 0x1;
if (filename.StartsWith("/"))
{
return dlopen(filename, RTLD_LAZY);
}
string[] searchPaths = (System.Environment.GetEnvironmentVariable(LibraryPath, EnvironmentVariableTarget.Process) ?? "").Split(Path.PathSeparator);
foreach (string directory in searchPaths)
{
string path = Path.Combine(directory, filename);
if (File.Exists(path))
{
return dlopen(path, RTLD_LAZY);
}
}
return IntPtr.Zero;
}
[DllImport(DynamicLoadingLibraryName)]
private static extern IntPtr dlopen([MarshalAs(UnmanagedType.LPStr)] string filename, int flag);
#elif WINDOWS
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
Then they use get GetProcAddress function from kernel32.dll (or equivalent on other platforms) to get the actual address of the variable:
#if MAC
[DllImport("libdl.dylib", EntryPoint = "dlsym")]
#elif LINUX
[DllImport("libdl.so", EntryPoint = "dlsym")]
#elif WINDOWS
[DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
#endif
private static extern IntPtr GetSymbolPointer(IntPtr handle, [MarshalAs(UnmanagedType.LPStr)] string symbol);
Related
I have been following the guidelines from:
https://www.meziantou.net/stop-using-intptr-for-dealing-with-system-handles.htm
So basically I have now a wrapper class on top of my native code as follow:
public sealed class MyFileWrapper : IDisposable
{
private readonly MySafeHandle _handle;
// implement cstor and Dispose() as recommended
public int ReadFile(byte[] bytes, int numBytesToRead) {
int ret = NativeMethods.ReadFile(_handle...);
// ...
return numBytesRead;
}
}
with:
internal static class NativeMethods
{
[...]
// Take a SafeHandle in parameter instead of IntPtr
[DllImport("kernel32", SetLastError = true)]
internal extern static int ReadFile(MySafeHandle handle, byte[] bytes, int numBytesToRead, out int numBytesRead, IntPtr overlapped_MustBeZero);
[...]
}
What would be a good practice to unit test this ReadFile function (using Xunit) ? It seems like the static class NativeMethods is against good practice for unit testing, so I was wondering what people do in this case. It seems that hiding the implementation detail of MySafeHandle is part of the solution, so I suspect turning NativeMethods into an interface would expose too much of the low level.
I'm using following struct for data marshaling with PInvoke
[StructLayout(LayoutKind.Sequential)]
struct Data
{
int id;
IntPtr state;
object obj;
}
And all works fine on Windows, but on Linux I'm getting the error:
Cannot marshal field 'obj' of type 'Data': Invalid managed/unmanaged type combination (Marshaling to and from COM interface pointers isn't supported).
Why?
Is it possible to disable marshaling for the specific field ?
object obj; property is invalid
You can try with IntPtr obj;
What is the actual type of obj?
Digging the CLR sources I've found that the issue is really Linux specific, and tied to COM interop feature, which is windows-only.
So I've fixed the problem using IntPtr for obj (as #Simonare suggested) and GCHandle.
Does exists some Axapta method to get the size of a file ?
Thanks
You can use WinAPI function GetFileSize;
client public static int getFileSize(int hFile)
Example usage code is here;
http://msdn.microsoft.com/en-us/library/aa609747(v=ax.50).aspx
References;
http://msdn.microsoft.com/en-us/library/aa586314(v=ax.10).aspx
You can use the System.IO namespace too. For whatever reason, I don't have the getFileSize() in my AX instance available.
server static void main(Args args)
{
System.IO.FileInfo fi;
int64 length;
;
fi = new System.IO.FileInfo(#'C:\temp.csv');
length = fi.get_Length();
info(strfmt("%1", int642str(fi.get_Length())));
}
Short version: Is it possible or not to use impersonation in ASP.NET to access mapped drives?
Long Version:
I'm currently using impersonation in ASP.NET to gain access to network files. This is working perfectly for any network file using a UNC path, but it is failing to access any files on mapped drives defined for the user account I'm impersonating.
For example, let's say a file lives on the network at \\machine\folder\file.txt, and let's also say that drive S: is mapped to \\machine\folder. We need to be able to access both the full UNC path, \\machine\folder\file.txt, as well as the shorter, mapped drive path, S:\file.txt.
Obviously the standard ASP.NET process cannot access either.
Using a console application that runs under the local account with the mapped S: drive, calling File.Exists(#"\\machine\folder\file.txt") returns true, and File.Exists(#"S:\file.txt") also returns true.
However, when impersonating in an ASP.NET context with the same local account, only File.Exists(#"\\machine\folder\file.txt") returns true. File.Exists(#"S:\file.txt") returns false.
I'm testing with IIS 7 running on my local Windows 7 Professional box, though this will need to run in both IIS 6 and IIS 7.
Impersonation is handled with a couple of classes in C# which I'll include here:
public static class Impersonation
{
private static WindowsImpersonationContext context;
public static void ImpersonateUser(string username, string password)
{
ImpersonateUser(".", username, password);
}
public static void ImpersonateUser(string domain, string username, string password)
{
StopImpersonating();
IntPtr userToken;
var returnValue = ImpersonationImports.LogonUser(username, domain, password,
ImpersonationImports.LOGON32_LOGON_INTERACTIVE,
ImpersonationImports.LOGON32_PROVIDER_DEFAULT,
out userToken);
context = WindowsIdentity.Impersonate(userToken);
}
public static void StopImpersonating()
{
if (context != null)
{
context.Undo();
context = null;
}
}
}
public static class ImpersonationImports
{
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_LOGON_NETWORK = 3;
public const int LOGON32_LOGON_BATCH = 4;
public const int LOGON32_LOGON_SERVICE = 5;
public const int LOGON32_LOGON_UNLOCK = 7;
public const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
public const int LOGON32_PROVIDER_DEFAULT = 0;
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken
);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int ImpersonateLoggedOnUser(
IntPtr hToken
);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int RevertToSelf();
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int CloseHandle(IntPtr hObject);
}
Then, during Page_Load, we basically do something like this:
Impersonation.ImpersonateUser("DOMAIN", "username", "password");
if (!File.Exists(#"S:\file.txt"))
throw new WeCannotContinueException();
I realize using mapped drives isn't a best practice, but for legacy reasons it's desirable for our business. Is it possible or not to use impersonation in ASP.NET to access mapped drives?
No, but you can use a symbolic link instead. Mklink /d will create a directory link.
You can only access mapped drives that were created by the user being impersonated.
So if you were to impersonate user X then map the share (e.g., via net use) then that share will be visible for as long as the impersonation is in effect.
You can determine which mapped drives are currently accessible via DriveInfo.GetDrives(). Drives with a DriveType of Network are accessible in the current security context.
I tried the mklink solution from Scott, and it is not working with ASP.NET.
As for answer from arnshea: it is not working at all; I even impersonated with the domain administrator, and all permissions set to everyone, iuser and network service.
So the only solution: when you design your web application you must decide whether you want to save to network resource and use the UNC protocol for that.
A mapped network drive does not work with ASP.NET for regular file operations.
This only clears items in the user cache:
public static void ClearCache()
{
foreach (DictionaryEntry entry in HttpRuntime.Cache)
{
HttpRuntime.Cache.Remove(entry.Key.ToString());
}
}
Is there any way to access the kernel cache as well?
Clarification: I want to print the keys of all items in the kernel cache, and as a bonus I'd like to be able to clear the kernel cache from a C# method as well.
Yep, it's possible to programmatically enumerate and remove items from IIS's kernel cache.
Caveats:
non-trivial text parsing requred for enumeration
lots of ugly P/Invoke required for removal
Also, you'll need at least Medium Trust (and probably Full Trust) to do the things below.
Removal won't work in IIS's integrated pipeline mode.
Enumeration probably won't work on IIS6
Enumeration:
The only documented way I know to enumerate the IIS kernel cache is a command-line app available in IIS7 and above (although you might be able to copy the NETSH helper DLL from V7 onto a V6 system-- haven't tried it).
netsh http show cachestate
See MSDN Documentation of the show cachestate command for more details. You could turn this into an "API" by executing the process and parsing the text results.
Big Caveat: I've never seen this command-line app actually return anything on my server, even for apps running in Classic mode. Not sure why-- but the app does work as I can see from other postings online. (e.g. http://chrison.net/ViewingTheKernelCache.aspx)
If you're horribly allergic to process creation and feeling ambitious, NETSH commands are implemented by DLL's with a documented Win32 interface, so you could write code which pretends it's NETSH.exe and calls into IIS's NETSH helper DLL directly. You can use the documentation on MSDN as a starting point for this approach. Warning: impersonating NETSH is non-trivially hard since the interface is 2-way: NETSH calls into the DLL and the DLL calls back into NETSH. And you'd still have to parse text output since the NETSH interface is text-based, not object-based like PowerShell or WMI. If it were me, I'd just spawn a NETSH process instead. ;-)
It's possible that the IIS7 PowerShell snapin may support this functionality in the future (meaning easier programmatic access than the hacks above), but AFAIK only NETSH supports this feature today.
Invalidation:
I've got good news and bad news for you.
The good news: Once you know the URL of the item you want to yank from IIS's kernel cache, there's a Win32 API available to remove it on IIS6 and above. This can be called from C# via P/Invoke (harder) or by putting the call in a managed C++ wrapper DLL. See HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK on MSDN for details.
I took a stab at the code required (attached below). Warning: it's ugly and untested-- it doesn't crash my IIS but (see above) I can't figure out how to get cache enumeration working so I can't actually call it with a valid URL to pull from the cache. If you can get enumeration working, then plugging in a valid URL (and hence testing this code) should be easy.
The bad news:
as you can guess from the code sample, it won't work on IIS7's integrated pipeline mode, only in Classic mode (or IIS6, of course) where ASP.NET runs as an ISAPI and has access to ISAPI functions
messing with private fields is a big hack and may break in a new version
P/Invoke is hard to deal with and requires (I believe) full trust
Here's some code:
using System;
using System.Web;
using System.Reflection;
using System.Runtime.InteropServices;
public partial class Test : System.Web.UI.Page
{
/// Return Type: BOOL->int
public delegate int GetServerVariable();
/// Return Type: BOOL->int
public delegate int WriteClient();
/// Return Type: BOOL->int
public delegate int ReadClient();
/// Return Type: BOOL->int
public delegate int ServerSupportFunction();
/// Return Type: BOOL->int
public delegate int EXTENSION_CONTROL_BLOCK_GetServerVariable();
/// Return Type: BOOL->int
public delegate int EXTENSION_CONTROL_BLOCK_WriteClient();
/// Return Type: BOOL->int
public delegate int EXTENSION_CONTROL_BLOCK_ReadClient();
/// Return Type: BOOL->int
public delegate int EXTENSION_CONTROL_BLOCK_ServerSupportFunction();
public static readonly int HSE_LOG_BUFFER_LEN = 80;
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
public struct EXTENSION_CONTROL_BLOCK
{
/// DWORD->unsigned int
public uint cbSize;
/// DWORD->unsigned int
public uint dwVersion;
/// DWORD->unsigned int
public uint connID;
/// DWORD->unsigned int
public uint dwHttpStatusCode;
/// CHAR[HSE_LOG_BUFFER_LEN]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 80 /*HSE_LOG_BUFFER_LEN*/)]
public string lpszLogData;
/// LPSTR->CHAR*
public System.IntPtr lpszMethod;
/// LPSTR->CHAR*
public System.IntPtr lpszQueryString;
/// LPSTR->CHAR*
public System.IntPtr lpszPathInfo;
/// LPSTR->CHAR*
public System.IntPtr lpszPathTranslated;
/// DWORD->unsigned int
public uint cbTotalBytes;
/// DWORD->unsigned int
public uint cbAvailable;
/// LPBYTE->BYTE*
public System.IntPtr lpbData;
/// LPSTR->CHAR*
public System.IntPtr lpszContentType;
/// EXTENSION_CONTROL_BLOCK_GetServerVariable
public EXTENSION_CONTROL_BLOCK_GetServerVariable GetServerVariable;
/// EXTENSION_CONTROL_BLOCK_WriteClient
public EXTENSION_CONTROL_BLOCK_WriteClient WriteClient;
/// EXTENSION_CONTROL_BLOCK_ReadClient
public EXTENSION_CONTROL_BLOCK_ReadClient ReadClient;
/// EXTENSION_CONTROL_BLOCK_ServerSupportFunction
// changed to specific signiature for invalidation callback
public ServerSupportFunction_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK ServerSupportFunction;
}
/// Return Type: BOOL->int
///ConnID: DWORD->unsigned int
///dwServerSupportFunction: DWORD->unsigned int
///lpvBuffer: LPVOID->void*
///lpdwSize: LPDWORD->DWORD*
///lpdwDataType: LPDWORD->DWORD*
[return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
public delegate bool ServerSupportFunction_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK(
uint ConnID,
uint dwServerSupportFunction, // must be HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK
out Callback_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK lpvBuffer,
out uint lpdwSize,
out uint lpdwDataType);
public readonly uint HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK = 1040;
// typedef HRESULT (WINAPI * PFN_HSE_CACHE_INVALIDATION_CALLBACK)(WCHAR *pszUrl);
[return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
public delegate bool Callback_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK(
[MarshalAs(UnmanagedType.LPWStr)]string url);
object GetField (Type t, object o, string fieldName)
{
FieldInfo fld = t.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
return fld == null ? null : fld.GetValue(o);
}
protected void Page_Load(object sender, EventArgs e)
{
// first, get the ECB from the ISAPIWorkerRequest
var ctx = HttpContext.Current;
HttpWorkerRequest wr = (HttpWorkerRequest) GetField(typeof(HttpContext), ctx, "_wr");
IntPtr ecbPtr = IntPtr.Zero;
for (var t = wr.GetType(); t != null && t != typeof(object); t = t.BaseType)
{
object o = GetField(t, wr, "_ecb");
if (o != null)
{
ecbPtr = (IntPtr)o;
break;
}
}
// now call the ECB callback function to remove the item from cache
if (ecbPtr != IntPtr.Zero)
{
EXTENSION_CONTROL_BLOCK ecb = (EXTENSION_CONTROL_BLOCK)Marshal.PtrToStructure(
ecbPtr, typeof(EXTENSION_CONTROL_BLOCK));
uint dummy1, dummy2;
Callback_HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK invalidationCallback;
ecb.ServerSupportFunction(ecb.connID,
HSE_REQ_GET_CACHE_INVALIDATION_CALLBACK,
out invalidationCallback,
out dummy1,
out dummy2);
bool success = invalidationCallback("/this/is/a/test");
}
}
}
From the discussion link you provided, it appears that the cache method or property exists but is protected or private so you can't access it.
Normally, you should stay away from using methods that are not part of the public API, but if you want to access them, use Reflection. With reflection, you can call private methods and get or set private properties and fields.